Jak wdrożyć niekończącą się listę za pomocą RecyclerView?


346

Chciałbym się zmienić ListViewna RecyclerView. Chcę używać onScrolltego OnScrollListenerIN RecyclerView, aby określić, czy użytkownik przewijane do końca listy.

Skąd mam wiedzieć, czy użytkownik przewija do końca listy, aby móc pobrać nowe dane z usługi REST?


Sprawdź ten link Pomoże ci to .. stackoverflow.com/questions/26293461/
samsad

32
Przeczytaj uważnie pytanie! Wiem, jak to zrobić za pomocą ListView, ale NIE jak zaimplementować go za pomocą RecycleView .
erdna

jak zaimplementować loadmore onscrolllistener w android.data jest załadowany, ale zaktualizowany o istniejące dane, pomóż mi
Harsha

2
Możesz użyć tej biblioteki: github.com/rockerhieu/rv-adapter-endless . Opiera się na idei cwac-endlessfor ListView.
Hieu Rocker

Odpowiedzi:


422

Dzięki @Kushal i tak to zaimplementowałem

private boolean loading = true;
int pastVisiblesItems, visibleItemCount, totalItemCount;

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        if (dy > 0) { //check for scroll down
            visibleItemCount = mLayoutManager.getChildCount();
            totalItemCount = mLayoutManager.getItemCount();
            pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();

            if (loading) {
                if ((visibleItemCount + pastVisiblesItems) >= totalItemCount) {
                    loading = false;
                    Log.v("...", "Last Item Wow !");
                    // Do pagination.. i.e. fetch new data
                }
            }
        }
    }
});

Nie zapomnij dodać

LinearLayoutManager mLayoutManager;
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);

11
Co mogę zrobićStaggeredGridLayoutManager
Pratik Butani

16
ComLayoutManager.findLastCompletelyVisibleItemPosition()==mLayoutManager.getItemCount()-1
laaptu

45
Wszystkim, którzy findFirstVisibleItemPosition()nie rozwiązali, powinieneś zmienić RecyclerView.LayoutManagernaLinearLayoutManager
Amr Mohammed

26
gdzie jest warunek loading = trueponownego zrobienia ?
Bhargav,

10
Nie zapomnij dodać loading = truepo //Do paginationczęści. w przeciwnym razie ten kod zostanie uruchomiony tylko raz i nie będzie można załadować więcej elementów.
Shaishav Jogani,

165

Utwórz te zmienne.

private int previousTotal = 0;
private boolean loading = true;
private int visibleThreshold = 5;
int firstVisibleItem, visibleItemCount, totalItemCount;

Ustaw opcję Przewiń, aby wyświetlić widok recyklera.

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        visibleItemCount = mRecyclerView.getChildCount();
        totalItemCount = mLayoutManager.getItemCount();
        firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();

        if (loading) {
            if (totalItemCount > previousTotal) {
                loading = false;
                previousTotal = totalItemCount;
            }
        }
        if (!loading && (totalItemCount - visibleItemCount) 
            <= (firstVisibleItem + visibleThreshold)) {
            // End has been reached

            Log.i("Yaeye!", "end called");

            // Do something

            loading = true;
        }
    }
});

Uwaga: upewnij się, że używasz LinearLayoutManagerjako menedżera układu dla RecyclerView.

LinearLayoutManager mLayoutManager;
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);

i dla siatki

GridLayoutManager mLayoutManager;
mLayoutManager = new GridLayoutManager(getActivity(), spanCount);
mRecyclerView.setLayoutManager(mLayoutManager);

Baw się z niekończącymi się zwojami !! ^. ^

Aktualizacja: mRecyclerView. setOnScrollListener () jest przestarzałe, po prostu zamień na, mRecyclerView.addOnScrollListener()a ostrzeżenie zniknie! Możesz przeczytać więcej z tego pytania SO .

Ponieważ Android oficjalnie obsługuje teraz Kotlin, oto aktualizacja tego samego -

Utwórz OnScrollListener

class OnScrollListener(val layoutManager: LinearLayoutManager, val adapter: RecyclerView.Adapter<RecyclerAdapter.ViewHolder>, val dataList: MutableList<Int>) : RecyclerView.OnScrollListener() {
    var previousTotal = 0
    var loading = true
    val visibleThreshold = 10
    var firstVisibleItem = 0
    var visibleItemCount = 0
    var totalItemCount = 0

    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)

        visibleItemCount = recyclerView.childCount
        totalItemCount = layoutManager.itemCount
        firstVisibleItem = layoutManager.findFirstVisibleItemPosition()

        if (loading) {
            if (totalItemCount > previousTotal) {
                loading = false
                previousTotal = totalItemCount
            }
        }

        if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
            val initialSize = dataList.size
            updateDataList(dataList)
            val updatedSize = dataList.size
            recyclerView.post { adapter.notifyItemRangeInserted(initialSize, updatedSize) }
            loading = true
        }
    }
}

i dodaj go do swojego RecyclerView w ten sposób

recyclerView.addOnScrollListener(OnScrollListener(layoutManager, adapter, dataList))

Aby uzyskać pełny kod, zachęcamy do zapoznania się z tym repozytorium Github .


14
Masz pomysł, jak zastosować to rozwiązanie za pomocą GridLayoutManager? Ponieważ findFirstVisibleItemPosition()metoda jest dostępna tylko z LinearLayoutManager, nie mogę wymyślić, jak to zrobić.
MathieuMaree

1
Informacje dodatkowe: w tym przypadku używanie visibleItemCount = mLayoutManager.getChildCount();zachowuje się tak samo jak używanie visibleItemCount = mRecyclerView.getChildCount();.
Jing Li

3
Umieść kod w metodzie onScrollStateChanged () zamiast w metodzie onScrolled ().
Anirudh

2
@ toobsco42 Możesz użyć tego:int[] firstVisibleItemPositions = new int[yourNumberOfColumns]; pastVisiblesItems = layoutManager.findFirstVisibleItemPositions(firstVisibleItemPositions)[0];
MathieuMaree

1
Co to jest findFirstVisibleItemPosition () nie rozwiązany
Suresh Sharma

72

Dla tych, którzy chcą otrzymywać powiadomienia tylko, gdy ostatni element jest całkowicie pokazany, możesz użyć View.canScrollVertically().

Oto moja implementacja:

