Implementacja onScrollListener do wykrywania końca przewijania w widoku listy

Mam ListView wyświetlanie niektórych elementów. Chciałbym wykonać pewną operację na elementach, które są obecnie wyświetlane w widocznej części ListView, w zależności od tego, jak ListView zostało przewinięte; dlatego pomyślałem, że zaimplementuję OnScrollListener z ListView. Zgodnie z odniesieniem API Androida, metoda onScroll "zostanie wywołana po zakończeniu przewijania". To wydaje mi się słuszne, czego potrzebowałem, ponieważ po zakończeniu przewijania wykonuję moje działania na ListView (metoda onScroll zwraca indeks pierwszej wyświetlanej pozycji oraz liczbę wyświetlonych pozycji).

Ale po zaimplementowaniu widzę z LogCat, że metoda onScroll nie jest wywoływana tylko po zakończeniu przewijania, ale jest wywoływana dla każdego nowego elementu, który wchodzi do wyświetlania widoku, od początku do końca przewijania. Tego nie oczekuję ani nie potrzebuję. Inna metoda słuchacza (onScrollStateChanged), zamiast tego, nie dostarcza informacji o elementach aktualnie wyświetlane w ListView.

Czy ktoś wie, jak wykorzystać te kilka metod, aby wykryć zakończenie przewijania i uzyskać informacje o wyświetlanych elementach? Niewspółosiowość między odniesieniem api a rzeczywistym zachowaniem metody trochę mnie zdezorientowała. Z góry dzięki.

P. S.: widziałem podobne tematy, ale nic nie pomaga mi zrozumieć, jak to wszystko działa..!

Author: Andro Selva, 2011-06-15

9 answers

W końcu doszedłem do rozwiązania nie tak eleganckiego, ale to działało dla mnie; zorientowawszy się, że metoda onScroll jest wywoływana dla każdego kroku przewijania, a nie tylko wywoływana na końcu przewijania, a że onscrollstatechanged jest wywoływana dopiero po zakończeniu przewijania, robię coś takiego: {]}

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! ***/
    }
}

Praktycznie za każdym razem, gdy jest przewijany ListView zapisuję dane o pierwszym widocznym elemencie i liczbie widocznych elementów (metoda onScroll); gdy stan przewijania się zmienia (onScrollStateChanged) zapisuję stan i wywołuję inną metodę, która faktycznie rozumie, czy nastąpiło przewijanie i czy jest zakończone. W ten sposób mam również dane o widocznych elementach, których potrzebuję. Może nie czysty, ale działa!

Pozdrawiam

 53
Author: e-cal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-17 10:46:33
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {

if (list.getLastVisiblePosition() == list.getAdapter().getCount() - 1
&& list.getChildAt(list.getChildCount() - 1).getBottom() <= list.getHeight()) {
//scroll end reached
//Write your code here
}
}
 13
Author: Duna,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-13 07:21:01
@Override
        public void onScroll(AbsListView view, int firstVisibleItem,
                int visibleItemCount, int totalItemCount) {

            if((firstVisibleItem + visibleItemCount) ==  totalItemCount)
            {
                Log.i("Info", "Scroll Bottom" );
            }
        }});
 9
Author: trytrysee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-17 01:25:07

Opanowanie tego zachowania jest trudne, a doskonalenie Zajęło mi sporo czasu. Mięsem problemu jest to, że słuchacze przewijania same w sobie nie są naprawdę wystarczające, aby wykryć "scroll stop" (w tym przyciski kierunkowe/trackball), o ile mogłem powiedzieć. Skończyło się na tym, że zrobiłem kombinację rzeczy, które działają tak, jak tego oczekuję.

