Odpowiedzi:
Edytowano :
Ponieważ badam ten konkretny temat w jednej z moich aplikacji, mogę napisać rozszerzoną odpowiedź dla przyszłych czytelników na to pytanie.
Zaimplementuj OnScrollListener
, ustaw własne ListView
, onScrollListener
a wtedy powinieneś być w stanie poprawnie obsługiwać rzeczy.
Na przykład:
private int preLast;
// Initialization stuff.
yourListView.setOnScrollListener(this);
// ... ... ...
@Override
public void onScroll(AbsListView lw, final int firstVisibleItem,
final int visibleItemCount, final int totalItemCount)
{
switch(lw.getId())
{
case R.id.your_list_id:
// Make your calculation stuff here. You have all your
// needed info from the parameters of this function.
// Sample calculation to determine if the last
// item is fully visible.
final int lastItem = firstVisibleItem + visibleItemCount;
if(lastItem == totalItemCount)
{
if(preLast!=lastItem)
{
//to avoid multiple calls for last item
Log.d("Last", "Last");
preLast = lastItem;
}
}
}
}
listview
jest stackFromBottom
? Próbowałem, if (0 == firstVisibleItem){//listviewtop}
ale jest to wielokrotnie wywoływane.
Późna odpowiedź, ale jeśli chcesz po prostu sprawdzić, czy Twój ListView jest przewijany do końca, czy nie, bez tworzenia detektora zdarzeń, możesz użyć tej instrukcji if:
if (yourListView.getLastVisiblePosition() == yourListView.getAdapter().getCount() -1 &&
yourListView.getChildAt(yourListView.getChildCount() - 1).getBottom() <= yourListView.getHeight())
{
//It is scrolled all the way down here
}
Najpierw sprawdza, czy widoczna jest ostatnia możliwa pozycja. Następnie sprawdza, czy dół ostatniego przycisku jest wyrównany z dołem ListView. Możesz zrobić coś podobnego, aby dowiedzieć się, czy jest na górze:
if (yourListView.getFirstVisiblePosition() == 0 &&
yourListView.getChildAt(0).getTop() >= 0)
{
//It is scrolled all the way up here
}
getChildCount()
zwraca widoki w grupie widoków, co w przypadku recyklingu widoków nie jest tym samym, co liczba elementów w adapterze. Jednak ponieważ ListView wywodzi się z AdapterView, można go używać getCount()
bezpośrednio w ListView.
Sposób, w jaki to zrobiłem:
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE
&& (listView.getLastVisiblePosition() - listView.getHeaderViewsCount() -
listView.getFooterViewsCount()) >= (adapter.getCount() - 1)) {
// Now your listview has hit the bottom
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
});
Coś w efekcie:
if (getListView().getLastVisiblePosition() == (adapter.items.size() - 1))
public void onScrollStateChanged(AbsListView view, int scrollState)
{
if (!view.canScrollList(View.SCROLL_AXIS_VERTICAL) && scrollState == SCROLL_STATE_IDLE)
{
//When List reaches bottom and the list isn't moving (is idle)
}
}
To zadziałało dla mnie.
To może być
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
if (scrollState == 2)
flag = true;
Log.i("Scroll State", "" + scrollState);
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// TODO Auto-generated method stub
if ((visibleItemCount == (totalItemCount - firstVisibleItem))
&& flag) {
flag = false;
Log.i("Scroll", "Ended");
}
}
Dość bolesne było radzenie sobie z przewijaniem, wykrywaniem, kiedy jest zakończone i rzeczywiście znajduje się na dole listy (nie na dole widocznego ekranu) i uruchamia moją usługę tylko raz, aby pobrać dane z sieci. Jednak teraz działa dobrze. Kod jest następujący, z korzyścią dla każdego, kto znajduje się w takiej samej sytuacji.
UWAGA: Musiałem przenieść mój kod związany z adapterem do onViewCreated zamiast onCreate i wykryć przewijanie głównie w ten sposób:
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {}
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (getListView().getLastVisiblePosition() == (adapter.getCount() - 1))
if (RideListSimpleCursorAdapter.REACHED_THE_END) {
Log.v(TAG, "Loading more data");
RideListSimpleCursorAdapter.REACHED_THE_END = false;
Intent intent = new Intent(getActivity().getApplicationContext(), FindRideService.class);
getActivity().getApplicationContext().startService(intent);
}
}
Tutaj RideListSimpleCursorAdapter.REACHED_THE_END jest dodatkową zmienną w moim SimpleCustomAdapter, która jest ustawiona w następujący sposób:
if (position == getCount() - 1) {
REACHED_THE_END = true;
} else {
REACHED_THE_END = false;
}
Dopiero spełnienie obu tych warunków oznacza, że rzeczywiście jestem na dole listy, a moja usługa będzie działać tylko raz. Jeśli nie złapię REACHED_THE_END, nawet przewijanie do tyłu uruchamia usługę ponownie, o ile ostatni element jest widoczny.
canScrollVertically(int direction)
działa dla wszystkich widoków i wydaje się robić to, o co prosiłeś, z mniejszą ilością kodu niż większość innych odpowiedzi. Wpisz liczbę dodatnią, a jeśli wynik jest fałszywy, jesteś na dole.
to znaczy:
if (!yourView.canScrollVertically(1)) {
//you've reached bottom
}
Aby rozwinąć nieco jedną z powyższych odpowiedzi, oto, co musiałem zrobić, aby całkowicie działała. Wygląda na to, że istnieje około 6dp wbudowanego dopełnienia wewnątrz ListViews, a onScroll () był wywoływany, gdy lista była pusta. To obsługuje obie te rzeczy. Prawdopodobnie mógłby być nieco zoptymalizowany, ale jest napisany bardziej dla przejrzystości.
Uwaga dodatkowa: próbowałem kilku różnych technik konwersji z dp na piksel, a ta dp2px () była najlepsza.
myListView.setOnScrollListener(new OnScrollListener() {
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (visibleItemCount > 0) {
boolean atStart = true;
boolean atEnd = true;
View firstView = view.getChildAt(0);
if ((firstVisibleItem > 0) ||
((firstVisibleItem == 0) && (firstView.getTop() < (dp2px(6) - 1)))) {
// not at start
atStart = false;
}
int lastVisibleItem = firstVisibleItem + visibleItemCount;
View lastView = view.getChildAt(visibleItemCount - 1);
if ((lastVisibleItem < totalItemCount) ||
((lastVisibleItem == totalItemCount) &&
((view.getHeight() - (dp2px(6) - 1)) < lastView.getBottom()))
) {
// not at end
atEnd = false;
}
// now use atStart and atEnd to do whatever you need to do
// ...
}
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
});
private int dp2px(int dp) {
return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
Nie mogę jeszcze komentować, ponieważ nie mam wystarczającej reputacji, ale w odpowiedzi @Ali Imran i @Wroclai wydaje mi się, że czegoś brakuje. Z tym fragmentem kodu, po zaktualizowaniu preLast, nigdy więcej nie uruchomi dziennika. W moim konkretnym problemie chcę wykonać jakąś operację za każdym razem, gdy przewijam do dołu, ale po zaktualizowaniu preLast do LastItem operacja ta nigdy nie jest wykonywana ponownie.
private int preLast;
// Initialization stuff.
yourListView.setOnScrollListener(this);
// ... ... ...
@Override
public void onScroll(AbsListView lw, final int firstVisibleItem,
final int visibleItemCount, final int totalItemCount) {
switch(lw.getId()) {
case android.R.id.list:
// Make your calculation stuff here. You have all your
// needed info from the parameters of this function.
// Sample calculation to determine if the last
// item is fully visible.
final int lastItem = firstVisibleItem + visibleItemCount;
if(lastItem == totalItemCount) {
if(preLast!=lastItem){ //to avoid multiple calls for last item
Log.d("Last", "Last");
preLast = lastItem;
}
} else {
preLast = lastItem;
}
}
Dzięki temu „else” możesz teraz wykonać swój kod (w tym przypadku Log) za każdym razem, gdy ponownie przewiniesz w dół.
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
int lastindex = view.getLastVisiblePosition() + 1;
if (lastindex == totalItemCount) { //showing last row
if ((view.getChildAt(visibleItemCount - 1)).getTop() == view.getHeight()) {
//Last row fully visible
}
}
}
Aby Twoja lista zadzwoniła, gdy lista osiągnie ostatnią pozycję i jeśli wystąpi błąd, nie spowoduje to ponownego wywołania endoflistview . Ten kod pomoże również w tym scenariuszu.
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
final int lastPosition = firstVisibleItem + visibleItemCount;
if (lastPosition == totalItemCount) {
if (previousLastPosition != lastPosition) {
//APPLY YOUR LOGIC HERE
}
previousLastPosition = lastPosition;
}
else if(lastPosition < previousLastPosition - LIST_UP_THRESHOLD_VALUE){
resetLastIndex();
}
}
public void resetLastIndex(){
previousLastPosition = 0;
}
gdzie LIST_UP_THRESHOLD_VALUE może być dowolną wartością całkowitą (użyłem 5), gdzie twoja lista jest przewijana w górę i podczas powrotu do końca, spowoduje to ponowne wywołanie końca widoku listy.
Znalazłem bardzo fajny sposób na automatyczne ładowanie kolejnej strony w sposób, który nie wymaga twojej własnej ScrollView
(jak wymaga zaakceptowana odpowiedź).
W ParseQueryAdapter jest wywoływana metoda, getNextPageView
która umożliwia dostarczenie własnego widoku niestandardowego, który pojawia się na końcu listy, gdy jest więcej danych do załadowania, więc zostanie wyzwolony dopiero po osiągnięciu końca bieżącego zestawu stron (jest to domyślnie widok „wczytaj więcej…”). Ta metoda jest wywoływana tylko wtedy, gdy jest więcej danych do załadowania, więc jest to świetne miejsce do wywołania W loadNextPage();
ten sposób adapter wykona całą ciężką pracę za Ciebie przy określaniu, kiedy należy załadować nowe dane i nie zostanie on w ogóle wywołany, jeśli masz osiągnął koniec zbioru danych.
public class YourAdapter extends ParseQueryAdapter<ParseObject> {
..
@Override
public View getNextPageView(View v, ViewGroup parent) {
loadNextPage();
return super.getNextPageView(v, parent);
}
}
Następnie w ramach swojej aktywności / fragmentu wystarczy ustawić adapter, a nowe dane zostaną automatycznie zaktualizowane, jak magia.
adapter = new YourAdapter(getActivity().getApplicationContext());
adapter.setObjectsPerPage(15);
adapter.setPaginationEnabled(true);
yourList.setAdapter(adapter);
Aby wykryć, czy ostatni element jest w pełni widoczny , możesz w prosty sposób dodać obliczenia na dole ostatniego widocznego elementu widoku lastItem.getBottom()
.
yourListView.setOnScrollListener(this);
@Override
public void onScroll(AbsListView view, final int firstVisibleItem,
final int visibleItemCount, final int totalItemCount) {
int vH = view.getHeight();
int topPos = view.getChildAt(0).getTop();
int bottomPos = view.getChildAt(visibleItemCount - 1).getBottom();
switch(view.getId()) {
case R.id.your_list_view_id:
if(firstVisibleItem == 0 && topPos == 0) {
//TODO things to do when the list view scroll to the top
}
if(firstVisibleItem + visibleItemCount == totalItemCount
&& vH >= bottomPos) {
//TODO things to do when the list view scroll to the bottom
}
break;
}
}
Poszedłem z:
@Override
public void onScroll(AbsListView listView, int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
if(totalItemCount - 1 == favoriteContactsListView.getLastVisiblePosition())
{
int pos = totalItemCount - favoriteContactsListView.getFirstVisiblePosition() - 1;
View last_item = favoriteContactsListView.getChildAt(pos);
//do stuff
}
}
W metodzie getView()
( BaseAdapter
klasy pochodnej) można sprawdzić, czy pozycja bieżącego widoku jest równa liście elementów w pliku Adapter
. Jeśli tak jest, oznacza to, że dotarliśmy do końca / dołu listy:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// ...
// detect if the adapter (of the ListView/GridView) has reached the end
if (position == getCount() - 1) {
// ... end of list reached
}
}
Znajduję lepszy sposób na wykrycie przewijania widoku listy na dole, najpierw wykryj koniec przewijania przez to
implementację onScrollListener, aby wykryć koniec przewijania w ListView
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
this.currentFirstVisibleItem = firstVisibleItem;
this.currentVisibleItemCount = visibleItemCount;
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
this.currentScrollState = scrollState;
this.isScrollCompleted();
}
private void isScrollCompleted() {
if (this.currentVisibleItemCount > 0 && this.currentScrollState == SCROLL_STATE_IDLE) {
/*** In this way I detect if there's been a scroll which has completed ***/
/*** do the work! ***/
}
}
w końcu połącz odpowiedź Martijna
OnScrollListener onScrollListener_listview = new OnScrollListener() {
private int currentScrollState;
private int currentVisibleItemCount;
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
this.currentScrollState = scrollState;
this.isScrollCompleted();
}
@Override
public void onScroll(AbsListView lw, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// TODO Auto-generated method stub
this.currentVisibleItemCount = visibleItemCount;
}
private void isScrollCompleted() {
if (this.currentVisibleItemCount > 0 && this.currentScrollState == SCROLL_STATE_IDLE) {
/*** In this way I detect if there's been a scroll which has completed ***/
/*** do the work! ***/
if (listview.getLastVisiblePosition() == listview.getAdapter().getCount() - 1
&& listview.getChildAt(listview.getChildCount() - 1).getBottom() <= listview.getHeight()) {
// It is scrolled all the way down here
Log.d("henrytest", "hit bottom");
}
}
}
};
Wielkie dzięki za plakaty w stackoverflow! Połączyłem kilka pomysłów i stworzyłem odbiornik klas dla działań i fragmentów (więc ten kod jest bardziej wielokrotnego użytku, dzięki czemu kod jest szybszy do pisania i znacznie czystszy).
Po otrzymaniu mojej klasy wystarczy zaimplementować interfejs (i oczywiście stworzyć dla niego metodę), który jest zadeklarowany w mojej klasie i stworzyć obiekt tej klasy przekazujący argumenty.
/**
* Listener for getting call when ListView gets scrolled to bottom
*/
public class ListViewScrolledToBottomListener implements AbsListView.OnScrollListener {
ListViewScrolledToBottomCallback scrolledToBottomCallback;
private int currentFirstVisibleItem;
private int currentVisibleItemCount;
private int totalItemCount;
private int currentScrollState;
public interface ListViewScrolledToBottomCallback {
public void onScrolledToBottom();
}
public ListViewScrolledToBottomListener(Fragment fragment, ListView listView) {
try {
scrolledToBottomCallback = (ListViewScrolledToBottomCallback) fragment;
listView.setOnScrollListener(this);
} catch (ClassCastException e) {
throw new ClassCastException(fragment.toString()
+ " must implement ListViewScrolledToBottomCallback");
}
}
public ListViewScrolledToBottomListener(Activity activity, ListView listView) {
try {
scrolledToBottomCallback = (ListViewScrolledToBottomCallback) activity;
listView.setOnScrollListener(this);
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement ListViewScrolledToBottomCallback");
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
this.currentFirstVisibleItem = firstVisibleItem;
this.currentVisibleItemCount = visibleItemCount;
this.totalItemCount = totalItemCount;
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
this.currentScrollState = scrollState;
if (isScrollCompleted()) {
if (isScrolledToBottom()) {
scrolledToBottomCallback.onScrolledToBottom();
}
}
}
private boolean isScrollCompleted() {
if (this.currentVisibleItemCount > 0 && this.currentScrollState == SCROLL_STATE_IDLE) {
return true;
} else {
return false;
}
}
private boolean isScrolledToBottom() {
System.out.println("First:" + currentFirstVisibleItem);
System.out.println("Current count:" + currentVisibleItemCount);
System.out.println("Total count:" + totalItemCount);
int lastItem = currentFirstVisibleItem + currentVisibleItemCount;
if (lastItem == totalItemCount) {
return true;
} else {
return false;
}
}
}
Musisz dodać pusty zasób stopki XML do swojej listView i sprawdzić, czy ta stopka jest widoczna.
private View listViewFooter;
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_newsfeed, container, false);
listView = (CardListView) rootView.findViewById(R.id.newsfeed_list);
footer = inflater.inflate(R.layout.newsfeed_listview_footer, null);
listView.addFooterView(footer);
return rootView;
}
Następnie robisz to w swoim listener przewijanym listView
@
Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (firstVisibleItem == 0) {
mSwipyRefreshLayout.setDirection(SwipyRefreshLayoutDirection.TOP);
mSwipyRefreshLayout.setEnabled(true);
} else if (firstVisibleItem + visibleItemCount == totalItemCount) //If last row is visible. In this case, the last row is the footer.
{
if (footer != null) //footer is a variable referencing the footer view of the ListView. You need to initialize this onCreate
{
if (listView.getHeight() == footer.getBottom()) { //Check if the whole footer is visible.
mSwipyRefreshLayout.setDirection(SwipyRefreshLayoutDirection.BOTTOM);
mSwipyRefreshLayout.setEnabled(true);
}
}
} else
mSwipyRefreshLayout.setEnabled(false);
}
Jeśli ustawisz znacznik w widoku ostatniego elementu widoku listy, możesz później pobrać widok ze znacznikiem, jeśli widok jest pusty, to dlatego, że widok nie jest już ładowany. Lubię to:
private class YourAdapter extends CursorAdapter {
public void bindView(View view, Context context, Cursor cursor) {
if (cursor.isLast()) {
viewInYourList.setTag("last");
}
else{
viewInYourList.setTag("notLast");
}
}
}
jeśli chcesz wiedzieć, czy ostatni element jest załadowany
View last = yourListView.findViewWithTag("last");
if (last != null) {
// do what you want to do
}
Janwilx72 ma rację, ale min sdk to 21, więc tworzę tę metodę :
private boolean canScrollList(@ScrollOrientation int direction, AbsListView listView) {
final int childCount = listView.getChildCount();
if (childCount == 0) {
return false;
}
final int firstPos = listView.getFirstVisiblePosition();
final int paddingBottom = listView.getListPaddingBottom();
final int paddingTop = listView.getListPaddingTop();
if (direction > 0) {
final int lastBottom = listView.getChildAt(childCount - 1).getBottom();
final int lastPos = firstPos + childCount;
return lastPos < listView.getChildCount() || lastBottom > listView.getHeight() - paddingBottom;
} else {
final int firstTop = listView.getChildAt(0).getTop();
return firstPos > 0 || firstTop < paddingTop;
}
}
dla ScrollOrientation:
protected static final int SCROLL_UP = -1;
protected static final int SCROLL_DOWN = 1;
@Retention(RetentionPolicy.SOURCE)
@IntDef({SCROLL_UP, SCROLL_DOWN})
protected @interface Scroll_Orientation{}
Może późno, tylko dla spóźnialskich。
Jeśli używasz niestandardowego adaptera z widokiem listy (większość ludzi tak robi!), Znajdziesz tutaj piękne rozwiązanie!
https://stackoverflow.com/a/55350409/1845404
Metoda getView adaptera wykrywa, kiedy lista została przewinięta do ostatniej pozycji. Dodaje również korektę w rzadkich przypadkach, gdy wywoływana jest wcześniejsza pozycja, nawet po wyrenderowaniu przez adapter ostatniego widoku.
Zrobiłem to i działa dla mnie:
private void YourListView_Scrolled(object sender, ScrolledEventArgs e)
{
double itemheight = YourListView.RowHeight;
double fullHeight = YourListView.Count * itemheight;
double ViewHeight = YourListView.Height;
if ((fullHeight - e.ScrollY) < ViewHeight )
{
DisplayAlert("Reached", "We got to the end", "OK");
}
}
Spowoduje to przewinięcie listy do ostatniego wpisu.
ListView listView = new ListView(this);
listView.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT));
listView.setTranscriptMode(ListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);
listView.setStackFromBottom(true);