Wykryj, kiedy RecyclerView osiąga najniższe położenie podczas przewijania

106

Mam ten kod dla RecyclerView.

    recyclerView = (RecyclerView)rootview.findViewById(R.id.fabric_recyclerView);
    recyclerView.setLayoutManager(layoutManager);
    recyclerView.addItemDecoration(new RV_Item_Spacing(5));
    FabricAdapter fabricAdapter=new FabricAdapter(ViewAdsCollection.getFabricAdsDetailsAsArray());
    recyclerView.setAdapter(fabricAdapter);

Muszę wiedzieć, kiedy podczas przewijania RecyclerView osiąga najniższe położenie. Czy to możliwe ? Jeśli tak, w jaki sposób?

Adrian
źródło
1
recyklerView.canScrollVertically (kierunek int); Jaki powinien być taki parametr, który musimy tutaj przekazać?
Adrian
@Adrian, na wypadek gdybyś nadal był zainteresowany tym pytaniem :)))) stackoverflow.com/a/48514857/6674369
Andriy Antonov

Odpowiedzi:

186

jest też na to prosty sposób

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

        if (!recyclerView.canScrollVertically(1)) {
            Toast.makeText(YourActivity.this, "Last", Toast.LENGTH_LONG).show();

        }
    }
});

Kierunek liczby całkowite: -1 dla góry, 1 dla dołu, 0 zawsze zwróci fałsz.

Kaustubh Bhagwat
źródło
17
onScrolled () zapobiega dwukrotnemu wykryciu.
Ian Wambai
Nie mogę dodać ScrollListnerEvent do My RecyclerView. Kod w onScrollStateChanged nie jest wykonywany.
Sandeep Yohans
1
jeśli chcesz tylko to wywołać raz (pokaż Toast raz) dodaj flagę logiczną (zmienną składową klasy) do instrukcji if i ustaw ją na true wewnątrz, jeśli na przykład: if (!recyclerView.canScrollVertically(1) && !mHasReachedBottomOnce) { mHasReachedBottomOnce = true Toast.makeText(YourActivity.this, "Last", Toast.LENGTH_LONG).show();}
bastami82
@ bastami82 Używam sztuczki, jak sugerujesz, aby zapobiec dwukrotnemu wydrukowaniu tostów, ale to nie działa
Vishwa Pratap
4
Dodanie newState == RecyclerView.SCROLL_STATE_IDLEdo ifinstrukcji zadziała.
Lee Chun Hoe,
73

Użyj tego kodu, aby uniknąć powtarzających się połączeń

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

            if (!recyclerView.canScrollVertically(1) && newState==RecyclerView.SCROLL_STATE_IDLE) {
                Log.d("-----","end");
                
            }
        }
    });
Milind Chaudhary
źródło
2
Próbowałem odpowiedzieć, ale to jest dla mnie proste i idealne. dzięki
Vishwa Pratap
@Venkatesh Witamy.
Milind Chaudhary,
Jedyna odpowiedź, która nie dała mi żadnych innych błędów
The Fluffy T Rex
@TheFluffyTRex Thanks
Milind Chaudhary
Ostrożnie! Spowoduje to również wyzwolenie, gdy spróbujesz przesunąć palcem w górę, mając kilka elementów w recyklerze. Spowoduje to również DWUKROTNIE w związku z zaniedbaniem nowego Państwa.
Ammar Mohammad
48

Po prostu zaimplementuj addOnScrollListener () w swoim widoku recyklingu. Następnie wewnątrz odbiornika przewijania zaimplementuj poniższy kod.

RecyclerView.OnScrollListener mScrollListener = new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            if (mIsLoading)
                return;
            int visibleItemCount = mLayoutManager.getChildCount();
            int totalItemCount = mLayoutManager.getItemCount();
            int pastVisibleItems = mLayoutManager.findFirstVisibleItemPosition();
            if (pastVisibleItems + visibleItemCount >= totalItemCount) {
                //End of list
            }
        }
    };
