Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Endless RecyclerView OnScrollListener
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
public abstract class EndlessRecyclerOnScrollListener extends RecyclerView.OnScrollListener {
public static String TAG = EndlessRecyclerOnScrollListener.class.getSimpleName();
private int previousTotal = 0; // The total number of items in the dataset after the last load
private boolean loading = true; // True if we are still waiting for the last set of data to load.
private int visibleThreshold = 5; // The minimum amount of items to have below your current scroll position before loading more.
int firstVisibleItem, visibleItemCount, totalItemCount;
private int current_page = 1;
private LinearLayoutManager mLinearLayoutManager;
public EndlessRecyclerOnScrollListener(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)) {
// End has been reached
// Do something
current_page++;
onLoadMore(current_page);
loading = true;
}
}
public abstract void onLoadMore(int current_page);
}
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
public class SampleActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sample);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setOnScrollListener(new EndlessRecyclerOnScrollListener(linearLayoutManager) {
@Override
public void onLoadMore(int current_page) {
// do something...
}
});
}
}
@dimiro1

This comment has been minimized.

Copy link

dimiro1 commented Dec 10, 2014

Thanks

@panagorn

This comment has been minimized.

Copy link

panagorn commented Jan 6, 2015

private int visibleThreshold = 5; means the number of items remain to the recycler before reaching the end?

@ssinss

This comment has been minimized.

Copy link
Owner Author

ssinss commented Jan 15, 2015

@panagorn I added comments. You can find details here.

@therealandroid

This comment has been minimized.

Copy link

therealandroid commented Feb 4, 2015

Nice, i was looking for something like this. Thank you.

@ruigoncalo

This comment has been minimized.

Copy link

ruigoncalo commented Feb 23, 2015

You should add a reset method:

public void reset(int previousTotal, boolean loading) {
  this.previousTotal = previousTotal;
  this.loading = loading;
}

And call it on onResume() in SampleActivity:

@Override
  public void onResume() {
    super.onResume();
    endlessScrollListener.reset(0, true);
}

Because after you pause the activity and resume again, the condition !loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold) is always false, thus onLoadMore is never called.

@ultimaters

This comment has been minimized.

Copy link

ultimaters commented Apr 7, 2015

totalItemCount = mLinearLayoutManager.getItemCount();

which gets the fixed count the first loading, may be wrong, I think should be adater.getItemCount(), get from adapter.

@fertwo

This comment has been minimized.

Copy link

fertwo commented Apr 14, 2015

Great, this is a very clean solution. Thank you.

@kprathap23

This comment has been minimized.

Copy link

kprathap23 commented Apr 17, 2015

How can i add progress bar at the footer, while loading data ?

@imran0101

This comment has been minimized.

Copy link

imran0101 commented May 3, 2015

I created a Helper class to get position for any LayoutManager based on Android implementation in LinearLayoutManager.

https://gist.github.com/mipreamble/b6d4b3d65b0b4775a22e#file-recyclerviewpositionhelper-java

@lon9

This comment has been minimized.

Copy link

lon9 commented May 4, 2015

@mipreamble
Thank you! I just looking for something that can be implemented to GridLayoutManager .

@philipgiuliani

This comment has been minimized.

Copy link

philipgiuliani commented May 11, 2015

How can i add a loading spinner as last item while i am fetching the data?

@rainbowhat

This comment has been minimized.

Copy link

rainbowhat commented May 17, 2015

My list of item is retrieved via JSON, how do I know what has been clicked?

  1. Attach custom value to the item on list
  2. Retrieve position and check Array data

Kindly advice >.<

@cielantropi

This comment has been minimized.

Copy link

cielantropi commented May 21, 2015

any updates on adding loading spinner while fetching data?

@geovanisouza92

This comment has been minimized.

Copy link

geovanisouza92 commented Jun 11, 2015

@cielantropi, you can use an android.support.v4.widget.SwipeRefreshLayout:

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipe_refresh"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </android.support.v4.widget.SwipeRefreshLayout>
public class MainActivity implements LoaderManager.LoaderCallbacks<List<...>>, SwipeRefreshLayout.OnRefreshListener {

    private ...Adapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        adapter = new ...Adapter(...);
        getSupportLoaderManager().initLoader(1, null, this);