public abstract class OnVerticalScrollListener
        extends RecyclerView.OnScrollListener {

    @Override
    public final void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        if (!recyclerView.canScrollVertically(-1)) {
            onScrolledToTop();
        } else if (!recyclerView.canScrollVertically(1)) {
            onScrolledToBottom();
        } else if (dy < 0) {
            onScrolledUp();
        } else if (dy > 0) {
            onScrolledDown();
        }
    }

    public void onScrolledUp() {}

    public void onScrolledDown() {}

    public void onScrolledToTop() {}

    public void onScrolledToBottom() {}
}

Uwaga: Możesz użyć, recyclerView.getLayoutManager().canScrollVertically()jeśli chcesz obsługiwać API <14.


To świetna i prosta odpowiedź! Dziękuję Ci!
Andranik

2
Podobne rozwiązanie można zastosować do ściągnięcia w celu odświeżenia, sprawdzając! RecyclinglerView.canScrollVertically (-1).
LordParsley

Najlepsze rozwiązanie. Dzięki stary!
Angmar

1
@ LordParsley Dziękuję, zaktualizowano. Jest to również jeden ze sposobów, w jaki SwipeRefreshLayout sprawdza ściąganie w celu odświeżenia.
Hai Zhang,

1
@EpicPandaForce Jeśli używasz RecyclerView, będziesz mieć szczęście, ponieważ możesz po prostu zrobić statyczną kopię, View.canScrollVertically()ponieważ powiązane metody są oznaczone jako publiczne zamiast chronione RecyclerView. Jeśli chcesz, możesz także przedłużyć RecyclerViewimplementację canScrollVertically(). Trzecim i prostym sposobem jest zadzwonienie recyclerView.getLayoutManager().canScrollVertically(). Edytowane w mojej odpowiedzi.
Hai Zhang

67

Oto inne podejście. Będzie działać z dowolnym menedżerem układu.

  1. Spraw, aby klasa adaptera była abstrakcyjna
  2. Następnie utwórz metodę abstrakcyjną w klasie adaptera (np. Load ())
  3. W onBindViewHolder sprawdź pozycję, jeśli ostatnia, i wywołaj load ()
  4. Zastąp funkcję load () podczas tworzenia obiektu adaptera w działaniu lub fragmencie.
  5. W funkcji overided load zaimplementuj wywołanie loadmore

Aby uzyskać szczegółowe zrozumienie, napisałem post na blogu i przykładowy projekt: http://sab99r.com/blog/recyclerview-endless-load-more/

MyAdapter.java

public abstract class MyAdapter extends RecyclerView.Adapter<ViewHolder>{

        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            //check for last item
            if ((position >= getItemCount() - 1))
                load();
        }

        public abstract void load();
}

MyActivity.java

public class MainActivity extends AppCompatActivity {
    List<Items> items;
    MyAdapter adapter;

   @Override
    protected void onCreate(Bundle savedInstanceState) {
    ...
    adapter=new MyAdapter(items){
            @Override
            public void load() {
                //implement your load more here
                Item lastItem=items.get(items.size()-1);
                loadMore();
            }
        };
   }
}

3
Podobnie jak w przypadku rozwiązania, nie musisz tworzyć streszczenia adaptera, zamiast tego możesz utworzyć interfejs w adapterze i zaimplementować go w działaniu, zachowując go elastycznym i eleganckim, np .: ItemDisplayListener => onItemDisplayed (pozycja wewnętrzna). W ten sposób możesz załadować w dowolnej pozycji: w N, N-1, N-2 ..
Samer

1
@mcwise dodaj wartość logiczną zakończoną = fałsz; po osiągnięciu końca zmień na ukończony = prawda .. zmień aktualny warunek if na if ((pozycja> = getItemCount () - 1) i& (! zakończono))
Sabeer Mohammed

1
To powinno być oznaczone jako poprawna odpowiedź. To jest eleganckie i proste.
Greg Ennis,

2
Dobry przykład doskonałego rozwiązania zakopanego pod popularnością pierwszego rozwiązania. Bardzo często ostatni wiersz zostanie powiązany z osobnym typem widoku, który służy jako baner „Ładowanie ...”. Gdy ten typ widoku otrzyma wywołanie powiązania, skonfiguruje wywołanie zwrotne, które wyzwoli powiadomienie o zmianie danych, gdy dostępnych będzie więcej danych. Przyjęta odpowiedź podłączenia odbiornika przewijania jest marnotrawstwem i prawdopodobnie nie działa w przypadku innych typów układów.
Michael Hays

3
@mcwise zadzwoniłby tylko w nieskończoność, gdyby użytkownik aktywnie przewinął i wrócił do ostatniego elementu. Funkcja onBindViewHolder jest wywoływana tylko raz za każdym razem, gdy widok pojawia się na ekranie, i nie będzie go stale wywoływać.
David Liu,

18

Moja odpowiedź to zmodyfikowana wersja Noora. Przeszedłem z miejsca, w ListViewktórym miałem EndlessScrollListener(który można łatwo znaleźć w wielu odpowiedziach na SO), RecyclerViewwięc chciałem EndlessRecyclScrollListenerłatwo zaktualizować mojego poprzedniego słuchacza.

Oto kod, mam nadzieję, że pomoże:

public abstract class EndlessScrollRecyclListener extends RecyclerView.OnScrollListener
{
    // The total number of items in the dataset after the last load
    private int previousTotalItemCount = 0;
    private boolean loading = true;
    private int visibleThreshold = 5;
    int firstVisibleItem, visibleItemCount, totalItemCount;
    private int startingPageIndex = 0;
    private int currentPage = 0;

    @Override
    public void onScrolled(RecyclerView mRecyclerView, int dx, int dy)
    {
        super.onScrolled(mRecyclerView, dx, dy);
        LinearLayoutManager mLayoutManager = (LinearLayoutManager) mRecyclerView
                .getLayoutManager();

        visibleItemCount = mRecyclerView.getChildCount();
        totalItemCount = mLayoutManager.getItemCount();
        firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();
        onScroll(firstVisibleItem, visibleItemCount, totalItemCount);
    }

    public void onScroll(int firstVisibleItem, int visibleItemCount, int totalItemCount)
    {
        // If the total item count is zero and the previous isn't, assume the
        // list is invalidated and should be reset back to initial state
        if (totalItemCount < previousTotalItemCount)
        {
            this.currentPage = this.startingPageIndex;
            this.previousTotalItemCount = totalItemCount;
            if (totalItemCount == 0)
            {
                this.loading = true;
            }
        }
        // If it’s still loading, we check to see if the dataset count has
        // changed, if so we conclude it has finished loading and update the current page
        // number and total item count.
        if (loading && (totalItemCount > previousTotalItemCount))
        {
            loading = false;
            previousTotalItemCount = totalItemCount;
            currentPage++;
        }

        // If it isn’t currently loading, we check to see if we have breached
        // the visibleThreshold and need to reload more data.
        // If we do need to reload some more data, we execute onLoadMore to fetch the data.
        if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem +
                visibleThreshold))
        {
            onLoadMore(currentPage + 1, totalItemCount);
            loading = true;
        }
    }

    // Defines the process for actually loading more data based on page
    public abstract void onLoadMore(int page, int totalItemsCount);

}

1
Twoja implementacja wymaga RecyclerView do korzystania z LinearLeayoutManager. Kiepski pomysł!
Orri

@Orri Czy możesz wyjaśnić, dlaczego? Dzięki!
Federico Ponzi

1
W swoim onScrolled rzuciłeś LayoutManager z RecyclerView na LinearLayoutManager. Nie działa dla StaggredGridLayout lub czegokolwiek innego. W StaggredGridLayout nie ma findFirstVisibleItemPosition ().
Orri

@Orri Dziękujemy za zwrócenie na to uwagi! Ale nie sądzę, że to taki zły pomysł, ponieważ zadziałało to na mnie i, w oparciu o opinie, inne przypadki :)
Federico Ponzi

10

Dla mnie to bardzo proste:

     private boolean mLoading = false;

     mList.setOnScrollListener(new RecyclerView.OnScrollListener() {

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);

            int totalItem = mLinearLayoutManager.getItemCount();
            int lastVisibleItem = mLinearLayoutManager.findLastVisibleItemPosition();

            if (!mLoading && lastVisibleItem == totalItem - 1) {
                mLoading = true;
                // Scrolled to bottom. Do something here.
                mLoading = false;
            }
        }
    });

Uważaj na zadania asynchroniczne: ładowanie mL musi zostać zmienione na końcu zadań asynchronicznych. Mam nadzieję, że to będzie pomocne!


Zasadniczo znalazłem tę odpowiedź po wielu dniach zadawania sobie pytań. To najlepsze rozwiązanie, imo.
wsgeorge

proszę podać rozwiązanie dla widoku recyklera z ładowaniem Androida przez numer strony moremore
Harsha

getWebServiceData (); mStoresAdapterData.setOnLoadMoreListener (nowe StoresAdapter.OnLoadMoreListener () {@Override public void onLoadMore () {// dodaj null, więc adapter sprawdzi typ_wyświetlania i wyświetli pasek postępu na dole storesList.add (null); mStoresAdapterDser.sotifyInotizeItaizeize () - 1); ++ pageNumber; getWebServiceData ();}}); obaj dzwonią jedna po drugiej, w końcu dane drugiej strony są widoczne, proszę tylko o pomoc
Harsha

próbowałem wielu wdrożeń paginacji recyclinglerView, ale żadna z nich nie działała, z wyjątkiem tego, proszę pana, mój głos poparcia
Mohammed Elrashied

9

Większość odpowiedzi zakłada, że RecyclerViewużywa LinearLayoutManageralbo GridLayoutManager, albo nawet StaggeredGridLayoutManager, albo zakłada, że ​​przewijanie jest pionowe lub poziome, ale nikt nie opublikował całkowicie ogólnej odpowiedzi .

Korzystanie z ViewHolderadaptera wyraźnie nie jest dobrym rozwiązaniem. Adapter może mieć z niego więcej niż 1 RecyclerView. „Dostosowuje” ich zawartość. Powinien to być widok Recycler View (który jest jedną klasą, która odpowiada za to, co jest obecnie wyświetlane użytkownikowi, a nie adapter, który jest odpowiedzialny tylko za dostarczanie treści do RecyclerView), który musi powiadomić system, że potrzeba więcej elementów (aby Załaduj).

Oto moje rozwiązanie, wykorzystujące wyłącznie abstrakcyjne klasy RecyclerView (RecycerView.LayoutManager i RecycerView.Adapter):

/**
 * Listener to callback when the last item of the adpater is visible to the user.
 * It should then be the time to load more items.
 **/
public abstract class LastItemListener extends RecyclerView.OnScrollListener {

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
      super.onScrolled(recyclerView, dx, dy);

      // init
      RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
      RecyclerView.Adapter adapter = recyclerView.getAdapter();

      if (layoutManager.getChildCount() > 0) {
        // Calculations..
        int indexOfLastItemViewVisible = layoutManager.getChildCount() -1;
        View lastItemViewVisible = layoutManager.getChildAt(indexOfLastItemViewVisible);
        int adapterPosition = layoutManager.getPosition(lastItemViewVisible);
        boolean isLastItemVisible = (adapterPosition == adapter.getItemCount() -1);

        // check
        if (isLastItemVisible)
          onLastItemVisible(); // callback
     }
   }

   /**
    * Here you should load more items because user is seeing the last item of the list.
    * Advice: you should add a bollean value to the class
    * so that the method {@link #onLastItemVisible()} will be triggered only once
    * and not every time the user touch the screen ;)
    **/
   public abstract void onLastItemVisible();

}


// --- Exemple of use ---

myRecyclerView.setOnScrollListener(new LastItemListener() {
    public void onLastItemVisible() {
         // start to load more items here.
    }
}

1
Twoja odpowiedź jest prawidłowa i zadziałała prawie przez cały LayoutManagersjeden moment: przenieś kod obliczeniowy za pomocą innej metody scrollListener - onScrollStateChangedzamiast tego przenieś go onScrolled. I onScrollStateChangedtylko sprawdź:if(newState == RecyclerView.SCROLL_STATE_IDLE) { // calculation code }
Trancer

Dziękuję za radę, ale nie sądzę, że byłoby to lepsze rozwiązanie. Deweloper chce, aby jego detektor był uruchamiany jeszcze zanim widok recyklera przestanie się przewijać. Może więc zakończyć ładowanie nowych elementów danych (jeśli dane są przechowywane lokalnie), a nawet dodać uchwyt widoku paska postępu na końcu podglądu recyklingu, więc kiedy podgląd recyklingu zatrzyma się, aby przewinąć, może być na kółku -bar jako ostatni widoczny element.
Yairopro

6

Chociaż zaakceptowana odpowiedź działa idealnie, poniższe rozwiązanie wykorzystuje addOnScrollListener, ponieważ setOnScrollListener jest przestarzały i zmniejsza liczbę zmiennych oraz warunków.

final LinearLayoutManager layoutManager = new LinearLayoutManager(context);
feedsRecyclerView.setLayoutManager(layoutManager);

feedsRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        if (dy > 0) {   
            if ((layoutManager.getChildCount() + layoutManager.findFirstVisibleItemPosition()) >= layoutManager.getItemCount()) {
                Log.d("TAG", "End of list");
                //loadMore();
            }
        }
    }
});

1
Czasami moja metoda loadMore () nazywa się trzykrotnie w onScrolled (), Każdy pomysł, dlaczego nazywa się trzykrotnie i jak przestać wywoływać ją wiele razy.
wed

dy> 0 jest zawsze fałszem
Dushyant Suthar

@ved To dlatego, że przewijanie ma 3 stany: przewijanie, przewijanie, bezczynność. Wybierz odpowiedni.
TheRealChx101

5

Chociaż jest tak wiele odpowiedzi na to pytanie, chciałbym podzielić się naszym doświadczeniem w tworzeniu niekończącego się widoku listy. Niedawno wdrożyliśmy niestandardowy Carousel LayoutManager, który może pracować w cyklu, przewijając listę nieskończenie, a nawet do pewnego momentu. Oto szczegółowy opis na GitHub .

Proponuję rzucić okiem na ten artykuł z krótkimi, ale cennymi zaleceniami dotyczącymi tworzenia niestandardowych menedżerów układów: http://cases.azoft.com/create-custom-layoutmanager-android/


5

Dzięki mocy funkcji rozszerzających Kotlin kod może wyglądać o wiele bardziej elegancko. Umieść to gdziekolwiek chcesz (mam go w pliku ExtensionFunctions.kt):

/**
 * WARNING: This assumes the layout manager is a LinearLayoutManager
 */
fun RecyclerView.addOnScrolledToEnd(onScrolledToEnd: () -> Unit){

    this.addOnScrollListener(object: RecyclerView.OnScrollListener(){

        private val VISIBLE_THRESHOLD = 5

        private var loading = true
        private var previousTotal = 0

        override fun onScrollStateChanged(recyclerView: RecyclerView,
                                          newState: Int) {

            with(layoutManager as LinearLayoutManager){

                val visibleItemCount = childCount
                val totalItemCount = itemCount
                val firstVisibleItem = findFirstVisibleItemPosition()

                if (loading && totalItemCount > previousTotal){

                    loading = false
                    previousTotal = totalItemCount
                }

                if(!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)){

                    onScrolledToEnd()
                    loading = true
                }
            }
        }
    })
}

