381

I would like to change ListView to RecyclerView. I want to use the onScroll of the OnScrollListener in RecyclerView to determine if a user scrolled to the end of the list.

How do I know if a user scrolls to the end of the list so that I can fetch new data from a REST service?

Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
erdna
  • 3,962
  • 3
  • 14
  • 9
  • Please check this link It will help you.. http://stackoverflow.com/questions/26293461/using-onscroll-in-android-listview/26293762#26293762 – samsad Oct 24 '14 at 07:23
  • 36
    **Please read the question carefully!** I know how to do this with a ListView but NOT how to implement it with a [RecycleView](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html). – erdna Oct 24 '14 at 07:43
  • how to implement loadmore onscrolllistener in android.data is loaded but updated on existing data please help me – Harsha May 19 '15 at 05:09
  • 2
    You can use this library: https://github.com/rockerhieu/rv-adapter-endless/. It is based on the idea of `cwac-endless` for `ListView`. – Hieu Rocker Aug 04 '15 at 05:40
  • https://github.com/MarkoMilos/Paginate – Shubham Apr 28 '16 at 11:58
  • am not using any libreary.just coordinatorlayout with in nested scrollview on it recycelrview please help me – Harsha Jul 26 '16 at 08:59
  • private final RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(final RecyclerView recyclerView, final int newState) { // code Log.i("onScrollStateChanged called","called"); currentScrollState = newState; // if (currentVisibleItemCount >0) { Log.i("onScrollStateChanged called 1","called"); if (!isLoading) { – Harsha Jul 26 '16 at 09:00
  • plse help me:https://gist.github.com/anonymous/efe45ab281ce9bd66a6990960f45708f – Harsha Aug 23 '16 at 06:57

36 Answers36

473

Thanks to @Kushal and this is how I implemented it

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

                    loading = true;
                }
            }
        }
    }
});

Don't forget to add

LinearLayoutManager mLayoutManager;
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
miguel
  • 16,205
  • 4
  • 53
  • 64