        setContentView(R.layout.activity_main);

        swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh);
        swipeRefreshLayout.setOnRefreshListener(this);

        final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        recyclerView.setLayoutManager(new LinearLayoutManager(recyclerView.getContext()));
        recyclerView.setAdapter(adapter);
    }

    @Override
    protected void onDestroy() {
        getSupportLoaderManager().destroyLoader(1);
        super.onDestroy();
    }

    @Override
    public void onRefresh() {
        if (!getSupportLoaderManager().hasRunningLoaders()) {
            getSupportLoaderManager().restartLoader(1, null, this);
        }
    }

    @Override
    public Loader<List<FilterableAdapter.Item>> onCreateLoader(int id, Bundle args) {
        if (swipeRefreshLayout != null && !swipeRefreshLayout.isRefreshing()) {
            swipeRefreshLayout.setRefreshing(true);  // This show the spinner on top of activity
        }
        return new OverviewLoader(this);
    }

    @Override
    public void onLoadFinished(Loader<List<...>> loader, List<...> data) {
        if (swipeRefreshLayout != null && swipeRefreshLayout.isRefreshing()) {
            swipeRefreshLayout.setRefreshing(false);  // This hides the spinner
        }
        adapter.addAll(data);  // Custom method on adapter, of course
    }

    @Override
    public void onLoaderReset(Loader<List<...>> loader) {
    }

}
@SainaaMoco

This comment has been minimized.

Copy link

SainaaMoco commented Jul 10, 2015

if (layoutManager.findLastCompletelyVisibleItemPosition() == recyclerAdapter.getItemCount() - 1) {
//load more
} its over 😄

@pushpendra-choudhary

This comment has been minimized.

Copy link

pushpendra-choudhary commented Jul 30, 2015

My Scroller always come on top on load more why? it should be smooth scroll from last position.

@rockerhieu

This comment has been minimized.

Copy link

rockerhieu commented Aug 4, 2015

@pushpendra-spartan: you could try out this https://github.com/rockerhieu/rv-adapter-endless/

@MacSimmy

This comment has been minimized.

Copy link

MacSimmy commented Aug 28, 2015

RecyclerView.setOnScrollListener(listener) is deprecated now. Please update it to addOnScrollListener(new listener);

@engr-erum

This comment has been minimized.

Copy link

engr-erum commented Sep 13, 2015

@pushpendra-spartan u found any solution ? i also look at the code and did not find code for recyclerview selection position ?

@polbins

This comment has been minimized.

Copy link

polbins commented Sep 19, 2015

Implemented this together w/ List/Endless List Adapter w/ code sample.
View it here: https://gist.github.com/polbins/b6830d392e5c687c8e77

@jt-gilkeson

This comment has been minimized.

Copy link

jt-gilkeson commented Sep 28, 2015

This doesn't seem to work well in a fragment - since during paging (in a viewpager for multiple lists) or rotation the recyclerview adapter retains all its items, but the listener must get recreated onCreateView (meaning it thinks its loading and on page 1).

I worked around it by making a no parameter constructor (that I can call in onCreate or I can make the listener as a member variable) and then a setLayoutManager (that I can call in onCreateView) but it feels like a hack.

@rarakat

This comment has been minimized.

Copy link

rarakat commented Sep 30, 2015

@jt-gilkeson i'm in the same situation as you.

Just found out another solution. You don't have to call setLayoutManager.

Just use mAdapter.notifyDataSetChanged();.

mRecyclerView.addOnScrollListener(new EndlessRecyclerOnScrollListener((LinearLayoutManager) mLayoutManager) {
            @Override
            public void onLoadMore(int current_page) {
                // Do somthing with the list

                // Notify the adapter that the data has changed
                mAdapter.notifyDataSetChanged();
            }
        });

Working with recyclerview inside a viewpager fragment.
When the list begun to load endlessly, it wont be at the TOP of the list and it will behave as intended.

@youfacepalm

This comment has been minimized.

Copy link

youfacepalm commented Oct 2, 2015

How do you handle rotation changes? some pages are reloading on rotation changes and duplicate entries are shown on the list. Did anyone faced this issue?

@youfacepalm

This comment has been minimized.

Copy link