A następnie użyj tego w ten sposób:

youRecyclerView.addOnScrolledToEnd {
    //What you want to do once the end is reached
}

To rozwiązanie opiera się na odpowiedzi Kushala Sharmy. Jest to jednak nieco lepsze, ponieważ:

  1. Używa onScrollStateChangedzamiast onScroll. Jest to lepsze, ponieważ onScrolljest wywoływane za każdym razem, gdy w RecyclerView występuje jakikolwiek ruch, podczas gdy onScrollStateChangedjest wywoływane tylko wtedy, gdy stan RecyclerView jest zmieniany. Korzystanie onScrollStateChangedz niego oszczędza czas procesora, aw konsekwencji baterię.
  2. Ponieważ wykorzystuje to funkcje rozszerzeń, można tego użyć w dowolnym posiadanym RecyclerView. Kod klienta ma tylko 1 linię.

Kiedy zrobiłem Pull SwipeRefreshLayout's setOnRefreshListener the addOnScrolledToEnd is not working prywatną klasę wewnętrzną PullToRefresh: SwipeRefreshLayout.OnRefreshListener {przesłonięcie zabawy onRefresh () {pbViewMore !!. Visibility = View.GONE pageCount = 0 courseList.clear () // wywołanie asynctask tutaj srlNoMessageRefreshLayout !!. False isRefef ! .isRefreshing = false}}
Naveen Kumar M

5

Tak to robię, proste i krótkie:

    recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener()
    {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy)
        {
            if(!recyclerView.canScrollVertically(1) && dy != 0)
            {
                // Load more results here

            }
        }
    });

3

OK, zrobiłem to za pomocą metody onBindViewHolder RecyclerView.Adapter.

Adapter:

public interface OnViewHolderListener {
    void onRequestedLastItem();
}

@Override
public void onBindViewHolder(ViewHolder holder, int position) {

    ...

    if (position == getItemCount() - 1) onViewHolderListener.onRequestedLastItem();
}

Fragment (lub aktywność) :

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    contentView = inflater.inflate(R.layout.comments_list, container, false);
    recyclerView = (RecyclerView) mContentView.findViewById(R.id.my_recycler_view);
    adapter = new Adapter();
    recyclerView.setAdapter(adapter);

    ...

    adapter.setOnViewHolderListener(new Adapter.OnViewHolderListener() {
        @Override
        public void onRequestedLastItem() {
            //TODO fetch new data from webservice
        }
    });
    return contentView;
}

1
poleganie na onBind nie jest zbyt dobrym pomysłem. RecyclerView wykonuje widoki pamięci podręcznej + ma kilka menedżerów układu, które umożliwiają wstępne buforowanie rzeczy. Sugerowałbym ustawienie detektora przewijania, a gdy zatrzyma się detektor przewijania, pobierz ostatnią widoczną pozycję elementu z menedżera układu. Jeśli używasz domyślnych menedżerów układu, wszystkie mają metody findLastVisible **.
yigit

@yigit Jak daleko RecyclerView będzie próbował wstępnie buforować? Jeśli masz 500 przedmiotów, naprawdę wątpię, aby to buforowało tak daleko (w przeciwnym razie będzie to gigantyczna strata wydajności). Kiedy faktycznie zacznie buforować (nawet gdy mamy 480 elementów), oznacza to, że nadszedł czas, aby załadować kolejną partię.
Alex Orlov

1
Nie snotuje wstępnego buforowania rzeczy, chyba że płynne przewijanie do dalekiej pozycji. W takim przypadku LLM (domyślnie) układa dwie strony zamiast jednej, aby mógł znaleźć widok docelowy i zwolnić do niego. Robi buforowane widoki, które wychodzą poza granice. Domyślnie ten rozmiar pamięci podręcznej wynosi 2 i można go skonfigurować za pomocą setItemCacheSize.
yigit

1
@yigit po prostu zastanawiając się nad problemem Nie rozumiem, w jaki sposób buforowanie może stanowić problem; interesuje nas tylko pierwszy raz, kiedy „ostatnie” przedmioty zostaną powiązane! Brak połączenia z onBindViewHolder, gdy menedżer układu ponownie wykorzystuje buforowany widok, który był już związany, po prostu nie stanowi problemu, gdy jesteśmy zainteresowani stronicowaniem większej ilości danych na końcu listy. Poprawny?
Greg Ennis,

