Erkennen Sie, wann RecyclerView beim Scrollen die unterste Position erreicht


85

Ich habe diesen Code für eine 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);

Ich muss wissen, wann die RecyclerView beim Scrollen die unterste Position erreicht. Ist es möglich ? Wenn ja, wie?




1
recyclerView.canScrollVertically (int direction); Was sollte der Parameter sein, den wir hier übergeben müssen?
Adrian

@ Adrian, falls Sie noch an dieser Frage interessiert sind :)))) stackoverflow.com/a/48514857/6674369
Andriy Antonov

Antworten:


174

Es gibt auch einen einfachen Weg, dies zu tun

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();

        }
    }
});

Richtungs-Ganzzahlen: -1 für Auf, 1 für Ab, 0 geben immer false zurück.


16
onScrolled () verhindert, dass die Erkennung zweimal erfolgt.
Ian Wambai

Ich kann ScrollListnerEvent nicht zu My RecyclerView hinzufügen. Der Code in onScrollStateChanged wird nicht ausgeführt.
Sandeep Yohans

1
Wenn Sie nur möchten, dass dies einmal ausgelöst wird (zeigen Sie Toast einmal an), fügen Sie der if-Anweisung ein boolesches Flag (eine Klassenmitgliedsvariable) hinzu und setzen Sie es innen auf true, wenn dies wie if (!recyclerView.canScrollVertically(1) && !mHasReachedBottomOnce) { mHasReachedBottomOnce = true Toast.makeText(YourActivity.this, "Last", Toast.LENGTH_LONG).show();}
folgt lautet

@ Bastami82 Ich benutze den Trick, wie Sie vorschlagen, um das zweimalige Drucken von Toast zu verhindern, aber es funktioniert nicht
Vishwa Pratap

4
Hinzufügen newState == RecyclerView.SCROLL_STATE_IDLEin die ifAnweisung würde funktionieren.
Lee Chun Hoe

48

Verwenden Sie diesen Code, um wiederholte Anrufe zu vermeiden

    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");
                
            }
        }
    });

2
Ich habe versucht zu antworten, aber das ist einfach und perfekt für mich. danke
Vishwa Pratap

@ Venkatesh Willkommen.
Milind Chaudhary

Die einzige Antwort, die mir keine anderen Fehler gab
The Fluffy T Rex

@ TheFluffyTRex Danke
Milind Chaudhary

45

Implementieren Sie einfach einen addOnScrollListener () in Ihrer Recyclingansicht. Implementieren Sie dann im Scroll-Listener den folgenden Code.

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
            }
        }
    };

1
Können Sie uns bitte etwas über mIsLoading erklären? Wo stellst du es auf oder änderst den Wert?
MiguelHincapieC

mLoading ist nur eine boolesche Variable, die angibt, ob die Ansicht aktiviert ist oder nicht. Zum Beispiel; Wenn die App die Recycling-Ansicht ausfüllt, ist mLoading wahr und wird falsch, sobald die Liste ausgefüllt wurde.
Febi M Felix

ohh ok, tankt dich für die Antwort. Ich dachte, es hätte eine wichtigere Rolle xD.
MiguelHincapieC

3
Die Methode kann nicht lösen 'findFirstVisibleItemPosition'aufandroid.support.v7.widget.RecyclerView
Iman Marashi

2
@Iman Marashi, du musst besetzen : (recyclerView.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition(). Diese Arbeit.
Bitvale

15

Die Antwort ist in Kotlin, es wird in Java funktionieren. IntelliJ sollte es für Sie konvertieren, wenn Sie kopieren und einfügen.

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)
    }
})

Dies funktioniert auch, LinearLayoutManagerda dieselben Methoden wie oben verwendet werden. Nämlich findLastVisibleItemPosition()und getItemCount()( itemCountin Kotlin).


5
Guter Ansatz, aber ein Problem sind die Anweisungen in der if-Schleife, die mehrfach ausgeführt wird
Vishnu,

Hatten Sie die Lösung für die gleichen Probleme
Vishwa Pratap