Febi M Felix
źródło
1
Czy możesz wyjaśnić trochę o mIsLoading? gdzie go ustawiasz lub zmieniasz wartość.
MiguelHincapieC
mLoading to po prostu zmienna logiczna wskazująca, czy widok jest zajęty, czy nie. Na przykład; jeśli aplikacja wypełnia widok recyklingu, funkcja mLoading będzie miała wartość prawda i stanie się fałszywa, gdy lista zostanie wypełniona.
Febi M Felix
1
to rozwiązanie nie działa poprawnie. spójrz na stackoverflow.com/a/48514857/6674369
Andriy Antonov
3
Nie można rozwiązać metody w 'findFirstVisibleItemPosition'dniuandroid.support.v7.widget.RecyclerView
Iman Marashi
2
@Iman Marashi, trzeba obsadzie: (recyclerView.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition(). Ta praca.
bitvale
17

Odpowiedź jest w Kotlinie, będzie działać w Javie. IntelliJ powinien przekonwertować go za Ciebie, jeśli skopiujesz i wkleisz.

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

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

        // 3 lines below are not needed.
        Log.d("TAG","Last visible item is: ${gridLayoutManager.findLastVisibleItemPosition()}")
        Log.d("TAG","Item count is: ${gridLayoutManager.itemCount}")
        Log.d("TAG","end? : ${gridLayoutManager.findLastVisibleItemPosition() == gridLayoutManager.itemCount-1}")

        if(gridLayoutManager.findLastVisibleItemPosition() == gridLayoutManager.itemCount-1){
            // We have reached the end of the recycler view.
        }

        super.onScrolled(recyclerView, dx, dy)
    }
})

To również zadziała, LinearLayoutManagerponieważ ma te same metody, co powyżej. Mianowicie findLastVisibleItemPosition()i getItemCount()( itemCountw Kotlinie).

daka
źródło
7
Dobre podejście, ale jednym problemem jest to, że instrukcje w pętli if wykonają wielokrotność
Wisznu
czy masz rozwiązanie dla tych samych problemów
Vishwa Pratap
1
@Vishnu Prosta zmienna logiczna może to naprawić.
daka
11

Powyższe odpowiedzi nie dawały mi idealnego rozwiązania, ponieważ uruchamiały się dwukrotnie onScrolled

override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)
           if( !recyclerView.canScrollVertically(RecyclerView.FOCUS_DOWN))
               context?.toast("Scroll end reached")
        }
Manoj Perumarath
źródło
2
Ale recyclerView.canScrollVertically(direction)kierunek to dowolna liczba całkowita + vs - więc może wynosić 4, jeśli jest na dole, lub -12, jeśli jest na górze.
Xenolion
6

Spróbuj tego

Skorzystałem z powyższych odpowiedzi, działa zawsze, gdy pójdziesz na koniec widoku recyklera,

Jeśli chcesz sprawdzić tylko raz, czy jest na dole, czy nie? Przykład: - Jeśli mam listę 10 pozycji za każdym razem, gdy przejdę na dół, wyświetli się ona i ponownie, jeśli przewinę z góry na dół, nie będzie drukowana ponownie, a jeśli dodasz więcej list i przejdziesz tam, zostanie ponownie wyświetlona.

