Instantly share code, notes, and snippets.

Embed
What would you like to do?
Endless RecyclerView scrolling for different layout managers
public abstract class EndlessRecyclerViewScrollListener extends RecyclerView.OnScrollListener {
// The minimum amount of items to have below your current scroll position
// before loading more.
private int visibleThreshold = 5;
// The current offset index of data you have loaded
private int currentPage = 0;
// The total number of items in the dataset after the last load
private int previousTotalItemCount = 0;
// True if we are still waiting for the last set of data to load.
private boolean loading = true;
// Sets the starting page index
private int startingPageIndex = 0;
RecyclerView.LayoutManager mLayoutManager;
public EndlessRecyclerViewScrollListener(LinearLayoutManager layoutManager) {
this.mLayoutManager = layoutManager;
}
public EndlessRecyclerViewScrollListener(GridLayoutManager layoutManager) {
this.mLayoutManager = layoutManager;
visibleThreshold = visibleThreshold * layoutManager.getSpanCount();
}
public EndlessRecyclerViewScrollListener(StaggeredGridLayoutManager layoutManager) {
this.mLayoutManager = layoutManager;
visibleThreshold = visibleThreshold * layoutManager.getSpanCount();
}
public int getLastVisibleItem(int[] lastVisibleItemPositions) {
int maxSize = 0;
for (int i = 0; i < lastVisibleItemPositions.length; i++) {
if (i == 0) {
maxSize = lastVisibleItemPositions[i];
}
else if (lastVisibleItemPositions[i] > maxSize) {
maxSize = lastVisibleItemPositions[i];
}
}
return maxSize;
}
// This happens many times a second during a scroll, so be wary of the code you place here.
// We are given a few useful parameters to help us work out if we need to load some more data,
// but first we check if we are waiting for the previous load to finish.
@Override
public void onScrolled(RecyclerView view, int dx, int dy) {
int lastVisibleItemPosition = 0;
int totalItemCount = mLayoutManager.getItemCount();
if (mLayoutManager instanceof StaggeredGridLayoutManager) {
int[] lastVisibleItemPositions = ((StaggeredGridLayoutManager) mLayoutManager).findLastVisibleItemPositions(null);
// get maximum element within the list
lastVisibleItemPosition = getLastVisibleItem(lastVisibleItemPositions);
} else if (mLayoutManager instanceof GridLayoutManager) {
lastVisibleItemPosition = ((GridLayoutManager) mLayoutManager).findLastVisibleItemPosition();
} else if (mLayoutManager instanceof LinearLayoutManager) {
lastVisibleItemPosition = ((LinearLayoutManager) mLayoutManager).findLastVisibleItemPosition();
}
// If the total item count is zero and the previous isn't, assume the
// list is invalidated and should be reset back to initial state
if (totalItemCount < previousTotalItemCount) {
this.currentPage = this.startingPageIndex;
this.previousTotalItemCount = totalItemCount;
if (totalItemCount == 0) {
this.loading = true;
}
}
// If it’s still loading, we check to see if the dataset count has
// changed, if so we conclude it has finished loading and update the current page
// number and total item count.
if (loading && (totalItemCount > previousTotalItemCount)) {
loading = false;
previousTotalItemCount = totalItemCount;
}
// If it isn’t currently loading, we check to see if we have breached
// the visibleThreshold and need to reload more data.
// If we do need to reload some more data, we execute onLoadMore to fetch the data.
// threshold should reflect how many total columns there are too
if (!loading && (lastVisibleItemPosition + visibleThreshold) > totalItemCount) {
currentPage++;
onLoadMore(currentPage, totalItemCount, view);
loading = true;
}
}
// Call this method whenever performing new searches
public void resetState() {
this.currentPage = this.startingPageIndex;
this.previousTotalItemCount = 0;
this.loading = true;
}
// Defines the process for actually loading more data based on page
public abstract void onLoadMore(int page, int totalItemsCount, RecyclerView view);
}
@Nishanthbabu

This comment has been minimized.

Copy link

Nishanthbabu commented Apr 6, 2016

can u give me the source code

@yilishazi

This comment has been minimized.

Copy link

yilishazi commented May 17, 2016

@PLNech

This comment has been minimized.

Copy link

PLNech commented May 23, 2016

Thank you for this useful gist!

One small thing: the condition line 57 (mLayoutManager instanceof GridLayoutManager) will always be false, as GridLayoutManager is a subclass of LinearLayoutManager a layout cannot pass this condition without already passing the previous instanceof LinearLayoutManager test.
This being said, findLastVisibleItemPosition is the same for GridLayoutManager and LinearLayoutManager as it is only implemented in the latter, so there is no point in having a special case for GridLayoutManager.

TL; DR: You can remove lines 57 and 58 ;)

@a3adel

This comment has been minimized.

Copy link