youfacepalm commented Oct 6, 2015

@akuafi My situation seemed to be the same as yours. I ended up handling the page number in the activty / fragment itself rather than relying on the listener. Use savedinstancestate as appropriate. This way, you don't need to call mAdapter.notifyDatasetChanged() which is processor heavy if you have 1000+ records :)

@hardikamal

This comment has been minimized.

Copy link

hardikamal commented Oct 29, 2015

how do i add progress bar at the bottom and show when scroll at the end??

@LouisCAD

This comment has been minimized.

Copy link

LouisCAD commented Nov 2, 2015

OnScrollListener is deprecated… we should use OnScrollChangedListener instead

@MuhamedFathy

This comment has been minimized.

Copy link

MuhamedFathy commented Jan 4, 2016

@LouisCAD
Use addOnScrollListener .

@ghorashi

This comment has been minimized.

Copy link

ghorashi commented Jan 4, 2016

How does this scale when data gets huge, like a very loooong list?

@JackLauu

This comment has been minimized.

Copy link

JackLauu commented Jan 21, 2016

If I use you file in my app project, should I include a license or declaration in some place?

@bhargavms

This comment has been minimized.

Copy link

bhargavms commented Jan 22, 2016

Does this work with staggeredGridlLayoutManager??

@cyberrob

This comment has been minimized.

Copy link

cyberrob commented Jan 28, 2016

https://gist.github.com/cyberrob/41a95de42498839d428b Add reset method and pageIndex to 1.

@xuexin

This comment has been minimized.

Copy link

xuexin commented Feb 26, 2016

change if (totalItemCount > previousTotal) to if (totalItemCount != previousTotal) will be better for dataset changing.

@pritam-kadam

This comment has been minimized.

Copy link

pritam-kadam commented Mar 1, 2016

why the onScroll() method is getting called twice? Even its getting called initially.

@esantiago1

This comment has been minimized.

@MVadala

This comment has been minimized.

Copy link

MVadala commented Mar 24, 2016

Thanks a lot for this, much appreciated.

@zfdang

This comment has been minimized.

Copy link

zfdang commented Mar 25, 2016

when I tried to use the above code, I found one problem: onLoadMore() might be called multiple times! So I have to make modification to this implementation to make sure only one onLoadMore is triggered. here are the changes:

`package com.zfdang.zsmth_android.listeners;

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

public abstract class EndlessRecyclerOnScrollListener extends RecyclerView.OnScrollListener {
public static String TAG = EndlessRecyclerOnScrollListener.class.getSimpleName();

private int previousTotal = 0; // The total number of items in the dataset after the last load
private boolean loading = false; // True if we are still waiting for the last set of data to load.
private int visibleThreshold = 2; // The minimum amount of items to have below your current scroll position before loading more.
int firstVisibleItem, visibleItemCount, totalItemCount;

private int current_page = 1;

private LinearLayoutManager mLinearLayoutManager;

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

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

    if(dy < 0) return;
    // check for scroll down
    visibleItemCount = recyclerView.getChildCount();
    totalItemCount = mLinearLayoutManager.getItemCount();
    firstVisibleItem = mLinearLayoutManager.findFirstVisibleItemPosition();

    synchronized (this) {
        if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
            // End has been reached, Do something
            current_page++;
            onLoadMore(current_page);
            loading = true;
        }
    }
}


public void setLoading(boolean loading) {
    this.loading = loading;
}

public abstract void onLoadMore(int current_page);

}`

when I'm using this listener, I will call listener.setLoading(false) explicitly to re-enable onLoadMore after loading is finished.

it works pretty well for me.

@chaitanya79

This comment has been minimized.

Copy link

chaitanya79 commented Apr 13, 2016

Will it work when stackFromEnd is set to true ?

@aliang228

This comment has been minimized.

Copy link

aliang228 commented Jul 19, 2016

                int lastVisibleItem = ((LinearLayoutManager) mLayoutManager).findLastVisibleItemPosition();
                int totalItem = mAdapter.getItemCount();

                if (lastVisibleItem > totalItem - 2 && dy > 0 && notLoading) {
                    curPage++;
                    onLoadMore(curPage);
                }
@abhaysood

This comment has been minimized.

Copy link