Abdulaziz Noor
  • 6,413
  • 2
  • 19
  • 21
  • 1
    This `OnScrollListener` works well, but how do you handle adding and removing a "load more" view in the `RecyclerView`? I don't have any custom ItemAnimator set on the `RecyclerView`, but when I add and remove a "load more" view, the other items in a StaggeredGrid fly into or out of position in a strange manner. – Etienne Lawlor Feb 06 '15 at 07:02
  • 13
    What can i do for `StaggeredGridLayoutManager` – Pratik Butani Apr 08 '15 at 10:27
  • 20
    What about `mLayoutManager.findLastCompletelyVisibleItemPosition()==mLayoutManager.getItemCount()-1` – laaptu Apr 16 '15 at 17:43
  • what about for normal gridview – Harsha May 20 '15 at 06:54
  • 1
    @PratikButani if you use `LinearLayoutManager.findFirstVisibleItem()` instead of its `RecyclerView.LayoutManager` equivalent it should be good – kip2 Jul 13 '15 at 10:07
  • 2
    setOnScrollListener is deprecated --> use addOnScrollListener and removeOnSrollListener instead – luckyhandler Jul 20 '15 at 09:53
  • 4
    How to do this for StaggeredGridLayoutManager? – Sudheesh Mohan Jul 23 '15 at 11:27
  • Thanks.. For this code..I was also not getting findFirstVisibleItemPosition(), i changed to Recyclerview's to LinearLayoutManager ..It Worked.... – John Jul 28 '15 at 11:55
  • 1
    Keep in mind that it's probably a good idea to start loading in new values before (visibleItemCount + pastVisiblesItems) >= totalItemCount, because this is the bottom of the list. If you wait until this check to add new values, your users will notice they'll hit a "wall", and then, after new values are loaded, they can continue scrolling. – w3bshark Aug 01 '15 at 19:21
  • 2
    If you can't access mentioned methods, please think about up-casting in ```onScrolled()```. ```LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();``` – drindt Aug 02 '15 at 07:34
  • 2
    `setOnScrollListener` is deprecated so now use `addOnScrollListener` – Pratik Butani Aug 14 '15 at 09:21
  • @laaptu `findLastCompletelyVisibleItemPosition` returns -1 when recyclerView items are bigger than screen, so i think better to use `findLastVisibleItemPosition` – ashakirov Aug 23 '15 at 17:23
  • 2
    For future viewers: Kushal Sharma's answer is more precise and easier to use. – Fatima Nov 21 '15 at 15:44
  • 30
    where is the condition for making `loading = true` again? – Bhargav Dec 11 '15 at 12:57
  • 51
    To all who `findFirstVisibleItemPosition()` not resolved you should change `RecyclerView.LayoutManager` to `LinearLayoutManager ` – Amr Mohammed Jun 18 '15 at 14:17
  • A better approach includes the scroll velocity. If the user scrolls through large lists quickly, you'll hit the bottom of the list quickly and in many cases that is too short of time to retrieve more data from a source like a server and format it to be included in the list. The user will see this delay. – Johann Feb 14 '16 at 17:37
  • Is there an interface for RecyclerView's Scroll – hkaraoglu Jun 02 '16 at 08:42
  • 1
    Just want to know... I saw many people using `visibleItemCount + pastVisiblesItems >= totalItemCount `, isn't `findLastVisibleItemPosition() ` more straight forward? `if (!loading && (mLinearLayoutManager.findLastVisibleItemPosition() == totalItemCount - 1))`, is there any wrong using this ? – Beeing Jk Jun 29 '16 at 06:51
  • 15
    Don't forget to add `loading = true` after `//Do pagination` part. otherwise this code will run only once and you can't load more item. – Shaishav Jogani Jul 20 '16 at 10:12
  • 3
    for `StaggeredGridLayout` , set `pastVisiblesItems = mLayoutManager.findFirstVisibleItemPositions(null)[0]` . where mLayoutManager is an instance of StaggeredGridLayoutManager . From [paginate](https://github.com/MarkoMilos/Paginate) – nonybrighto Jul 21 '16 at 01:52
  • How can I know if I on the first Item ? – AsfK Aug 10 '16 at 11:23
  • 2
    wouldn't it make more sense to set loading to true when loading data (false when finished) and check if(!loading) before loading? – hmac Sep 22 '16 at 11:12
  • the variables firstVisibleItem , visibleItemCount, totalItemCount are declared outside the inner class and i get an error like you cannot use these in the scroll listener unless they are final ?? – Ankit Goel Jun 06 '17 at 17:36
  • For those who are using StaggeredGridLayoutManager, please check this link it will be helpfull : https://stackoverflow.com/questions/29463560/findfirstvisibleitempositions-doesnt-work-for-recycleview-android/45768855#45768855 – varotariya vajsi Aug 19 '17 at 07:20
  • Hello there,its worked perfectly but while scrolling some data is changing.what i have to do – Dhanu K Nov 22 '17 at 14:53
  • https://github.com/Suleiman19/Android-Pagination-with-RecyclerView/tree/a1dd15095bdfc48fd41171061d1226963e07c0f8 – Ashwin H Jul 05 '18 at 12:32
  • This one only load more data one time, after the first load it won't load more data even if you reaches to the bottom. –  Sep 04 '18 at 04:44
  • The problem with this solution is once you scroll all the way to bottom to fetch data, and you slightly scroll it up and downwards it makes the query multiple times – DIRTY DAVE Feb 24 '21 at 07:05
  • Sometimes it works properly, after a few more times it does not work properly – Masoud Siahkali Nov 20 '21 at 08:20
  • `findFirstVisibleItemPosition()` doesn't exist for me on `LinearLayoutManager` – JCutting8 Jun 23 '22 at 21:36
177

Make these variables.

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

Set on Scroll for recycler view.

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

Note : Make sure you are using LinearLayoutManager as layout manager for RecyclerView.

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

and for a grid

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

Have fun with your endless scrolls !! ^.^

Update : mRecyclerView.setOnScrollListener() is deprecated just replace with mRecyclerView.addOnScrollListener() and the warning will be gone! You can read more from this SO question.

Since Android now officially support Kotlin, here is an update for the same -

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

and add it to your RecyclerView like this

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

For a full code example, feel free to refer this Github repo.

Kushal Sharma
  • 5,978
  • 5
  • 25
  • 41
  • 15
    Any idea of how to apply this solution with a GridLayoutManager? Since the `findFirstVisibleItemPosition()` method is only available with the LinearLayoutManager, I can't figure out how to make it work. – MathieuMaree Nov 16 '14 at 19:47
  • 1
    Additional information: in this case, using `visibleItemCount = mLayoutManager.getChildCount();` behaves the same as using `visibleItemCount = mRecyclerView.getChildCount();`. – Jing Li Nov 19 '14 at 09:10
  • 3
    Place the code in onScrollStateChanged() method instead of onScrolled() method. – Anirudh Nov 26 '14 at 06:26
  • What about with a `StaggeredGridLayoutManager` since the `findFirstVisibleItemPosition()` method is not available? – Etienne Lawlor Jan 09 '15 at 14:32
  • 2
    @toobsco42 You can use this : `int[] firstVisibleItemPositions = new int[yourNumberOfColumns]; pastVisiblesItems = layoutManager.findFirstVisibleItemPositions(firstVisibleItemPositions)[0];` – MathieuMaree Feb 27 '15 at 13:55
  • add findLastVisibleItemPosition or findLastCompletelyVisibleItemPosition and this is the perfect anwer imo. No need for calculations! – endevour Apr 23 '15 at 12:43
  • 1
    What is this findFirstVisibleItemPosition() not resolved – Suresh Sharma Jun 16 '15 at 08:54
  • 1
    To all those who are getting "findFirstVisibleItemPosition() not resolved" please use LinearLayoutManager instead of just LinearLayout. Here is the documentation link - [LinearLayoutManager](https://developer.android.com/reference/android/support/v7/widget/LinearLayoutManager.html#findFirstVisibleItemPosition()) – Kaveesh Kanwal Jul 04 '15 at 07:59
  • 1
    This does not behave correctly when you try to refresh again and again with swipe refresh – Jemshit Aug 14 '15 at 13:42
  • 1
    I don't see a difference when changing the parameter `visibleThreshold`, can someone explain its meaning? – marktani Dec 07 '15 at 13:31
  • a little simpler approach `if ( !loading && (totalItemCount) <= ( lastVisibleItem + visibleThreshold )) { loadMore( totalItemCount/itemsPerPage + 1 ); loading = true; }` – anandbibek Dec 24 '15 at 08:01
  • Just want to know... Isn't `findLastVisibleItemPosition()` more straight forward? `if (!loading && (mLinearLayoutManager.findLastVisibleItemPosition() == totalItemCount - 1))`, is there any wrong using this ? – Beeing Jk Jun 29 '16 at 06:54
  • @BeeingJk the idea here is that I want to do a long operation in background (like making a network request for data of query DB for more data etc) as soon as the end of list is reached and thus lazy load stuff only when required. Since I already made a request for more data and IF the user scrolls back up and comes back down the logic without holding a state will trigger another request to get the data. So with `loading` variable we make sure we reach the end of list only once. If we get a error callback while getting the data, you can set the `loading` flag to `false` & hence trigger it again – Kushal Sharma Jun 29 '16 at 12:48
  • 1
    @KushalSharma thanks for your explaination. I'm just curious about the different of `if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem))` and 'if (!loading && (mLinearLayoutManager.findLastVisibleItemPosition() == totalItemCount - 1))' , because I saw many people are using the first one with "this minus that" instead of calling the existing `findLastVisibleItemPosition()` – Beeing Jk Jun 30 '16 at 03:39
  • 1
    What is the purpose of `visibleThreshold` here? – Shaishav Jogani Jul 20 '16 at 10:14
  • am also fallowing your answer,in my fragment am plan to show recyclerview data using pagenations.for that oncreateview am calling async task with page=1,and then recyclerview.addOnScroll increase the page count and once again call async task only first time data is replaced with second time and then not called the addonscroll please help me – Harsha Jul 22 '16 at 06:32
  • where i need to call recyclerview.addonscrolllistener after first loading data to recycelrview – Harsha Jul 22 '16 at 07:28
  • but scrollstatechanged called every time onscroll onli first time please give prorper solution.where i code onscroll or onscrollstatechanged, – Harsha Jul 26 '16 at 12:48
  • please am posting code:https://gist.github.com/anonymous/efe45ab281ce9bd66a6990960f45708f – Harsha Aug 23 '16 at 06:57
  • getWebServiceData(); 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 !"); getWebServiceData(); }} } }}); – Harsha Aug 23 '16 at 13:07
  • oneafter one calling asynctask and addonscroll also with out scroll recycelrview only called please – Harsha Aug 23 '16 at 13:08
  • @ShaishavJogani, the purpose of the visible threshold (I believe) is to begin querying the next set of items before the user hits the bottom of the RecyclerView. A good conceptual explanation can be found [here](https://guides.codepath.com/android/Endless-Scrolling-with-AdapterViews-and-RecyclerView) – coolDude Nov 15 '16 at 12:27
  • second time when I scroll down, it is not recognising..why so? –  Dec 22 '16 at 09:34
  • @KushalSharma This code loads more data immediately I open the app. What I need to assign to `previousTotal` and `visibleThreshold`? I don't get it. I would like to load more data when there is only 3 items below. –  Sep 04 '18 at 05:31
79

For those who only want to get notified when the last item is totally shown, you can use View.canScrollVertically().

Here is my implementation:

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

Note: You can use recyclerView.getLayoutManager().canScrollVertically() if you want to support API < 14.

Hai Zhang
  • 5,574
  • 1
  • 44
  • 51
  • This is a great and simple answer! Thank you! – Andranik May 31 '15 at 14:40
  • 2
    A similar solution can be used for pull to refresh by checking !recyclerView.canScrollVertically(-1). – LordParsley Jul 27 '15 at 12:54
  • Best solution out there. Thanks dude! – Angmar Sep 15 '15 at 15:45
  • 1
    @LordParsley Thank you, updated. This is also one of the way SwipeRefreshLayout checks for pull to refresh. – Hai Zhang Oct 05 '15 at 03:07
  • Cool. That's exactly what I want. – FUJI Goro Oct 11 '15 at 05:20
  • This is great, but it's API Level 14+. Hopefully you won't be as unlucky as I am. – EpicPandaForce Nov 26 '15 at 06:56
  • 1
    @EpicPandaForce If you are using `RecyclerView` you'll be lucky again, because you can simply make a static copy of `View.canScrollVertically()` since the related methods are marked public instead of protected in `RecyclerView`. You can also extend `RecyclerView` to re-implement `canScrollVertically()` if you like. A third and simple way is to call `recyclerView.getLayoutManager().canScrollVertically()`. Edited in my answer. – Hai Zhang Nov 26 '15 at 12:04
  • onScrolled is never called if the list is too small. – Oliver Dixon May 04 '16 at 09:50
  • This isn't that useful for endless/infinite scrolling since once onScrolledToBottom() has been called it's already too late for the last visible item to be modified before the user reaches the end of the list, as a result the user has to perform an additional scrolling action to reveal the progress indicator. Far from smooth. – Aspiring Dev Jul 12 '16 at 16:27
  • Thanks @Dreaming in Code Your answer helped me. One more thing i was using match_parent height of recyler view , thats why it was getting called again and again. – Deepak Rathore Nov 21 '18 at 16:38
74

Here is another approach. It will work with any layout manager.

  1. Make Adapter class abstract
  2. Then create an abstract method in adapter class (eg. load())
  3. In onBindViewHolder check the position if last and call load()
  4. Override the load() function while creating the adapter object in your activity or fragment.
  5. In the overided load function implement your loadmore call

For a detail understanding I wrote a blog post and example project get it here 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();
            }
        };
   }
}
Sabeer
  • 3,940
  • 1
  • 24
  • 20
  • 3
    Like the solution, you don't have to make your adapter abstract, instead you can create an interface in your adapter and make your activity implement it keeping it flexible and elegant, ex: ItemDisplayListener=>onItemDisplayed(int position). This way you can load at any position: at N, N-1, N-2.. – Samer Aug 27 '15 at 05:12
  • 1
    I like this solution !! No need to listen to all scroll events and to make potentially costly computations – Teovald Oct 29 '15 at 18:24
  • This will endlessy call load() if the last item is visible and no more items will be loaded by loadMore() (e. g., we reached the end of the *source* list as well). Do you have a solution for this situation? – marktani Dec 07 '15 at 13:26
  • 1
    @mcwise add a boolean finished=false; when you reach the end make finished=true.. change the current if condition to if ((position >= getItemCount() - 1) && (!finished)) – Sabeer Dec 07 '15 at 13:48
  • 1
    This should be marked as the correct answer. This is elegant and simple. – Greg Ennis Dec 22 '15 at 00:36
  • 4
    A good example of the superior solution buried under the popularity of the first solution. Very often, the last row will be bound to a separate viewtype which serves as a "Loading..." banner. When this viewtype gets a bind call, it will set up a callback which triggers a data change notification when more data is available. The accepted answer of hooking up the scroll listener is wasteful, and probably doesn't work for other layout types. – Michael Hays Feb 24 '16 at 00:59
  • I generally prefer this kind of solution, in part because it's possible that the first bunch of data won't fill the entire screen for whatever reason and you'd still want to keep loading data without making the user do a pointless scroll. – Artemiy Mar 13 '16 at 11:36
  • 3
    @mcwise it'd only endlessly call if the user actively scrolls away and back to the last item. onBindViewHolder is only called once each time the view enters the screen and would not constantly call it. – David Liu Mar 18 '16 at 22:55
  • 1
    I also like this solution, but it has one major problem. When you use some loading view as item of list, you can't just simply call notifyItemChanged. And yeah as @mcwise says, it could be infinite loading loop in wrong hands. But double mutex could solve it `if(!loading)if(!loading){loading = true: load()}` – l0v3 Mar 28 '16 at 13:22
  • @l0v3 I just posted the method to implement the load more functionality... to add loading view add recycleView.post(new Runnable() { public void run() { //loadMore(); } }); in the load() function.. which will fix the recycleview onscroll notifydatasetchange issue – Sabeer Mar 29 '16 at 04:58
  • 1
    simplicity is definitely here, but what about performance, is it approach more efficient than the others? – ericn Apr 11 '17 at 02:18
  • @SabeerMohammed you should create a github repository to display the full use of your code. It will be very helpful to all users – Raghav Satyadev Jan 11 '18 at 06:53
  • You should add a kotlin version – Jevon Jul 08 '20 at 21:24
  • if you need to call `notifydatasetchanged` to refresh your UI after loading more items, be sure to make your call in `recyclerView.post(new Runnable())` otherwise you get an error as you can't update the data while scrolling. This makes sure it runs after the UI update is complete. – JCutting8 Jun 23 '22 at 21:51
21

My answer is a modified version of Noor. I passed from a ListView where i had EndlessScrollListener (that you can find easily in many answers on SO) to a RecyclerView so i wanted a EndlessRecyclScrollListener to easily update my past listener.

So here is the code, hope it helps:

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

}
Vasily Kabunov
  • 6,511
  • 13
  • 49
  • 53
Federico Ponzi
  • 2,682
  • 4
  • 34
  • 60
  • 1
    Your implementation requires the RecyclerView to use LinearLeayoutManager. Bad Idea! – Orri Jul 04 '16 at 09:02
  • @Orri Could you please explain why? Thanks! – Federico Ponzi Jul 04 '16 at 09:06
  • 2
    In your onScrolled you cast the LayoutManager from the RecyclerView to LinearLayoutManager. Its not working for StaggredGridLayout or anthing else. There is not findFirstVisibleItemPosition() in StaggredGridLayout. – Orri Jul 04 '16 at 16:01
14

For me, it's very simple:

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

Be careful with asynchronous jobs: mLoading must be changed at the end of the asynchronous jobs. Hope it will be helpful!

Minh Nguyen
  • 149
  • 1
  • 5
  • I found this answer after days of questioning myself, basically. This is the best solution, imo. – wsgeorge Nov 26 '15 at 15:06
  • please give some solution for recyclerview with loadmore page number passing android – Harsha Jul 22 '16 at 08:05
  • getWebServiceData(); mStoresAdapterData.setOnLoadMoreListener(new StoresAdapter.OnLoadMoreListener() { @Override public void onLoadMore() { //add null , so the adapter will check view_type and show progress bar at bottom storesList.add(null); mStoresAdapterData.notifyItemInserted(storesList.size() - 1); ++pageNumber; getWebServiceData(); } }); both calling one after another finally 2nd page data only visibled please help – Harsha Aug 23 '16 at 11:52
  • i tried many implementation of the recyclerView pagination but none of them worked , except this one , you have my upvote sir – Mohammed Elrashied Feb 25 '17 at 17:24
11

With the power of Kotlin's extension functions, the code can look a lot more elegant. Put this anywhere you want (I have it inside an ExtensionFunctions.kt file):

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

And then use it like this:

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

This solution is based on Kushal Sharma's answer. However, this is a bit better because:

  1. It uses onScrollStateChanged instead of onScroll. This is better because onScroll is called every time there is any sort of movement in the RecyclerView, whereas onScrollStateChanged is only called when the state of the RecyclerView is changed. Using onScrollStateChanged will save you CPU time and, as a consequence, battery.
  2. Since this uses Extension Functions, this can be used in any RecyclerView you have. The client code is just 1 line.
  • 1
    When I did Pull `SwipeRefreshLayout's setOnRefreshListener the addOnScrolledToEnd is not working` private inner class PullToRefresh : SwipeRefreshLayout.OnRefreshListener { override fun onRefresh() { pbViewMore!!.visibility = View.GONE pageCount = 0 courseList.clear() //calling asynctask here srlNoMessageRefreshLayout!!.isRefreshing = false swipeRefreshLayout!!.isRefreshing = false } } – Naveen Kumar M Feb 20 '18 at 07:50
  • 1
    Hey Bitcoin Cash - ADA enthusiast, I've got a question. VISIBLE_THRESHOLD is the number of elements you want the recyclerList to show...right?...is it a limit? – Jose Ricardo Citerio Alcala Jul 05 '21 at 05:45
9

Most answer are assuming the RecyclerView uses a LinearLayoutManager, or GridLayoutManager, or even StaggeredGridLayoutManager, or assuming that the scrolling is vertical or horyzontal, but no one has posted a completly generic answer.

Using the ViewHolder's adapter is clearly not a good solution. An adapter might have more than 1 RecyclerView using it. It "adapts" their contents. It should be the RecyclerView (which is the one class which is responsible of what is currently displayed to the user, and not the adapter which is responsible only to provide content to the RecyclerView) which must notify your system that more items are needed (to load).

Here is my solution, using nothing else than the abstracted classes of the RecyclerView (RecycerView.LayoutManager and 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.
    }
}
Yairopro
  • 9,084
  • 6
  • 44
  • 51
  • 1
    Your answer is right and it's worked for all `LayoutManagers` but only one moment: move your calculation code in another scrollListener method - move it to `onScrollStateChanged` instead `onScrolled`. And in `onScrollStateChanged` just check: `if(newState == RecyclerView.SCROLL_STATE_IDLE) { // calculation code }` – Trancer Feb 16 '17 at 19:53
  • Thanks for your advice, but I don't think it would be a better solution. The developer wants its listener to be triggered even before the recyclerview stops to scroll. So it might finish to load new data items (if datas are stored localy), or even add a progress-bar view-holder at the end of the recyclerview, so when the recyclerview will stop to scroll, it can be on a circle progress-bar as last visible item. – Yairopro Feb 18 '17 at 20:03
8

This is how I do it, simple and short:

    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

            }
        }
    });
John T
  • 814
  • 10
  • 17
  • how does this prevent flooding of more results request to the server? This reads like it will execute everytime the scroll view is scrolled, but quite often you only want to load more items when you get close to the bottom of the list. – JCutting8 Jun 16 '22 at 20:16
  • 1
    @John-T One of the tremendous and working answer for my case. Thank you so much for this shortest answer. – M DEV Jul 17 '23 at 13:50
7

Although the accepted answer works perfectly, the solution below uses addOnScrollListener since setOnScrollListener is deprecated, and reduces number of variables, and if conditions.

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();
            }
        }
    }
});
capt.swag
  • 10,335
  • 2
  • 41
  • 41