Najlepszym sposobem, w jaki mogłem to zrobić, było rozszerzenie ListView i nadpisanie kilku metod:

  ....
  @Override
  public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN || keyCode == KeyEvent.KEYCODE_DPAD_UP) {
      startWait();
    }
    return super.onKeyUp(keyCode, event);
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
      stopWait();
    }
    if (event.getAction() == MotionEvent.ACTION_UP ||
        event.getAction() == MotionEvent.ACTION_CANCEL) {
      startWait();
    }
    return super.onTouchEvent(event);
  }

  private OnScrollListener customScrollListener = new OnScrollListener() {
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
      int totalItemCount) {
      stopWait();
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
      if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
        startWait();
      } else {
        stopWait();
      }
    }
  };

  //All this waiting could be improved, but that's the idea
  private Thread waitThread = null;
  private int waitCount  = Integer.MIN_VALUE;

  public void stopWait() {
    waitCount = Integer.MIN_VALUE;
  }

  public synchronized void startWait() {
    waitCount = 0;

    if (waitThread != null) {
      return;
    }

    waitThread = new Thread(new Runnable() {
    @Override
    public void run() {
    try {
      for (; waitCount.get() < 10; waitCount++) {
        Thread.sleep(50);
      }

        //Kick it back to the UI thread.
        view.post(theRunnableWithYourOnScrollStopCode); // HERE IS WHERE YOU DO WHATEVER YOU WANTED TO DO ON STOP
      } catch (InterruptedException e) {
      } finally  {
        waitThread = null;
      }
    }
  });
  waitThread.start();
}

Zauważ, że musisz również bind the customScrollListener in your constructors. Myślę, że ta implementacja jest fajna, ponieważ nie odpali od razu "eventu", będzie trochę czekać, aż faktycznie całkowicie przestanie przewijać.

 6
Author: dmon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-15 13:55:25

Spróbuj tego...

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
                     int totalItemCount) {

    int position = firstVisibleItem+visibleItemCount;
    int limit = totalItemCount;

    // Check if bottom has been reached
    if (position >= limit && totalItemCount > 0) {
         //scroll end reached
          //Write your code here
    }
}
 3
Author: user4813506,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-21 06:49:28

Tylko bardziej przydatna Reprezentacja odpowiedzi e-cala

Realizacja słuchacza:

public abstract class OnScrollFinishListener implements AbsListView.OnScrollListener {

    int mCurrentScrollState;
    int mCurrentVisibleItemCount;

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        mCurrentScrollState = scrollState;
        if (isScrollCompleted()) {
            onScrollFinished();
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        mCurrentVisibleItemCount = visibleItemCount;
    }

    private boolean isScrollCompleted() {
        return mCurrentVisibleItemCount > 0 && mCurrentScrollState == SCROLL_STATE_IDLE;
    }

    protected abstract void onScrollFinished();
}

Użycie:

    listView.setOnScrollListener(new OnScrollFinishListener() {
         @Override
         protected void onScrollFinished() {
             // do whatever you need here!
         }
    });
 3
Author: Oleksandr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:33:26

Analizując poprzednie odpowiedzi, znalazłem własne:

W onViewCreated, onCreate lub gdziekolwiek chwycisz listview:

ListView list = (ListView) view.findViewById(R.id.[your id]);
    list.setOnScrollListener(this);

Następnie wypełnij metody słuchacza:

public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
 // yes, just leave it empty
}

public void onScrollStateChanged(AbsListView view, int scrollState) {
    this.isScrollCompleted(view, scrollState);
 }

private void isScrollCompleted(AbsListView view, int scrollState) {
    if (!view.canScrollVertically(1) && this.currentScrollState == SCROLL_STATE_IDLE) {
        // do work
    }
}

Wygląda na to, że robi to, co powinno: wykrywa koniec listy bez ton kodu i modyfikacji adaptera.

 3
Author: Nikola-L_F,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-25 15:57:32

Jest jeszcze jedno proste rozwiązanie. Umieść słuchacz na adapterze listview i w getview sprawdź czy (position= = totalItemCount elementów w tablicy, które mają być wyświetlane), jeśli tak wywołuję mój słuchacz z adaptera i w słuchaczu Uruchom swoją operację niezależnie od tego, czy to będzie

 0
Author: Ajith Memana,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-18 08:57:01

Doświadczyłem wielu problemów, próbując dowiedzieć się, czy lista jest na dole z Onscrollistener, najlepszym rozwiązaniem znalazłem, aby sprawdzić z metody getView adaptera, jeśli currntly pokazuje ostatni wiersz:

@Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ActivityView view;
        if (convertView==null)
            view = (ActivityView)inflater.inflate(R.layout.activity_item, null);
        else
            view = (ActivityView)convertView;

       view.showRow(activitiesList.get(position),position,controller,this);
        if(position == activitiesList.size() - 1)
        {
            Log.i("BOTTOM","reached");
        }
        return view;
    }

Tam mogę łatwo utworzyć interfejs, aby powiadomić Fragment / aktywność, że dotarłem do dołu.

Zdrówko.
 0
Author: Gal Rom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-06 03:04:19