Uwaga: - Użyj tej metody, gdy masz do czynienia z przesunięciem w uderzaniu API

  1. Utwórz klasę o nazwie EndlessRecyclerViewScrollListener

        import android.support.v7.widget.GridLayoutManager;
        import android.support.v7.widget.LinearLayoutManager;
        import android.support.v7.widget.RecyclerView;
        import android.support.v7.widget.StaggeredGridLayoutManager;
    
        public abstract class EndlessRecyclerViewScrollListener extends RecyclerView.OnScrollListener {
            // The minimum amount of items to have below your current scroll position
            // before loading more.
            private int visibleThreshold = 5;
            // The current offset index of data you have loaded
            private int currentPage = 0;
            // The total number of items in the dataset after the last load
            private int previousTotalItemCount = 0;
            // True if we are still waiting for the last set of data to load.
            private boolean loading = true;
            // Sets the starting page index
            private int startingPageIndex = 0;
    
            RecyclerView.LayoutManager mLayoutManager;
    
            public EndlessRecyclerViewScrollListener(LinearLayoutManager layoutManager) {
                this.mLayoutManager = layoutManager;
            }
    
        //    public EndlessRecyclerViewScrollListener() {
        //        this.mLayoutManager = layoutManager;
        //        visibleThreshold = visibleThreshold * layoutManager.getSpanCount();
        //    }
    
            public EndlessRecyclerViewScrollListener(StaggeredGridLayoutManager layoutManager) {
                this.mLayoutManager = layoutManager;
                visibleThreshold = visibleThreshold * layoutManager.getSpanCount();
            }
    
            public int getLastVisibleItem(int[] lastVisibleItemPositions) {
                int maxSize = 0;
                for (int i = 0; i < lastVisibleItemPositions.length; i++) {
                    if (i == 0) {
                        maxSize = lastVisibleItemPositions[i];
                    }
                    else if (lastVisibleItemPositions[i] > maxSize) {
                        maxSize = lastVisibleItemPositions[i];
                    }
                }
                return maxSize;
            }
    
            // This happens many times a second during a scroll, so be wary of the code you place here.
            // We are given a few useful parameters to help us work out if we need to load some more data,
            // but first we check if we are waiting for the previous load to finish.
            @Override
            public void onScrolled(RecyclerView view, int dx, int dy) {
                int lastVisibleItemPosition = 0;
                int totalItemCount = mLayoutManager.getItemCount();
    
                if (mLayoutManager instanceof StaggeredGridLayoutManager) {
                    int[] lastVisibleItemPositions = ((StaggeredGridLayoutManager) mLayoutManager).findLastVisibleItemPositions(null);
                    // get maximum element within the list
                    lastVisibleItemPosition = getLastVisibleItem(lastVisibleItemPositions);
                } else if (mLayoutManager instanceof GridLayoutManager) {
                    lastVisibleItemPosition = ((GridLayoutManager) mLayoutManager).findLastVisibleItemPosition();
                } else if (mLayoutManager instanceof LinearLayoutManager) {
                    lastVisibleItemPosition = ((LinearLayoutManager) mLayoutManager).findLastVisibleItemPosition();
                }
    
                // 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;
                }
    
                // 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.
                // threshold should reflect how many total columns there are too
                if (!loading && (lastVisibleItemPosition + visibleThreshold) > totalItemCount) {
                    currentPage++;
                    onLoadMore(currentPage, totalItemCount, view);
                    loading = true;
                }
            }
    
            // Call this method whenever performing new searches
            public void resetState() {
                this.currentPage = this.startingPageIndex;
                this.previousTotalItemCount = 0;
                this.loading = true;
            }
    
            // Defines the process for actually loading more data based on page
            public abstract void onLoadMore(int page, int totalItemsCount, RecyclerView view);
    
        }
    
  2. użyj tej klasy w ten sposób

         LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());
            recyclerView.setLayoutManager(linearLayoutManager);
            recyclerView.addOnScrollListener(new EndlessRecyclerViewScrollListener( linearLayoutManager) {
                @Override
                public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
                    Toast.makeText(getActivity(),"LAst",Toast.LENGTH_LONG).show();
                }
            });
    

Z mojego punktu widzenia działa idealnie, pochwal mnie, jeśli masz jakiś problem

Sunil
źródło
To było bardzo pomocne :)
Devrath
4

Oto moja realizacja, która jest bardzo przydatna StaggeredGridLayout.

Stosowanie :

private EndlessScrollListener scrollListener =
        new EndlessScrollListener(new EndlessScrollListener.RefreshList() {
            @Override public void onRefresh(int pageNumber) {
                //end of the list
            }
        });

rvMain.addOnScrollListener(scrollListener);

Implementacja słuchacza:

class EndlessScrollListener extends RecyclerView.OnScrollListener {
private boolean isLoading;
private boolean hasMorePages;
private int pageNumber = 0;
private RefreshList refreshList;
private boolean isRefreshing;
private int pastVisibleItems;

EndlessScrollListener(RefreshList refreshList) {
    this.isLoading = false;
    this.hasMorePages = true;
    this.refreshList = refreshList;
}

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

    int visibleItemCount = manager.getChildCount();
    int totalItemCount = manager.getItemCount();
    int[] firstVisibleItems = manager.findFirstVisibleItemPositions(null);
    if (firstVisibleItems != null && firstVisibleItems.length > 0) {
        pastVisibleItems = firstVisibleItems[0];
    }

    if (visibleItemCount + pastVisibleItems >= totalItemCount && !isLoading) {
        isLoading = true;
        if (hasMorePages && !isRefreshing) {
            isRefreshing = true;
            new Handler().postDelayed(new Runnable() {
                @Override public void run() {
                    refreshList.onRefresh(pageNumber);
                }
            }, 200);
        }
    } else {
        isLoading = false;
    }
}

public void noMorePages() {
    this.hasMorePages = false;
}

void notifyMorePages() {
    isRefreshing = false;
    pageNumber = pageNumber + 1;
}

interface RefreshList {
    void onRefresh(int pageNumber);
}  }
Aleksey Timoshchenko
źródło
Po co dodawać 200 milisekund opóźnienia do oddzwaniania?
Mani
@Mani Nie pamiętam dokładnie w tej chwili, ale może zrobiłem to tylko dla płynnego efektu ...
Aleksey Timoshchenko
4

Po tym, jak nie byłem zadowolony z większości innych odpowiedzi w tym wątku, znalazłem coś, co moim zdaniem jest lepsze i nigdzie nie ma.

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        if (!recyclerView.canScrollVertically(1) && dy > 0)
        {
             //scrolled to bottom
        }else if (!recyclerView.canScrollVertically(-1) && dy < 0)
        {
            //scrolled to bottom
        }
    }
});

Jest to proste i nastąpi dokładnie raz w każdych warunkach, gdy przewiniesz w górę lub w dół.

noone392
źródło
3

Szukałem również tego pytania, ale nie znalazłem odpowiedzi, która by mnie satysfakcjonowała, dlatego tworzę własną realizację recyclinglerView.

inne rozwiązania są mniej precyzyjne niż moje. na przykład: jeśli ostatni element jest dość duży (dużo tekstu), to wywołanie zwrotne innych rozwiązań nastąpi znacznie wcześniej, niż recykllerView naprawdę osiągnął dno.

moje rozwiązanie rozwiązuje ten problem.

class CustomRecyclerView: RecyclerView{

    abstract class TopAndBottomListener{
        open fun onBottomNow(onBottomNow:Boolean){}
        open fun onTopNow(onTopNow:Boolean){}
    }


    constructor(c:Context):this(c, null)
    constructor(c:Context, attr:AttributeSet?):super(c, attr, 0)
    constructor(c:Context, attr:AttributeSet?, defStyle:Int):super(c, attr, defStyle)


    private var linearLayoutManager:LinearLayoutManager? = null
    private var topAndBottomListener:TopAndBottomListener? = null
    private var onBottomNow = false
    private var onTopNow = false
    private var onBottomTopScrollListener:RecyclerView.OnScrollListener? = null


    fun setTopAndBottomListener(l:TopAndBottomListener?){
        if (l != null){
            checkLayoutManager()

            onBottomTopScrollListener = createBottomAndTopScrollListener()
            addOnScrollListener(onBottomTopScrollListener)
            topAndBottomListener = l
        } else {
            removeOnScrollListener(onBottomTopScrollListener)
            topAndBottomListener = null
        }
    }