abhaysood commented Jul 27, 2016

@zfdang Why is there a synchronized block in onScrolled()?

@griajobag

This comment has been minimized.

Copy link

griajobag commented Aug 11, 2016

i faced an issue like this : "cannot call this method while RecyclerView is computing a layout or scrolling Load More". How should i do?

other questions, do i put

list.clear();
list.addAll(list);
adapter.notifySetDataChanged()

inside loadMore() ?

that error comes, because i put these code inside loadMore()

@vsalguero

This comment has been minimized.

Copy link

vsalguero commented Aug 24, 2016

Thanks so much! excellent

@pratikpopat

This comment has been minimized.

Copy link

pratikpopat commented Sep 17, 2016

Will it work with orientation changes?

@Dooskington

This comment has been minimized.

Copy link

Dooskington commented Oct 2, 2016

Please note that using this view with a SwipeRefreshLayout can cause some issues... You are going to have to reset the EndlessRecyclerViewScrollListener when you refresh, or else it gets confused and may not work when you continue to try to scroll again. There needs to be a basic reset() method added...

public void reset()
{
    previousTotalItemCount = 0;
    currentPage = 1;
}

If you call this in the onRefresh() of your SwipeRefreshLayout.OnRefreshListener(), it should work properly. It does for me.

@maxbax

This comment has been minimized.

Copy link

maxbax commented Oct 5, 2016

@Dooskington your solutions works well for me. Thanks

@pratikbutani

This comment has been minimized.

Copy link

pratikbutani commented Feb 9, 2017

Its calling two times when RecyclerView not have enough (2 or 3 row) data as height of screen.

@pratikbutani

This comment has been minimized.

Copy link

pratikbutani commented Feb 22, 2017

I have Updated this file as per my usage. I got problem like its calling two times when RecyclerView not have enough (2 or 3 row) data as height of screen.

@lectricas

This comment has been minimized.

Copy link

lectricas commented May 16, 2017

This thing is all the way bugged.
It stuck when I use SwipeOnRefresh even with proposet reset.
Eventually employed https://gist.github.com/nesquena/d09dc68ff07e845cc622 this

@gvsharma

This comment has been minimized.

Copy link

gvsharma commented Nov 15, 2017

if (layoutManager.findLastCompletelyVisibleItemPosition() == recyclerAdapter.getItemCount() - 1) {
//load more
}

@sharad-appristine

This comment has been minimized.

Copy link

sharad-appristine commented Dec 13, 2017

it works good with activity but I am facing problem with fragment onLoadMore not calling.

how can be resolved this ?

@nahlanabil84

This comment has been minimized.

Copy link

nahlanabil84 commented Jan 10, 2018

How to use it to scroll a "Horizontal" linearLayoutManager ?

@ZaeemSattar

This comment has been minimized.

Copy link

ZaeemSattar commented Jan 16, 2018

Cool. Useful !
How to use it in horizontal Recyclerview ?

@Mukeshkrjha

This comment has been minimized.

Copy link

Mukeshkrjha commented Mar 1, 2018

Not working when "visibleThreshold" set to 1 & current_page = 0;. Setting "visibleThreshold" as 2 and current_page = 0, cause an unnecessary "onLoadMore" call. Which is result in bad experience to user if there in not data on 2nd page.

@lahrm

This comment has been minimized.

Copy link

lahrm commented Mar 13, 2018

It works. Thanks!
It was not working for me because I was wrapping the recycler view in a scroll view in my layout file. I spent days to find that it was the root cause of the issue and that I should remove the scroll view.

@badrddinb

This comment has been minimized.

Copy link

badrddinb commented May 5, 2019

Great work!
I'm using kotlin and i was stuck on this one.
Thank

@SakthivelFantain

This comment has been minimized.

Copy link

SakthivelFantain commented Oct 17, 2019

You should add a reset method:

public void reset(int previousTotal, boolean loading) {
  this.previousTotal = previousTotal;
  this.loading = loading;
}

And call it on onResume() in SampleActivity:

@Override
  public void onResume() {
    super.onResume();
    endlessScrollListener.reset(0, true);
}

Because after you pause the activity and resume again, the condition !loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold) is always false, thus onLoadMore is never called.

Thanks...Worked like a pro

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.