a3adel commented Aug 2, 2016

You should check if(dy>0) to make sure the user is scrolling down so that the loading is not called each time the scrolls

@rahulshimpi015

This comment has been minimized.

Copy link

rahulshimpi015 commented Sep 21, 2016

There is one problem, it does not work with SwipeRefreshLayout. It's working perfect without SwipeRefreshLayout. And I noticed that for first call previousTotalItemCount should be zero. But when I refresh it takes last value. For example, if four items are visible and I refresh then previousTotalItemCount would be four but it should be zero.

@geolyth

This comment has been minimized.

Copy link

geolyth commented Sep 22, 2016

how can i obtain same effect on Scroll To Top?

@hahn

This comment has been minimized.

Copy link

hahn commented Nov 24, 2016

it not work when recyclerview inside viewpager. how to fix it?
thanks

@ananth10

This comment has been minimized.

Copy link

ananth10 commented Jan 12, 2017

I have to implement Endless scroll for recyclerview but i am showing some content above recyclerview , so i have used recyclerview inside nestedscrollview , if i use like this , recyclerview draws all views at first time. so
endless scroll for calling web service keep on calling. could you suggest me any solution for this?

@CapnSpellcheck

This comment has been minimized.

Copy link

CapnSpellcheck commented Jan 31, 2017

The check (lastVisibleItemPosition + visibleThreshold) > totalItemCount has an off-by-one error. I found it by using visibleThreshold = 1 since I don't want to load too early. If totalItemCount == 10 then lastVisibleItemPosition will be 9 at the very bottom, so 9 + 1 = 10 which cannot be greater. You can fix this just by using >=.

@nvcruzhe

This comment has been minimized.

Copy link

nvcruzhe commented Apr 4, 2017

@rahulshimpi015

For ones that have used this code plus a SwipeRefreshLayout, notice that once the list has got all the elements on the server sido or data provider, and then you pull to refresh the paginator stops working.

Just check that the variable loading, is true, avoiding to get into the condition set on line 82, also avoiding the method onLoadMore(...) to be executed.

SOLUTION:
Once you do pull to refresh reset all variables to the initial state.
this.currentPage = 0;
this.previousTotalItemCount = 0;
this.loading = false;

once this is done, the code will work like charm!

@dhiyaulhaqZA

This comment has been minimized.

Copy link

dhiyaulhaqZA commented May 26, 2017

Thanks alot 👍

@AdamSHurwitz

This comment has been minimized.

Copy link

AdamSHurwitz commented Jul 15, 2017

What is the recommended page size to use?

@BenjaminRichardson

This comment has been minimized.

Copy link

BenjaminRichardson commented Sep 22, 2017

Thank you so much for this snippet at the rest of your guide! I'm finding it hugely useful.

A couple quick questions:
Why do we need the concept of a page? It seems like you could pass along the current total item count or something similar and let the loadNextDataFromApi function determine what "page" needs to be retrieved.

Related to that, I'm curious why you chose to have the scroll listener dictate what is to be retrieved instead of loadNextDataFromApi tracking where it left off and returning the next set of data. I guess they could both work, I was just curious if I missed something.

Thank you again for the great guide!

@Coccoonx

This comment has been minimized.

Copy link

Coccoonx commented Oct 12, 2017

Please, any solution of endless scrolling usage with RecyclerView inside a NestedScrollView ?

@AucT

This comment has been minimized.

Copy link

AucT commented Nov 12, 2017

don't work after pause or after turn off and on internet

@javieritis

This comment has been minimized.

Copy link

javieritis commented Feb 9, 2018

the same problem @AucT ..... any solution?

@ranforsin

This comment has been minimized.

Copy link

ranforsin commented Mar 4, 2018

It's awesome, but does anyone know how we can handle orientation change using this class?
I tried to use onSaveInstanceState() to save page number but then I'm unable to scroll up - only down :/
( I populate views from the Internet and I use page parameter to access the proper page on a website from which I download content)

@quevon24

This comment has been minimized.

Copy link

quevon24 commented Mar 12, 2018

There is a way to load more when scroll up? Like Facebook mesenger app which appends the messages at top when scroll up

@alphater

This comment has been minimized.

Copy link

alphater commented Mar 15, 2018

Hey, great code But I encountered a problem during use: when loading more, it will be repeated many times, I do not know if it is a case, so I modified the code, added an identifier to determine whether Loaded then reset this identifier in resetState.

@sharukhmohammed

This comment has been minimized.

Copy link

sharukhmohammed commented Aug 10, 2018

This doesn't work with NestedScrollView, all the pagination is happening at once, without user intervention.
Waiting for a solution for this great snippet.

@mapo-lp

This comment has been minimized.

Copy link

mapo-lp commented Oct 4, 2018

This doesn't work with NestedScrollView, all the pagination is happening at once ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment