Detect when RecyclerView reaches the bottom most position while scrolling
Asked Answered
T

23

153

I have this code for a 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);

I need to know when the RecyclerView reaches bottom most position while scrolling. Is it possible? If yes, how?

Thrill answered 21/3, 2016 at 9:53 Comment(4)
Try #27842240Extensile
#26543631Grillo
recyclerView.canScrollVertically(int direction); What should be the parameter like that we have to pass here ?Thrill
@Adrian, in case you still interested in this question :)))) https://mcmap.net/q/156372/-detect-when-recyclerview-reaches-the-bottom-most-position-while-scrollingLigneous
B
244

there is also a simple way to do it

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

        }
    }
});

direction integers: -1 for up, 1 for down, 0 will always return false.

Boric answered 21/9, 2017 at 11:15 Comment(8)
onScrolled() prevents the detection happening twice.Stalactite
I am unable to add ScrollListnerEvent to My RecyclerView. The code in onScrollStateChanged does not execute.Choriocarcinoma
if you want to only this to trigger once (show Toast once) add boolean flag ( a class member variable) to the if statement and set it to true inside if like: if (!recyclerView.canScrollVertically(1) && !mHasReachedBottomOnce) { mHasReachedBottomOnce = true Toast.makeText(YourActivity.this, "Last", Toast.LENGTH_LONG).show();}Poundal
@ bastami82 i use the trick as you suggest for preventing twice print of toast but it not workingLangill
This didn't work for me. It triggered a Toast for every view at createRolandrolanda
Add newState == RecyclerView.SCROLL_STATE_IDLE into the if statement would work.Threewheeler
For the Wear OS peeps, this does not work on WearableRecyclerView when rotary scroll is used. For touch scroll it works great.Fiat
For anyone confused about what the 1 is, it is the constant RecyclerView.VERTICALStringendo
V
112

Use this code for avoiding repeated calls

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");
            
        }
    }
});
Vyse answered 9/4, 2019 at 15:29 Comment(2)
Careful guys! This will also trigger when you try to swipe up while having few elements in the recycler. It will also trigger TWICE regarding that newState is neglected.Appenzell
Just add an additional check for if you can scroll up. If you can't scroll up, and can't scroll down, then just ignore the result (cause the list isn't long enough). recyclerView.canScrollVertically(-1) && !recyclerView.canScrollVertically(1)Novara
B
50

Just implement a addOnScrollListener() on your recyclerview. Then inside the scroll listener implement the code below.

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
            }
        }
    };
Bert answered 21/3, 2016 at 10:26 Comment(5)
Can you explain a little bit about mIsLoading please? where are u setting it up or changing value.Plerre
mLoading is just a boolean variable indicating whether the view is engaged or not. For eg; if app is populating the recyclerview, mLoading will be true and becomes false once the list got populated.Bert
this solution doesn't work properly. take a look at https://mcmap.net/q/156372/-detect-when-recyclerview-reaches-the-bottom-most-position-while-scrollingLigneous
Cannot resolve method 'findFirstVisibleItemPosition' on android.support.v7.widget.RecyclerViewMoffat
@Iman Marashi, you need to cast: (recyclerView.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition(). This work.Unlookedfor
T
29

After not being satisfied with most the other answers in this thread, I found something I think is better and is not anywhere on here.

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

This is simple and will hit exactly one time under all conditions when you have scrolled to the top or bottom.

Tibetoburman answered 21/7, 2020 at 7:15 Comment(0)
P
24

Answer is in Kotlin, it will work in Java. IntelliJ should convert it for you if you copy and paste.

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

This will also work for LinearLayoutManager because it has the same methods used above. Namely findLastVisibleItemPosition() and getItemCount() (itemCount in Kotlin).

Publishing answered 22/6, 2018 at 14:19 Comment(4)
Good approache, but one issue is the statements in if loop will execute multipltimesEadwina
had you get the solution for the same issuesLangill
@Eadwina A simple Boolean variable can fix that.Publishing
I think this is the best answer! Maybe I'd add a first check: if(dy <= 0) return. In this case, if the scroll direction is to the top, then do nothing.Reluctant
P
17

I was not getting a perfect solution by the above answers because it was triggering twice even on 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")
        }