6

Although there are so many answers to the question, I would like to share our experience of creating the endless list view. We have recently implemented custom Carousel LayoutManager that can work in the cycle by scrolling the list infinitely as well as up to a certain point. Here is a detailed description on GitHub.

I suggest you take a look at this article with short but valuable recommendations on creating custom LayoutManagers: http://cases.azoft.com/create-custom-layoutmanager-android/

5

OK, I did it by using the onBindViewHolder method of RecyclerView.Adapter.

Adapter:

public interface OnViewHolderListener {
    void onRequestedLastItem();
}

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

    ...

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

Fragment (or Activity):

@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;
}
erdna
  • 3,962
  • 3
  • 14
  • 9
  • 1
    relying on onBind is not a very good idea. RecyclerView does cache views + has some layout managers has means to pre-cache things. I would suggest setting a scroll listener and when scroll listener stops, get the last visible item position from layout manager. If you are using default layout managers, they all have findLastVisible** methods. – yigit Oct 24 '14 at 18:21
  • @yigit How far away will RecyclerView try to precache things? If you have 500 items, I really doubt it will cache so far away (otherwise it'll be just a giant waste of performance). When it'll actually start to cache (even when we at 480 element) that means it's time to load another batch anyway. – Alex Orlov Jan 27 '15 at 18:24
  • 1
    It doe snot preCache things unless it is smooth scrolling to a far away position. In that case, LLM (by default) layout out two pages instead of one so that it can find the target view and decelerate to it. It does cache views that go out of bounds. By default, this cache size is 2 and can be configured via setItemCacheSize. – yigit Jan 28 '15 at 06:42
  • 1
    @yigit just thinking through the problem I dont see how caching could present an issue here; we are only interested in the first time the "last" items are bound anyway! Missing the call to onBindViewHolder when the layout manager re-uses a cached view that was already bound is just not an issue when we are interesting in paging in more data at the end of the list. Correct? – Greg Ennis Dec 22 '15 at 00:36
  • 2
    In my FlexibleAdapter library I chose to adopt the endless scroll with the call in `onBindViewHolder()`, it works like a charm. The implementation is tiny compared to the scroll listener and it's easier to use. – Davideas Jul 25 '16 at 09:47
4
 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 
                        }
                    }          
                }
            });  
Anirudh
  • 2,767
  • 5
  • 69
  • 119
  • 1
    `onScrollStateChanged()` will not work since it will be only called when you change the state of the scroll and not while you're scrolling. You must use the `onScrolled()` method. – mradzinski Jan 20 '15 at 23:56
3

I would try to extend used LayoutManager (e.g. LinearLayoutManager) and override scrollVerticallyBy() method. Firstly, I would call super first and then check returned integer value. If the value equals to 0 then a bottom or a top border is reached. Then I would use findLastVisibleItemPosition() method to find out which border is reached and load more data if needed. Just an idea.

In addition, you can even return your value from that method allowing overscroll and showing "loading" indicator.

sergej shafarenka
  • 20,071
  • 7
  • 67
  • 86
3

I achieved an infinite scrolling type implementation using this logic in the onBindViewHolder method of my RecyclerView.Adapter class.

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

With this code when the RecyclerView gets to the last item, it increments the current page and callbacks on an interface which is responsible for loading more data from the api and adding the new results to the adapter.

I can post more complete example if this isn't clear?

Pratik Butani
  • 60,504
  • 58
  • 273
  • 437
speedynomads
  • 2,632
  • 1
  • 26
  • 24
  • I know this post is really old, but I'm somewhat of a newb and don't know what where to get the variables you're comparing. mCurrentPage and mTotalPageCount are refering to your data set I assume, and mItems is the arraylist of list items, but how do I find position? – BooleanCheese Jan 17 '15 at 22:37
  • You can see how I implemented my RecyclerView.Adapter here: https://github.com/dominicthomas/FlikrGridRecyclerView/blob/master/app/src/main/java/com/android/domji84/mcgridview/adapters/GridItemAdapter.java – speedynomads Jan 19 '15 at 10:40