    private fun createBottomAndTopScrollListener() = object :RecyclerView.OnScrollListener(){
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            checkOnTop()
            checkOnBottom()
        }
    }

    private fun checkOnTop(){
        val firstVisible = linearLayoutManager!!.findFirstCompletelyVisibleItemPosition()
        if(firstVisible == 0 || firstVisible == -1 && !canScrollToTop()){
            if (!onTopNow) {
                onTopNow = true
                topAndBottomListener?.onTopNow(true)
            }
        } else if (onTopNow){
            onTopNow = false
            topAndBottomListener?.onTopNow(false)
        }
    }

    private fun checkOnBottom(){
        var lastVisible = linearLayoutManager!!.findLastCompletelyVisibleItemPosition()
        val size = linearLayoutManager!!.itemCount - 1
        if(lastVisible == size || lastVisible == -1 && !canScrollToBottom()){
            if (!onBottomNow){
                onBottomNow = true
                topAndBottomListener?.onBottomNow(true)
            }
        } else if(onBottomNow){
            onBottomNow = false
            topAndBottomListener?.onBottomNow(false)
        }
    }


    private fun checkLayoutManager(){
        if (layoutManager is LinearLayoutManager)
            linearLayoutManager = layoutManager as LinearLayoutManager
        else
            throw Exception("for using this listener, please set LinearLayoutManager")
    }

    private fun canScrollToTop():Boolean = canScrollVertically(-1)
    private fun canScrollToBottom():Boolean = canScrollVertically(1)
}

następnie w swoim działaniu / fragmencie:

override fun onCreate() {
    customRecyclerView.layoutManager = LinearLayoutManager(context)
}

override fun onResume() {
    super.onResume()
    customRecyclerView.setTopAndBottomListener(this)
}

override fun onStop() {
    super.onStop()
    customRecyclerView.setTopAndBottomListener(null)
}

mam nadzieję, że komuś pomoże ;-)

Andriy Antonov
źródło
1
która część rozwiązania od innych Cię nie zadowoliła? powinieneś to najpierw wyjaśnić, zanim zasugerujesz swoją odpowiedź
Zam Sunk
@ZamSunk cos inne rozwiązania są mniej precyzyjne niż moje. na przykład, jeśli ostatnia pozycja jest całkiem niezła (dużo tekstu), wywołanie zwrotne innych rozwiązań nastąpi znacznie wcześniej, niż recykllerView naprawdę osiągnie dno. moje rozwiązanie rozwiązuje ten problem.
Andriy Antonov
0

To jest moje rozwiązanie:

    val onScrollListener = object : RecyclerView.OnScrollListener() {

    override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
        directionDown = dy > 0
    }

    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        if (recyclerView.canScrollVertically(1).not()
                && state != State.UPDATING
                && newState == RecyclerView.SCROLL_STATE_IDLE
                && directionDown) {
               state = State.UPDATING
            // TODO do what you want  when you reach bottom, direction
            // is down and flag for non-duplicate execution
        }
    }
}
Alex Zezekalo
źródło
skąd masz state to enum?
raditya gumay
Stan jest moim wyliczeniem, to jest flaga do sprawdzania stanów dla tych ekranów (użyłem MVVM z wiązaniem danych w mojej aplikacji)
Alex Zezekalo
jak zaimplementowałbyś wywołanie zwrotne, jeśli nie ma połączenia z Internetem?
Aks4125,
0

Możemy użyć interfejsu do uzyskania pozycji

Interfejs: utwórz interfejs dla słuchacza

public interface OnTopReachListener { void onTopReached(int position);}

Czynność :

mediaRecycleAdapter = new MediaRecycleAdapter(Class.this, taskList); recycle.setAdapter(mediaRecycleAdapter); mediaRecycleAdapter.setOnSchrollPostionListener(new OnTopReachListener() {
@Override
public void onTopReached(int position) {
    Log.i("Position","onTopReached "+position);  
}
});

Adapter:

public void setOnSchrollPostionListener(OnTopReachListener topReachListener) {
    this.topReachListener = topReachListener;}@Override public void onBindViewHolder(MyViewHolder holder, int position) {if(position == 0) {
  topReachListener.onTopReached(position);}}
Amuthan S
źródło