Alternative solution which I had found some days ago,

  rv_repatriations.addOnScrollListener(object : RecyclerView.OnScrollListener() {
        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
            super.onScrollStateChanged(recyclerView, newState)
            if (!recyclerView.canScrollVertically(RecyclerView.FOCUS_DOWN) && recyclerView.scrollState == RecyclerView.SCROLL_STATE_IDLE
                && !isLoaded
            ) {
                isLoaded = true
               //do what you want here and after calling the function change the value of boolean
                Log.e("RepatriationFragment", "Scroll end reached")
            }
        }
    })

Using a boolean to ensure that it's not called multiple times when we hit the bottom.

Pizza answered 4/7, 2019 at 9:16 Comment(3)
But recyclerView.canScrollVertically(direction) direction is just any integer + vs - so it can 4 if it is at the bottom or -12 if it is at the top.Chrissie
This logic executes twice the first timeGalarza
@Galarza can you try the alternative solution also?Pizza
I
5

Try This

I have used above answers it runs always when you will go at the end of recycler view,

If you want to check only one time whether it is on a bottom or not? Example:- If I have the list of 10 items whenever I go on the bottom it will display me and again if I scroll top to bottom it will not print again, and if you add more lists and you go there it will again display.

Note:- Use this method when you deal with offset in hitting API

  1. Create a class named as 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. use this class like this

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

Its running perfect at my end, commnent me if you are getting any issue

Internment answered 18/5, 2018 at 8:14 Comment(0)
O
5

Kotlin Answer

You can use this Kotlin function for best practice of bottom scroll following to create infinite or endless scrolling.

// Scroll listener.
private fun setupListenerPostListScroll() {
    val scrollDirectionDown = 1 // Scroll down is +1, up is -1.
    var currentListSize = 0

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

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

                if (!recyclerView.canScrollVertically(scrollDirectionDown)
                    && newState == RecyclerView.SCROLL_STATE_IDLE
                ) {
                    val listSizeAfterLoading = recyclerView.layoutManager!!.itemCount

                    // List has more item.
                    if (currentListSize != listSizeAfterLoading) {
                        currentListSize = listSizeAfterLoading

                        // Get more posts.
                        postListScrollUpAction(listSizeAfterLoading)
                    }
                    else { // List comes limit.
                        showToastMessageShort("No more items.")
                    }
                }
            }
        })
}
Overdye answered 21/5, 2021 at 15:54 Comment(0)
B
4

There is my implementation, it is very useful for StaggeredGridLayout.

Usage :

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

rvMain.addOnScrollListener(scrollListener);

Listener implementation :

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);
}  }
Bethesda answered 13/2, 2017 at 17:6 Comment(2)
Why to add 200 milliseconds delay to callback?Tippet
@Tippet I don't remember exactly by this moment, but maybe I done it just for smooth effect...Bethesda
K
4

I've seen to many responses for this question and I stand that all of them don't give accurate behavior as an outcome. However if you follow this approach I'm positive you'll get the best behavior.

rvCategories is your RecyclerView

categoriesList is the list passed to your adapter

binding.rvCategories.addOnScrollListener(object : RecyclerView.OnScrollListener() {
  override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            val position = (recyclerView.layoutManager as LinearLayoutManager).findLastCompletelyVisibleItemPosition()
            if (position + 1 == categoriesList.size) {
               // END OF RECYCLERVIEW IS REACHED
            } else {
                // END OF RECYCLERVIEW IS NOT REACHED
            }
    }
})
Kilovoltampere answered 23/3, 2021 at 18:36 Comment(0)
L
3

I was also searching for this question but I didn't find the answer that satisfied me, so I create own realization of recyclerView.

other solutions is less precise then mine. for example: if the last item is pretty big (lot of text) then callback of other solutions will come much earlier then recyclerView realy reached bottom.

my sollution fix this issue.

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

then in your activity/fragment:

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

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

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