3

For people who use StaggeredGridLayoutManager here is my implementation, it works for me.

 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;
    }
}
Dennis Zinkovski
  • 1,821
  • 3
  • 25
  • 42
  • @SudheeshMohan [Here](https://github.com/Dandewine/GoogleImageSearchAndroid/blob/master/app/src/main/java/com/dandewine/user/tocleveroad/fragments/ResultOfSearch.java) is full class, it's small) In my project I change dynamically span count, and It will works for 1 and 2 span counts – Dennis Zinkovski Jul 23 '15 at 12:16
  • recyclerView.canScrollVertically(1) will do it but it requires API 14. :( – Sudheesh Mohan Jul 24 '15 at 04:55
3

There is an easy to use library for this named paginate . Supports both ListView and RecyclerView ( LinearLayout , GridLayout and StaggeredGridLayout).

Here is the link to the project on Github

nonybrighto
  • 8,752
  • 5
  • 41
  • 55
3

My way to detect loading event is not to detect scrolling, but to listen whether the last view was attached. If the last view was attached, I regard it as timing to load more content.

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) {}
}
walkingice
  • 111
  • 1
  • 3
3
  1. Create an abstract class and extends 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. in activity (or fragment) add addOnScrollListener to recyclerView

    LinearLayoutManager mLayoutManager = new LinearLayoutManager(this);
    recyclerView.setLayoutManager(mLayoutManager);
    recyclerView.addOnScrollListener(new EndlessRecyclerOnScrollListener(mLayoutManager, 3) {
        @Override
        public void onLoadMore() {
            //TODO
            ...
        }
    });
    