1
@ Vishnu Eine einfache boolesche Variable kann das beheben.
Daka

7

Ich habe mit den obigen Antworten keine perfekte Lösung gefunden, weil sie sogar zweimal ausgelöst wurde 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")
        }

Aber die recyclerView.canScrollVertically(direction)Richtung ist nur eine ganze Zahl + vs - also kann sie 4 sein, wenn sie unten ist, oder -12, wenn sie oben ist.
Xenolion

5

Versuche dies

Ich habe oben Antworten verwendet, es läuft immer, wenn Sie am Ende der Recycler-Ansicht gehen,

Wenn Sie nur einmal prüfen möchten, ob es sich auf einem Boden befindet oder nicht? Beispiel: - Wenn ich die Liste mit 10 Elementen habe, wenn ich nach unten gehe, wird sie mir angezeigt. Wenn ich von oben nach unten scrolle, wird sie nicht erneut gedruckt. Wenn Sie weitere Listen hinzufügen und dorthin gehen, wird sie erneut angezeigt.

Hinweis: - Verwenden Sie diese Methode, wenn Sie beim Schlagen der API mit dem Versatz umgehen

  1. Erstellen Sie eine Klasse mit dem Namen 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. Verwenden Sie diese Klasse wie folgt

         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();
                }
            });

Es läuft perfekt an meinem Ende, bitte, wenn Sie irgendwelche Probleme haben


Das war sehr hilfreich :)
Devrath

4

Es gibt meine Implementierung, für die es sehr nützlich ist StaggeredGridLayout.

Verwendung :

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

rvMain.addOnScrollListener(scrollListener);

Listener-Implementierung:

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);
}  }

Warum sollte der Rückruf um 200 Millisekunden verzögert werden?
Mani

@Mani Ich erinnere mich nicht genau an diesen Moment, aber vielleicht habe ich es nur für einen reibungslosen Effekt getan ...
Aleksey Timoshchenko

3

Ich habe auch nach dieser Frage gesucht, aber keine Antwort gefunden, die mich zufriedenstellte. Deshalb erstelle ich eine eigene Realisierung von recyclerView.

andere Lösungen sind weniger präzise als meine. Beispiel: Wenn das letzte Element ziemlich groß ist (viel Text), erfolgt der Rückruf anderer Lösungen viel früher, als recyclerView wirklich den Boden erreicht hat.

Meine Lösung behebt dieses 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)
}

dann in Ihrer Aktivität / Fragment:

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

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

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

hoffe es wird jemand hepl ;-)


1
Welcher Teil der Lösung von anderen hat Sie nicht zufrieden gestellt? Sie sollten es zuerst erklären, bevor Sie Ihre Antwort vorschlagen
Zam Sunk

@ZamSunk weil andere Lösungen weniger präzise sind als meine. Wenn zum Beispiel das letzte Element ziemlich klein ist (viel Text), erfolgt der Rückruf anderer Lösungen viel früher, als recyclerView wirklich den Boden erreicht. Meine Lösung behebt dieses Problem.
Andriy Antonov

1

Nachdem ich mit den meisten anderen Antworten in diesem Thread nicht zufrieden war, fand ich etwas, das ich für besser halte und das hier nirgendwo zu finden ist.

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
        }
    }
});

Dies ist einfach und wird unter allen Bedingungen genau einmal getroffen, wenn Sie nach oben oder unten gescrollt haben.


0

Das ist meine Lösung:

    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
        }
    }
}

Woher hast du state diese Aufzählung?
Raditya Gumay

Status ist eine Aufzählung von mir, dies ist ein Flag zum Überprüfen der Status für diese Bildschirme (ich habe MVVM mit Datenbindung in meiner App verwendet)
Alex Zezekalo

Wie würden Sie einen Rückruf implementieren, wenn keine Internetverbindung besteht?
Aks4125

0

Wir können Interface verwenden, um die Position zu erhalten

Schnittstelle: Erstellen Sie eine Schnittstelle für den Listener

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

Aktivität:

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);}}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.