2
W mojej bibliotece FlexibleAdapter zdecydowałem się przyjąć nieskończony zwój z połączeniem onBindViewHolder(), działa jak urok. Implementacja jest niewielka w porównaniu do detektora przewijania i jest łatwiejsza w użyciu.
Davideas,

3

Moim sposobem na wykrycie zdarzenia ładowania nie jest wykrycie przewijania, ale sprawdzenie, czy ostatni widok został dołączony. Jeśli dołączono ostatni widok, uważam go za czas załadowania większej ilości treści.

class MyListener implements RecyclerView.OnChildAttachStateChangeListener {
    RecyclerView mRecyclerView;

    MyListener(RecyclerView view) {
        mRecyclerView = view;
    }

    @Override
    public void onChildViewAttachedToWindow(View view) {

    RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
    RecyclerView.LayoutManager mgr = mRecyclerView.getLayoutManager();
    int adapterPosition = mgr.getPosition(view);

    if (adapterPosition == adapter.getItemCount() - 1) {
        // last view was attached
        loadMoreContent();
    }

    @Override
    public void onChildViewDetachedFromWindow(View view) {}
}

2

Spróbowałbym rozszerzyć używaną LayoutManager(np. LinearLayoutManager) I przesłonić scrollVerticallyBy()metodę. Najpierw zadzwoniłbym superpierwszy, a następnie sprawdził zwracaną wartość całkowitą. Jeśli wartość jest równa, 0wówczas zostanie osiągnięta dolna lub górna granica. Następnie użyłbym findLastVisibleItemPosition()metody, aby dowiedzieć się, która granica została osiągnięta i w razie potrzeby załadować więcej danych. Po prostu pomysł.

Ponadto możesz nawet zwrócić swoją wartość z tej metody, umożliwiając przewinięcie i pokazując wskaźnik „ładowanie”.


2
 recyclerList.setOnScrollListener(new RecyclerView.OnScrollListener() 
            {
                @Override
                public void onScrolled(RecyclerView recyclerView, int dx,int dy)
                {
                    super.onScrolled(recyclerView, dx, dy); 
                }

                @Override
                public void onScrollStateChanged(RecyclerView recyclerView,int newState) 
                {
                    int totalItemCount = layoutManager.getItemCount();
                    int lastVisibleItem = layoutManager.findLastVisibleItemPosition();

                    if (totalItemCount> 1) 
                    {
                        if (lastVisibleItem >= totalItemCount - 1) 
                        {
                            // End has been reached
                            // do something 
                        }
                    }          
                }
            });  

1
onScrollStateChanged()nie będzie działać, ponieważ będzie wywoływany tylko po zmianie stanu przewijania, a nie podczas przewijania. Musisz użyć tej onScrolled()metody.
mradzinski

2

Osiągnąłem nieskończoną implementację typu przewijania za pomocą tej logiki w onBindViewHoldermetodzie mojej RecyclerView.Adapterklasy.

    if (position == mItems.size() - 1 && mCurrentPage <= mTotalPageCount) {
        if (mCurrentPage == mTotalPageCount) {
            mLoadImagesListener.noMorePages();
        } else {
            int newPage = mCurrentPage + 1;
            mLoadImagesListener.loadPage(newPage);
        }
    }

Za pomocą tego kodu, gdy RecyclerView dotrze do ostatniego elementu, zwiększa bieżącą stronę i wywołania zwrotne w interfejsie odpowiedzialnym za ładowanie większej ilości danych z interfejsu API i dodawanie nowych wyników do adaptera.

Mogę opublikować pełniejszy przykład, jeśli nie jest to jasne?


Wiem, że ten post jest naprawdę stary, ale jestem trochę nowicjuszem i nie wiem, skąd wziąć porównywane zmienne. Zakładam, że mCurrentPage i mTotalPageCount odnoszą się do twojego zestawu danych, a mItems jest zestawieniem elementów listy, ale jak znaleźć pozycję?
BooleanCheese

Możesz zobaczyć, jak wdrożyłem
speedynomads

2

Dla osób, które używają StaggeredGridLayoutManager tutaj jest moja implementacja, działa ona dla mnie.

 private class ScrollListener extends RecyclerView.OnScrollListener {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {

        firstVivisibleItems = mLayoutManager.findFirstVisibleItemPositions(firstVivisibleItems);

        if(!recyclerView.canScrollVertically(1) && firstVivisibleItems[0]!=0) {
            loadMoreImages();
        }

    }

    private boolean loadMoreImages(){
        Log.d("myTag", "LAST-------HERE------");
        return true;
    }
}

@SudheeshMohan Oto pełna klasa, jest mała) W moim projekcie zmieniam dynamicznie liczbę przęseł, i będzie
działać

recyclerView.canScrollVertically (1) zrobi to, ale wymaga API 14. :(
Sudheesh Mohan

2

Dla tego o nazwie paginate istnieje łatwa w użyciu biblioteka . Obsługuje zarówno ListView, jak i RecyclerView (LinearLayout, GridLayout i StaggeredGridLayout).

Oto link do projektu na Github


2
  1. Utwórz klasę abstrakcyjną i rozszerzy RecyclerView.OnScrollListener

    public abstract class EndlessRecyclerOnScrollListener extends RecyclerView.OnScrollListener {
    private int previousTotal = 0;
    private boolean loading = true;
    private int visibleThreshold;
    private int firstVisibleItem, visibleItemCount, totalItemCount;
    private RecyclerView.LayoutManager layoutManager;
    
    public EndlessRecyclerOnScrollListener(RecyclerView.LayoutManager layoutManager, int visibleThreshold) {
    this.layoutManager = layoutManager; this.visibleThreshold = visibleThreshold;
    }
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
    super.onScrolled(recyclerView, dx, dy);
    
    visibleItemCount = recyclerView.getChildCount();
    totalItemCount = layoutManager.getItemCount();
    firstVisibleItem = ((LinearLayoutManager)layoutManager).findFirstVisibleItemPosition();
    
    if (loading) {
        if (totalItemCount > previousTotal) {
            loading = false;
            previousTotal = totalItemCount;
        }
    }
    if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
        onLoadMore();
        loading = true;
    }
      }
    
    public abstract void onLoadMore();}
  2. w działaniu (lub fragmencie) dodaj addOnScrollListener do recyclinglerView

    LinearLayoutManager mLayoutManager = new LinearLayoutManager(this);
    recyclerView.setLayoutManager(mLayoutManager);
    recyclerView.addOnScrollListener(new EndlessRecyclerOnScrollListener(mLayoutManager, 3) {
        @Override
        public void onLoadMore() {
            //TODO
            ...
        }
    });

1