roghayeh hosseini
  • 676
  • 1
  • 8
  • 21
3

As @John T suggest. Just use code block below, really short, beauty and simple :D

public void loadMoreOnRecyclerView() {
    mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(@NonNull @NotNull RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            if (!recyclerView.canScrollVertically(1) && dy != 0) {
                //Load more items here
            }
        }
    });
}

You can follow my Repo to understand the way that it work.

https://github.com/Nghien-Nghien/PokeAPI-Java/blob/0d8d69d348e068911b883f0ae7791d904cc75cb5/app/src/main/java/com/example/pokemonapi/MainActivity.java

Description info about app like this: https://github.com/skydoves/Pokedex#readme

Nghien Nghien
  • 189
  • 2
  • 6
2

I have a pretty detailed example of how to paginate with a RecyclerView. At a high level, I have a set PAGE_SIZE , lets say 30. So I request 30 items and if I get 30 back then I request the next page. If I get less than 30 items I flag a variable to indicate that the last page has been reached and then I stop requesting for more pages. Check it out and let me know what you think.

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

Etienne Lawlor
  • 6,817
  • 18
  • 77
  • 89
2

Here my solution using AsyncListUtil, in the web says: Note that this class uses a single thread to load the data, so it suitable to load data from secondary storage such as disk, but not from network. but i am using odata to read the data and work fine. I miss in my example data entities and network methods. I include only the example 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();
                }
            });
        }
    }
}
FRL
  • 31
  • 2