hope it will hepl someone ;-)

Ligneous answered 30/1, 2018 at 6:3 Comment(2)
which part of the solution from others that didn't satisfy you? you should explain it first before suggesting your answerPlantagenet
@ZamSunk cos other solutions is less precise then mine. for example if last item is pretty bit (alot of text) then callback of other solutions will come much earlier then recyclerView realy reach the bottom. my sollution fix this issue.Ligneous
R
3

Using Kotlin

   recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                if (!recyclerView.canScrollVertically(1)) {
                    Toast.makeText(context, "Last", Toast.LENGTH_LONG).show();
                }
            }
        })
Resultant answered 22/2, 2021 at 2:41 Comment(0)
S
2

The most simple way to do it is in the adapter like this:

@Override
public void onBindViewHolder(HistoryMessageListAdapter.ItemViewHolder holder, int position) {
            if (position == getItemCount()-1){
                listener.onLastItemReached();
            }
        }

Because as soon as the last item is recycled the listener is triggered.

Somali answered 2/9, 2021 at 15:33 Comment(0)
D
2

Most of the answers are poorly constructed and have some issues. One of the common issues is if the user scrolls fast, the end reached block executes multiple times I've found a solution, where the end block runs just 1 single time.

override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
    if (yourLayoutManager.findLastVisibleItemPosition() ==
          yourLayoutManager.itemCount - 1 && !recyclerView.canScrollVertically(1)) {
                Logger.log("End reached")
                // Do your operations
       }

       super.onScrolled(recyclerView, dx, dy)
 }

P.S. Sometimes if RecyclerView gets empty, the end listener might get called. As a solution, you can also add this check in the above code.

if (recyclerView.adapter?.itemCount!! > 0)
Dinnie answered 16/4, 2022 at 10:12 Comment(0)
T
1

You can use this, if you put 1 thats will be indicated when you stay in end of list, if you want now when you stay in the start of the list you change 1 for -1

recyclerChat.addOnScrollListener(object : RecyclerView.OnScrollListener() {
        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
            super.onScrollStateChanged(recyclerView, newState)
            if (!recyclerView.canScrollVertically(1)) {
               
            }
        }
    })
Transistor answered 21/5, 2021 at 15:39 Comment(0)
N
1

This is my solution after reading all answers in this post. I only want to show loading when the last item is shown at the end of the list and listview length is larger than screen height, meaning if there's only one or two items in the list, won't show the loading.

private var isLoadMoreLoading: Boolean = false

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

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

        if (!isLoadMoreLoading) {
            if (linearLayoutManager.findLastCompletelyVisibleItemPosition() == (list.size-1)) {
                if (recyclerView.canScrollVertically(-1)) {

                    adapter?.addLoadMoreView()
                    loadMore()
                }
            }
        }
    }
})

private fun loadMore() {
    isLoadMoreLoading = true
    //call loadMore api
}

Because of this linearLayoutManager.findLastCompletelyVisibleItemPosition() == (list.size-1), we can know last item is shown, but we also need to know listview can scroll or not.

Therefore I added recyclerView.canScrollVertically(-1). Once you hit the bottom of the list, it cannot scroll down anymore. -1 means list can scroll up. That means listview length is larger than screen height.

This answer is in kotlin.

Nerval answered 9/6, 2021 at 6:49 Comment(1)
This one triggers also if you scroll up, reaching the top up most itemRubbico
H
1
yourRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);

            // Get the current scroll position
            int currentScrollPosition = recyclerView.computeVerticalScrollOffset();

            // Get the total height of the RecyclerView
            int totalHeight = recyclerView.computeVerticalScrollRange();

            // Set a threshold, for example, 100 pixels from the bottom
            int threshold = totalHeight/2;

            // Check if the user is near the end of the RecyclerView
            if (currentScrollPosition + recyclerView.getHeight() + threshold >= totalHeight) {
                loadMorePosts();
            }
        }
    });

This is the code I'm using for my endless scrolling and works very well! You can adjust the threshold as you prefer in order to be more / less permissive with what you consider "bottom" of recyclerView

Hone answered 9/2 at 14:55 Comment(0)
S
0

This is my solution:

    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
        }
    }
}
Spilt answered 15/8, 2018 at 14:7 Comment(3)
where did you get state is that enum?Seminarian
State is a enum of mine, this is flag for checking states for that screens (I used MVVM with databinding at my app)Spilt
how would you implement callback if no internet connectivity?Latticed
S
0

We can use Interface for get the position

Interface : Create an Interface for listener

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

Activity :

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);}}
Soldier answered 2/2, 2019 at 11:26 Comment(0)
C
0

Use this method after declaring and initializing your recyclerView with adapter

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
              
            }

            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
               
                if(dy > 0){ // Scrolling Down
                 

//**Look at the condition inside if, this is how you can check, either you //have scrolled till last element of your recyclerView or not.**

//------------------------------------------------------------------------------

            LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();

                        if (linearLayoutManager != null && linearLayoutManager.findLastCompletelyVisibleItemPosition() == recyclerViewList.size() - 1) {
                            //bottom of list!
                        } 
//------------------------------------------------------------------------------                  
                    
                }else if(dy < 0){
                    // Scrolling Up
                   
                }

            }
        });
Constance answered 23/8, 2022 at 6:19 Comment(0)
J
0

After a long search, I found the prefect solution, that only scrolls to bottom when you want to, and also maintains the smooth scroll behavior you get by using a ListAdapter:

    adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
        override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
            super.onItemRangeInserted(positionStart, itemCount)

            scrollToBottomIfUserIsAtTheBottom(linearLayoutManager, positionStart)
        }
    })

    private fun scrollToBottomIfUserIsAtTheBottom(linearLayout: LinearLayoutManager, positionStart: Int) {
        val messageCount = adapter.itemCount
        val lastVisiblePosition = linearLayout.findLastCompletelyVisibleItemPosition()
        if (lastVisiblePosition == -1 ||
            (positionStart >= (messageCount - 1) &&
                lastVisiblePosition == (positionStart - 1))) 
        {
            recyclerView.scrollToPosition(positionStart)
        }
    }

A few notes:

  • If you use a list adapter and you add a new item to the list each update, you must create a new list each time for the smooth scroll to work.
  • Don't use smoothScrollToPosition with the ListAdapter! It ruins the ListAdapter "smoothier" behavior that happens when the diff works good and it detects that the change between old and new list is in a newly added item.
  • linearLayoutManager is adapter.layoutManager as LinearLayoutManager
Johannisberger answered 19/10, 2022 at 19:51 Comment(0)
Y
0

Try this,

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

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

            int visibleItemCount = recyclerView.getLayoutManager().getChildCount();
            int totalItemCount = recyclerView.getLayoutManager().getItemCount();
            int firstVisibleItem = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition();

            final int lastItem = firstVisibleItem + visibleItemCount;
            if(lastItem == totalItemCount) {
                if(previousLast != lastItem) {
                    previousLast = lastItem;
                    load();
                }
            }
        }
    });
Yolande answered 2/12, 2022 at 2:53 Comment(0)
C
0

Kotlin version of ScrollListener with the ability to set the indent from the last element to load

class MyScrollListener(
    private val indentForAction: Int = DEFAULT_INDENT_TO_INVOKE_ACTION,
    private val onEndReached: () -> Unit,
) : RecyclerView.OnScrollListener() {

    private var currentListSize = 0

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

        val linearLayoutManager = recyclerView.layoutManager as? LinearLayoutManager
            ?: error("ScrollListener works only with LinearLayoutManager")
        val lastVisiblePosition = linearLayoutManager.findLastVisibleItemPosition()
        val itemCount = linearLayoutManager.itemCount
        if (lastVisiblePosition > itemCount - indentForAction && itemCount > 0) {
            if (currentListSize != itemCount) {
                currentListSize = itemCount
                onEndReached.invoke()
            }
        }
    }

    private companion object {
        const val DEFAULT_INDENT_TO_INVOKE_ACTION = 8
    }
}
Cray answered 22/2, 2023 at 4:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.