Mam dość szczegółowy przykład sposobu paginacji za pomocą RecyclerView. Na wysokim poziomie mam zestaw PAGE_SIZE, powiedzmy 30. Więc proszę o 30 elementów, a jeśli otrzymam 30, to poproszę o następną stronę. Jeśli otrzymam mniej niż 30 elementów, oflaguję zmienną, aby wskazać, że osiągnięto ostatnią stronę, a następnie przestaję prosić o więcej stron. Sprawdź to i daj mi znać, co myślisz.

https://medium.com/@etiennelawlor/pagination-with-recyclerview-1cb7e66a502b


1

Tutaj moje rozwiązanie wykorzystujące AsyncListUtil w Internecie mówi: Zauważ, że ta klasa używa jednego wątku do ładowania danych, więc nadaje się do ładowania danych z pamięci dodatkowej, takiej jak dysk, ale nie z sieci. ale używam odata do odczytu danych i działa dobrze. Brakuje w moich przykładowych jednostkach danych i metodach sieciowych. Podaję tylko przykładowy adapter.

public class AsyncPlatoAdapter extends RecyclerView.Adapter {

    private final AsyncPlatoListUtil mAsyncListUtil;
    private final MainActivity mActivity;
    private final RecyclerView mRecyclerView;
    private final String mFilter;
    private final String mOrderby;
    private final String mExpand;

    public AsyncPlatoAdapter(String filter, String orderby, String expand, RecyclerView recyclerView, MainActivity activity) {
        mFilter = filter;
        mOrderby = orderby;
        mExpand = expand;
        mRecyclerView = recyclerView;
        mActivity = activity;
        mAsyncListUtil = new AsyncPlatoListUtil();

    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext()).
                inflate(R.layout.plato_cardview, parent, false);

        // Create a ViewHolder to find and hold these view references, and
        // register OnClick with the view holder:
        return new PlatoViewHolderAsync(itemView, this);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        final Plato item = mAsyncListUtil.getItem(position);
        PlatoViewHolderAsync vh = (PlatoViewHolderAsync) holder;
        if (item != null) {
            Integer imagen_id = item.Imagen_Id.get();
            vh.getBinding().setVariable(BR.plato, item);
            vh.getBinding().executePendingBindings();
            vh.getImage().setVisibility(View.VISIBLE);
            vh.getProgress().setVisibility(View.GONE);
            String cacheName = null;
            String urlString = null;
            if (imagen_id != null) {
                cacheName = String.format("imagenes/imagen/%d", imagen_id);
                urlString = String.format("%s/menusapi/%s", MainActivity.ROOTPATH, cacheName);
            }
            ImageHelper.downloadBitmap(mActivity, vh.getImage(), vh.getProgress(), urlString, cacheName, position);
        } else {
            vh.getBinding().setVariable(BR.plato, item);
            vh.getBinding().executePendingBindings();
            //show progress while loading.
            vh.getImage().setVisibility(View.GONE);
            vh.getProgress().setVisibility(View.VISIBLE);
        }
    }

    @Override
    public int getItemCount() {
        return mAsyncListUtil.getItemCount();
    }

    public class AsyncPlatoListUtil extends AsyncListUtil<Plato> {
        /**
         * Creates an AsyncListUtil.
         */
        public AsyncPlatoListUtil() {
            super(Plato.class, //my data class
                    10, //page size
                    new DataCallback<Plato>() {
                        @Override
                        public int refreshData() {
                            //get count calling ../$count ... odata endpoint
                            return countPlatos(mFilter, mOrderby, mExpand, mActivity);
                        }

                        @Override
                        public void fillData(Plato[] data, int startPosition, int itemCount) {
                            //get items from odata endpoint using $skip and $top
                            Platos p = loadPlatos(mFilter, mOrderby, mExpand, startPosition, itemCount, mActivity);
                            for (int i = 0; i < Math.min(itemCount, p.value.size()); i++) {
                                data[i] = p.value.get(i);
                            }

                        }
                    }, new ViewCallback() {
                        @Override
                        public void getItemRangeInto(int[] outRange) {
                            //i use LinearLayoutManager in the RecyclerView
                            LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
                            outRange[0] = layoutManager.findFirstVisibleItemPosition();
                            outRange[1] = layoutManager.findLastVisibleItemPosition();
                        }

                        @Override
                        public void onDataRefresh() {
                            mRecyclerView.getAdapter().notifyDataSetChanged();
                        }

                        @Override
                        public void onItemLoaded(int position) {
                            mRecyclerView.getAdapter().notifyItemChanged(position);
                        }
                    });
            mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
                @Override
                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                    onRangeChanged();
                }
            });
        }
    }
}

1

@kushal @abdulaziz

Dlaczego zamiast tego nie użyć tej logiki?

public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
    int totalItemCount, lastVisibleItemPosition;

    if (dy > 0) {
      totalItemCount = _layoutManager.getItemCount();
      lastVisibleItemPosition = _layoutManager.findLastVisibleItemPosition();

      if (!_isLastItem) {
        if ((totalItemCount - 1) == lastVisibleItemPosition) {
          LogUtil.e("end_of_list");

          _isLastItem = true;
        }
      }
    }
  }

Mój onScroller () wywołał trzykrotnie dla pojedynczego elementu, więc trzykrotnie otrzymałem end_of_list i moja logika stronicowania trzykrotnie. Jak to naprawić, aby wywołać paginację tylko raz.
wed

@ved To nie powinno się zdarzyć. Powyższy kod zapewnia, że end_of_listwarunek zostanie wywołany tylko raz. Flaga _isLastItemjest zmienną globalną w działaniu, która ma domyślną wartość false. To, co zrobiłem, to wykonanie zadania w tle po wykryciu końca listy, pobraniu następnej strony, a następnie powiadomieniu adaptera o nowym zestawie danych i wreszcie ustawieniu wartości _isLastItemfalse ponownie.
leonapse

1

Spróbuj poniżej:

import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.LayoutManager;

/**
 * Abstract Endless ScrollListener
 * 
 */