2
if (layoutManager.findLastCompletelyVisibleItemPosition() == 
 recyclerAdapter.getItemCount() - 1) {
    //load more items.
 }

Fair and simple. This will work.

GvSharma
  • 2,632
  • 1
  • 24
  • 30
1

There is a method public void setOnScrollListener (RecyclerView.OnScrollListener listener) in https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#setOnScrollListener%28android.support.v7.widget.RecyclerView.OnScrollListener%29. Use that

EDIT:

Override onScrollStateChanged method inside the onScrollListener and do this

            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
            }
Panther
  • 8,938
  • 3
  • 23
  • 34
  • 2
    **Please be more specific!** How do I use [RecyclerView.OnScrollListener](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.OnScrollListener.html) to determine that the user scrolled to the end of the list? – erdna Oct 24 '14 at 07:48
  • There is no onScroll at [RecyclerView.OnScrollListener](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.OnScrollListener.html)! You mix the AbsListView.OnScrollListener with the RecyclerView.OnScrollListener. – erdna Oct 24 '14 at 08:10
  • ya it is supposed to be `onScrollStateChanged` – Panther Oct 24 '14 at 08:17
  • `onScrollStateChanged` will not work since it will be only called when you change the state of the scroll and not while you're scrolling. – Pedro Oliveira Oct 24 '14 at 08:20
  • @PedroOliveira Is it possible with `onScrollStateChanged` to determine that the user scrolls to the last item? – erdna Oct 24 '14 at 08:49
  • @erdna AFAIK no. You need to implement your own `RecyclerView` to report `onScroll` events. – Pedro Oliveira Oct 24 '14 at 08:50
1

Check this every thing is explained in detail: Pagination using RecyclerView From A to 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();
}

in 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);
    }
}
awsleiman
  • 1,879
  • 20
  • 14
  • The link may contain an answer, but try to explain some substantial parts of the explanations in the link in your post. – Ian Feb 23 '16 at 23:52
1

I let you my aproximation. Works fine for me.

I hope it helps you.

/**
 * 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);

}
Dani
  • 4,001
  • 7
  • 36
  • 60
1

None of these answers take into account if the list is too small or not.

Here's a piece of code I've been using that works on RecycleViews in both directions.

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

        });
    }
Oliver Dixon
  • 7,012
  • 5
  • 61
  • 95
1

@kushal @abdulaziz

Why not use this logic instead?

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;
        }
      }
    }
  }
leonapse
  • 204
  • 1
  • 6
  • My onScroller() called thrice for an single item so I get end_of_list thrice and my pagination logic called thrice. How to correct this to call pagination only once. – Ved Aug 25 '16 at 06:34
  • @ved That's not supposed to happen. That code above ensures the `end_of_list` condition will only be called once. The flag `_isLastItem` is a global variable in the activity that has a default value of `false`. What I did is to execute a background task after detecting the end of list, retrieving the next page then notify the adapter of the new dataset, and finally setting `_isLastItem` to false again. – leonapse Sep 02 '16 at 08:15
1

Try below:

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

}
Shridutt Kothari
  • 7,326
  • 3
  • 41
  • 61
1

I have created LoadMoreRecyclerView using 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");
        }
    }
}

//Add it in xml like

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

OnCreate or 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();
    }
}
Zar E Ahmer
  • 33,936
  • 20
  • 234
  • 300
1

It is also possible to implement without the scroll listener, using the pure logic of the data model alone. The scroll view requires to get items by position as well as the maximal item count. The model can have the background logic to fetch the needed items in chunks, rather than one by one, and do this in the background thread, notifying the view when the data are ready.

This approach allows to have the fetching queue which prefers most recently requested (so currently visible) items over older (most likely already scrolled away) submissions, control the number of parallel threads to use and things the like. The complete source code for this approach (demo app and reusable library) are available here.

Audrius Meškauskas
  • 20,936
  • 12
  • 75
  • 93
1

This solution works perfectly for me.

//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);
Giedrius Šlikas
  • 1,073
  • 12
  • 12
0

@erdna Please refer my below code.May be it will become helpful to you.

     int firstVisibleItem, visibleItemCount, totalItemCount;
   recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {

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

            visibleItemCount = layoutManager.getChildCount();
            totalItemCount = layoutManager.getItemCount();
            firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
            Log.e("firstVisibleItem", firstVisibleItem + "");
            Log.e("visibleItemCount", visibleItemCount + "");
            Log.e("totalItemCount", totalItemCount + "");

            if (page != total_page_index) {
                if (loading) {
                    if ((visibleItemCount + firstVisibleItem) >= totalItemCount) {
                        Log.e("page", String.valueOf(page));
                        page=page+1;
                        new GetSummary().execute(String.valueOf(page), "");
                        loading = false;
                    }
                }
            }

        }


    });
komal akhani
  • 553
  • 6
  • 20
0

No repetition calls however you scroll. fetchData will be called only when you are at the end of the list and there is more data to be fetched

Regarding my code:
1.fetchData() fetches 10 items on each call.
2.isScrolling is a class member variable 3.previousTotalCount is also a class member. It stores previously visited last item

recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                super.onScrollStateChanged(recyclerView, newState)
                if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
                    isScrolling = true
                }
            }

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

                val visibleItemCount = layoutManager.childCount
                val totalItemCount = layoutManager.itemCount
                val scrolledOutItems = layoutManager.findFirstVisibleItemPosition()


                if (isScrolling && (visibleItemCount + scrolledOutItems == totalItemCount)
                    && (visibleItemCount + scrolledOutItems != previousTotalCount)) {
                    previousTotalCount = totalItemCount              
                    isScrolling = false
                    fetchData()  //fetches 10 new items from Firestore
                    pagingProgressBar.visibility = View.VISIBLE
                }
            }
        })
Syed Umair
  • 1,480
  • 1
  • 13
  • 14
-1

I just tried this:

    final LinearLayoutManager rvLayoutManager = new LinearLayoutManager(this);
    rvMovieList = (RecyclerView) findViewById(R.id.rvMovieList);
    rvMovieList.setLayoutManager(rvLayoutManager);
    adapter = new MovieRecyclerAdapter(this, movies);
    rvMovieList.setAdapter(adapter);
    adapter.notifyDataSetChanged();

    rvMovieList.addOnScrollListener(new OnScrollListener() {
        boolean loading = true;
        int pastVisiblesItems, visibleItemCount, totalItemCount;
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);

            visibleItemCount = rvLayoutManager.getChildCount();
            totalItemCount = rvLayoutManager.getItemCount();
            pastVisiblesItems = rvLayoutManager.findFirstVisibleItemPosition();

            int lastitem = visibleItemCount + pastVisiblesItems;

            if ( lastitem == totalItemCount
                    && page < total_pages
                    && !keyword.equals("") ) {
                // hit bottom ..

                String strnext;
                if (page == total_pages - 1) {
                    int last = total_results - (page * 20);
                    strnext = String.format(getResources().getString(R.string.next), last);
                } else {
                    strnext = String.format(getResources().getString(R.string.next), 20);
                }
                btNext.setText(strnext);
                btNext.setVisibility(View.VISIBLE);
            } else {
                btNext.setVisibility(View.INVISIBLE);
            }

            if(pastVisiblesItems == 0 &&  page > 1){
                // hit top
                btPrev.setVisibility(View.VISIBLE);
            } else {
                btPrev.setVisibility(View.INVISIBLE);
            }
        }
    });

I've got confused where to put loading with true or false, so I try to remove it, and this code works as I expected.

Sofyan Thayf
  • 1,322
  • 2
  • 14
  • 26
-1

Here is example for Simple Implementation of Endless Scrolling RecyclerView using a Simple Library compiled from the various sources.

Add this line in build.gradle

implementation 'com.hereshem.lib:awesomelib:2.0.1'

Create RecyclerView Layout in Activity with

<com.hereshem.lib.recycler.MyRecyclerView
        android:id="@+id/recycler"
        app:layoutManager="LinearLayoutManager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

Create a ViewHolder by passing the class it supports

public static class EVHolder extends MyViewHolder<Events> {
    TextView date, title, summary;
    public EVHolder(View v) {
        super(v);
        date = v.findViewById(R.id.date);
        title = v.findViewById(R.id.title);
        summary = v.findViewById(R.id.summary);
    }
    @Override
    public void bindView(Events c) {
        date.setText(c.date);
        title.setText(c.title);
        summary.setText(c.summary);
    }
}

Create Items List variable and adapters with very few lines by passing items, class and layout in the adapter

List<Events> items = new ArrayList<>();
MyRecyclerView recycler = findViewById(R.id.recycler);
RecyclerViewAdapter adapter = new RecyclerViewAdapter(this, items, EVHolder.class, R.layout.row_event);
recycler.setAdapter(adapter);

ClickListener and LoadMore Listener can be added with following lines

recycler.setOnItemClickListener(new MyRecyclerView.OnItemClickListener() {
    @Override
    public void onItemClick(int position) {
        Toast.makeText(MainActivity.this, "Recycler Item Clicked " + position, Toast.LENGTH_SHORT).show();
    }
});

recycler.setOnLoadMoreListener(new MyRecyclerView.OnLoadMoreListener() {
    @Override
    public void onLoadMore() {
        loadData();
    }
});
loadData();

After the data is loaded this must be called

recycler.loadComplete();

When no LoadMore is required LoadMore layout can be hidden by calling

recycler.hideLoadMore();

More example can be found here

Hope this helps :)

Hem Shrestha
  • 479
  • 7
  • 14