public abstract class EndlessScrollListener extends
        RecyclerView.OnScrollListener {
    // The minimum amount of items to have below your current scroll position
    // before loading more.
    private int visibleThreshold = 10;
    // The current offset index of data you have loaded
    private int currentPage = 1;
    // True if we are still waiting for the last set of data to load.
    private boolean loading = true;
    // The total number of items in the data set after the last load
    private int previousTotal = 0;
    private int firstVisibleItem;
    private int visibleItemCount;
    private int totalItemCount;
    private LayoutManager layoutManager;

    public EndlessScrollListener(LayoutManager layoutManager) {
        validateLayoutManager(layoutManager);
        this.layoutManager = layoutManager;
    }

    public EndlessScrollListener(int visibleThreshold,
            LayoutManager layoutManager, int startPage) {
        validateLayoutManager(layoutManager);
        this.visibleThreshold = visibleThreshold;
        this.layoutManager = layoutManager;
        this.currentPage = startPage;
    }

    private void validateLayoutManager(LayoutManager layoutManager)
            throws IllegalArgumentException {
        if (null == layoutManager
                || !(layoutManager instanceof GridLayoutManager)
                || !(layoutManager instanceof LinearLayoutManager)) {
            throw new IllegalArgumentException(
                    "LayoutManager must be of type GridLayoutManager or LinearLayoutManager.");
        }
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        visibleItemCount = recyclerView.getChildCount();
        totalItemCount = layoutManager.getItemCount();
        if (layoutManager instanceof GridLayoutManager) {
            firstVisibleItem = ((GridLayoutManager) layoutManager)
                    .findFirstVisibleItemPosition();
        } else if (layoutManager instanceof LinearLayoutManager) {
            firstVisibleItem = ((LinearLayoutManager) layoutManager)
                    .findFirstVisibleItemPosition();
        }
        if (loading) {
            if (totalItemCount > previousTotal) {
                loading = false;
                previousTotal = totalItemCount;
            }
        }
        if (!loading
                && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
            // End has been reached do something
            currentPage++;
            onLoadMore(currentPage);
            loading = true;
        }
    }

    // Defines the process for actually loading more data based on page
    public abstract void onLoadMore(int page);

}

1

Utworzyłem LoadMoreRecyclerView przy użyciu Abdulaziz Noor Answer

LoadMoreRecyclerView

public class LoadMoreRecyclerView extends RecyclerView  {

    private boolean loading = true;
    int pastVisiblesItems, visibleItemCount, totalItemCount;
    //WrapperLinearLayout is for handling crash in RecyclerView
    private WrapperLinearLayout mLayoutManager;
    private Context mContext;
    private OnLoadMoreListener onLoadMoreListener;

    public LoadMoreRecyclerView(Context context) {
        super(context);
        mContext = context;
        init();
    }

    public LoadMoreRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        init();
    }

    public LoadMoreRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mContext = context;
        init();
    }

    private void init(){
        mLayoutManager = new WrapperLinearLayout(mContext,LinearLayoutManager.VERTICAL,false);
        this.setLayoutManager(mLayoutManager);
        this.setItemAnimator(new DefaultItemAnimator());
        this.setHasFixedSize(true);
    }

    @Override
    public void onScrolled(int dx, int dy) {
        super.onScrolled(dx, dy);

        if(dy > 0) //check for scroll down
        {
            visibleItemCount = mLayoutManager.getChildCount();
            totalItemCount = mLayoutManager.getItemCount();
            pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();

            if (loading)
            {
                if ( (visibleItemCount + pastVisiblesItems) >= totalItemCount)
                {
                    loading = false;
                    Log.v("...", "Call Load More !");
                    if(onLoadMoreListener != null){
                        onLoadMoreListener.onLoadMore();
                    }
                    //Do pagination.. i.e. fetch new data
                }
            }
        }
    }

    @Override
    public void onScrollStateChanged(int state) {
        super.onScrollStateChanged(state);
    }

    public void onLoadMoreCompleted(){
        loading = true;
    }

    public void setMoreLoading(boolean moreLoading){
        loading = moreLoading;
    }

    public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
        this.onLoadMoreListener = onLoadMoreListener;
    }
}

WrapperLinearLayout

public class WrapperLinearLayout extends LinearLayoutManager
{
    public WrapperLinearLayout(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        try {
            super.onLayoutChildren(recycler, state);
        } catch (IndexOutOfBoundsException e) {
            Log.e("probe", "meet a IOOBE in RecyclerView");
        }
    }
}

// Dodaj to w formacie XML

<your.package.LoadMoreRecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
</your.package.LoadMoreRecyclerView>

OnCreate lub onViewCreated

mLoadMoreRecyclerView = (LoadMoreRecyclerView) view.findViewById(R.id.recycler_view);
mLoadMoreRecyclerView.setOnLoadMoreListener(new OnLoadMoreListener() {
            @Override
            public void onLoadMore() {
                callYourService(StartIndex);
            }
        });

callYourService

private void callYourService(){
    //callyour Service and get response in any List

    List<AnyModelClass> newDataFromServer = getDataFromServerService();
    //Enable Load More
    mLoadMoreRecyclerView.onLoadMoreCompleted();

    if(newDataFromServer != null && newDataFromServer.size() > 0){
            StartIndex += newDataFromServer.size();

            if (newDataFromServer.size() < Integer.valueOf(MAX_ROWS)) {
                    //StopLoading..
                   mLoadMoreRecyclerView.setMoreLoading(false);
            }
    }
    else{
            mLoadMoreRecyclerView.setMoreLoading(false);
            mAdapter.notifyDataSetChanged();
    }
}

1

Możliwe jest również wdrożenie bez detektora przewijania, przy użyciu samej logiki samego modelu danych. Widok przewijania wymaga pobierania elementów według pozycji, a także maksymalnej liczby elementów. Model może mieć logikę tła, aby pobierać potrzebne elementy w porcjach, a nie jeden po drugim, i robić to w wątku tła, powiadamiając widok, gdy dane są gotowe.

Takie podejście pozwala mieć kolejkę pobierania, która woli ostatnio żądane (tak obecnie widoczne) elementy niż starsze (najprawdopodobniej już przewinięte) przesyłki, kontrolować liczbę równoległych wątków do użycia i tym podobne. Pełny kod źródłowy tego podejścia (aplikacja demonstracyjna i biblioteka wielokrotnego użytku) są dostępne tutaj .


0

Istnieje metoda public void setOnScrollListener (RecyclerView.OnScrollListener listener)w https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#setOnScrollListener%28android.support.v7.widget.RecyclerView.OnScrollListener%29 . Użyć tego

EDYTOWAĆ:

Zastąp onScrollStateChangedmetodę wewnątrz onScrollListeneri zrób to

            boolean loadMore = firstVisibleItem + visibleItemCount >= totalItemCount;

            //loading is used to see if its already loading, you have to manually manipulate this boolean variable
            if (loadMore && !loading) {
                 //end of list reached
            }

1
Proszę być bardziej precyzyjnym! Jak korzystać z RecyclerView.OnScrollListener, aby ustalić, czy użytkownik przewinął na koniec listy?
erdna

W RecyclerView.OnScrollListener nie ma onScroll ! Miksujesz AbsListView.OnScrollListener z RecyclerView.OnScrollListener.
erdna

tak, powinno byćonScrollStateChanged
Panther

onScrollStateChangednie będzie działać, ponieważ będzie wywoływany tylko po zmianie stanu przewijania, a nie podczas przewijania.
Pedro Oliveira

@PedroOliveira Czy można onScrollStateChangedustalić, czy użytkownik przewija do ostatniego elementu?
erdna

0

Sprawdź, czy wszystko zostało szczegółowo wyjaśnione: paginacja przy użyciu RecyclerView od A do Z

    mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView,
                                     int newState) {
        super.onScrollStateChanged(recyclerView, newState);
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        int visibleItemCount = mLayoutManager.getChildCount();
        int totalItemCount = mLayoutManager.getItemCount();
        int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition();

        if (!mIsLoading && !mIsLastPage) {
            if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount
                    && firstVisibleItemPosition >= 0) {
                loadMoreItems();
            }
        }
    }
})

loadMoreItems ():

private void loadMoreItems() {
    mAdapter.removeLoading();
    //load data here from the server

    // in case of success
    mAdapter.addData(data);
    // if there might be more data
    mAdapter.addLoading();
}

w MyAdapter:

private boolean mIsLoadingFooterAdded = false;

public void addLoading() {
    if (!mIsLoadingFooterAdded) {
        mIsLoadingFooterAdded = true;
        mLineItemList.add(new LineItem());
        notifyItemInserted(mLineItemList.size() - 1);
    }
}

public void removeLoading() {
    if (mIsLoadingFooterAdded) {
        mIsLoadingFooterAdded = false;
        int position = mLineItemList.size() - 1;
        LineItem item = mLineItemList.get(position);

        if (item != null) {
            mLineItemList.remove(position);
            notifyItemRemoved(position);
        }
    }
}

public void addData(List<YourDataClass> data) {

    for (int i = 0; i < data.size(); i++) {
        YourDataClass yourDataObject = data.get(i);
        mLineItemList.add(new LineItem(yourDataObject));
        notifyItemInserted(mLineItemList.size() - 1);
    }
}

Link może zawierać odpowiedź, ale spróbuj wyjaśnić niektóre znaczące części wyjaśnień w linku w swoim poście.
Ian

0

Żadna z tych odpowiedzi nie bierze pod uwagę, jeśli lista jest zbyt mała, czy nie.

Oto fragment kodu, którego używałem, który działa w RecycleViews w obu kierunkach.

@Override
    public boolean onTouchEvent(MotionEvent motionEvent) {

        if (recyclerViewListener == null) {
            return super.onTouchEvent(motionEvent);
        }

        /**
         * If the list is too small to scroll.
         */
        if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
            if (!canScrollVertically(1)) {
                recyclerViewListener.reachedBottom();
            } else if (!canScrollVertically(-1)) {
                recyclerViewListener.reachedTop();
            }
        }

        return super.onTouchEvent(motionEvent);
    }

    public void setListener(RecyclerViewListener recycleViewListener) {
        this.recyclerViewListener = recycleViewListener;
        addOnScrollListener(new OnScrollListener() {

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                if (recyclerViewListener == null) {
                    return;
                }

                recyclerViewListener.scrolling(dy);

                if (!canScrollVertically(1)) {
                    recyclerViewListener.reachedBottom();
                } else if (!canScrollVertically(-1)) {
                    recyclerViewListener.reachedTop();
                }
            }

        });
    }

0

Pozwalam ci moje zbliżenie. Działa dobrze dla mnie.

Mam nadzieję, że Ci to pomoże.

/**
 * Created by Daniel Pardo Ligorred on 03/03/2016.
 */
public abstract class BaseScrollListener extends RecyclerView.OnScrollListener {

    protected RecyclerView.LayoutManager layoutManager;

    public BaseScrollListener(RecyclerView.LayoutManager layoutManager) {

        this.layoutManager = layoutManager;

        this.init();
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {

        super.onScrolled(recyclerView, dx, dy);

        this.onScroll(recyclerView, this.getFirstVisibleItem(), this.layoutManager.getChildCount(), this.layoutManager.getItemCount(), dx, dy);
    }

    private int getFirstVisibleItem(){

        if(this.layoutManager instanceof LinearLayoutManager){

            return ((LinearLayoutManager) this.layoutManager).findFirstVisibleItemPosition();
        } else if (this.layoutManager instanceof StaggeredGridLayoutManager){

            int[] spanPositions = null; //Should be null -> StaggeredGridLayoutManager.findFirstVisibleItemPositions makes the work.

            try{

                return ((StaggeredGridLayoutManager) this.layoutManager).findFirstVisibleItemPositions(spanPositions)[0];
            }catch (Exception ex){

                // Do stuff...
            }
        }

        return 0;
    }

    public abstract void init();

    protected abstract void onScroll(RecyclerView recyclerView, int firstVisibleItem, int visibleItemCount, int totalItemCount, int dx, int dy);

}

0

To rozwiązanie działa dla mnie idealnie.

//Listener    

public abstract class InfiniteScrollListener     extendsRecyclerView.OnScrollListener {

public static String TAG = InfiniteScrollListener.class.getSimpleName();
int firstVisibleItem, visibleItemCount, totalItemCount;
private int previousTotal = 0;
private boolean loading = true;
private int visibleThreshold = 1;
private int current_page = 1;

private LinearLayoutManager mLinearLayoutManager;

public InfiniteScrollListener(LinearLayoutManager linearLayoutManager) {
    this.mLinearLayoutManager = linearLayoutManager;
}

@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
    super.onScrolled(recyclerView, dx, dy);

    visibleItemCount = recyclerView.getChildCount();
    totalItemCount = mLinearLayoutManager.getItemCount();
    firstVisibleItem = mLinearLayoutManager.findFirstVisibleItemPosition();

    if (loading) {
        if (totalItemCount > previousTotal) {
            loading = false;
            previousTotal = totalItemCount;
        }
    }
    if (!loading && (totalItemCount - visibleItemCount - firstVisibleItem <= visibleThreshold)) {

        current_page++;

        onLoadMore(current_page);

        loading = true;
    }
}

public void resetState() {
    loading = true;
    previousTotal = 0;
    current_page = 1;
}

public abstract void onLoadMore(int current_page);
}

//Implementation into fragment 
 private InfiniteScrollListener scrollListener;

scrollListener = new InfiniteScrollListener(manager) {
        @Override
        public void onLoadMore(int current_page) {
            //Load data
        }
    };
    rv.setLayoutManager(manager);
    rv.addOnScrollListener(scrollListener);
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.