Skip to content

Instantly share code, notes, and snippets.

@smarteist
Last active October 4, 2020 06:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save smarteist/979e11ebce5b6fd3f3605cf49380ddea to your computer and use it in GitHub Desktop.
Save smarteist/979e11ebce5b6fd3f3605cf49380ddea to your computer and use it in GitHub Desktop.
Android recyclerView Infinite Scrolling (Endless Scrolling)
package com.nodano.memo.utils.AppUtils;
import androidx.annotation.NonNull;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import android.content.res.Resources;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import java.util.Objects;
/**
* Created by Ali Hosseini on 8/8/16.
* this class provide infinite scrolling behavior for recycler View
*/
public class InfiniteScrollProvider extends RecyclerView.OnScrollListener {
/**
* Current window height pixels
*/
private final int windowHeight;
/**
* Setup infinite scrolling behavior for reverse scrolling direction
*/
private boolean isReverse;
/**
* Counter for scrolling steps
*/
private int page;
/**
* Number of items per page
*/
private int itemsPerPage;
/**
* {@link RecyclerView} that we want to provide infinite scrolling behavior for it
*/
private RecyclerView recyclerView;
/**
* listener that trigger when user reach end of list.
*/
private OnLoadMoreListener onLoadMoreListener;
/**
* {@link GridLayoutManager} that is attached to {@link #recyclerView}
* <p/>
* used to determine {@link #lastVisibleItem},{@link #totalItemCount}
*/
private RecyclerView.LayoutManager layoutManager;
/**
* position of last visible item
*/
private int lastVisibleItem;
/**
* position of first visible item
*/
private int firstVisibleItem;
/**
* total items badgeCount of {@link #recyclerView}
*/
private int totalItemCount;
/**
* {@link #onLoadMoreListener} called when {@link #recyclerView} reach to item with position {@link #totalItemCount}
*/
private int threshold;
/**
* span count of {@link #layoutManager}
*/
private int spanCount;
/**
* determines is nested scroll or not
*/
private boolean nestedScroll;
public InfiniteScrollProvider() {
this.windowHeight = Resources.getSystem().getDisplayMetrics().heightPixels;
this.nestedScroll = false;
this.isReverse = false;
this.threshold = 3;
this.spanCount = 1;
this.page = 0;
}
/**
* this function attach {@link #recyclerView} to provide infinite scroll for it
*
* @param recyclerView see {@link #recyclerView} for more information
* @param itemsPerPage Specifies how many items appear on each page
*/
public void attach(RecyclerView recyclerView, int itemsPerPage) {
attach(recyclerView, itemsPerPage, false);
}
/**
* this function attach {@link #recyclerView} to provide infinite scroll for it
*
* @param recyclerView see {@link #recyclerView} for more information
* @param itemsPerPage Specifies how many items appear on each page
* @param reverseScrolling If the scroll is from the bottom to up,
* like a chat page, infiniteScroll can be mirrored
*/
public void attach(RecyclerView recyclerView, int itemsPerPage, boolean reverseScrolling) {
this.page = 0;
this.isReverse = reverseScrolling;
this.itemsPerPage = itemsPerPage;
this.recyclerView = recyclerView;
this.layoutManager = recyclerView.getLayoutManager();
recyclerView.addOnScrollListener(this);
}
/**
* this function detaches {@link #recyclerView} from this infinite scroll.
*/
public void detach() {
try {
this.recyclerView.removeOnScrollListener(this);
this.isReverse = false;
this.recyclerView = null;
this.layoutManager = null;
this.page = 0;
Log.i("InfiniteScroll", "detach: Done");
} catch (NullPointerException e) {
Log.i("InfiniteScroll", "detach: No recycler attached");
}
}
/**
* @param onLoadMoreListener callback for notifying when user reach list ends.
*/
public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
this.onLoadMoreListener = onLoadMoreListener;
}
/**
* manually calling {@link OnLoadMoreListener}
*/
public void retryLoadMore() {
if (onLoadMoreListener != null) {
onLoadMoreListener.onLoadMore(this.page);
}
}
/**
* @param threshold Setting the infinite scroll loading threshold
* it can't be smaller than 2 for logical reasons!
*/
public void setThreshold(int threshold) {
if (threshold <= 2) {
this.threshold = 2;
} else {
this.threshold = threshold;
}
}
/**
* @return the current page number
*/
public int getCurrentPage() {
return page;
}
public void setHasNestedScroll(boolean nestedScroll) {
this.nestedScroll = nestedScroll;
}
/**
* @param page manually changes the page number
*/
public void setCurrentPage(int page) {
this.page = page;
}
/**
* this function get scrolling control of {@link #recyclerView} and whenever
* user reached list ends, {@link #onLoadMoreListener#onLoadMoreListener} will be called
*/
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
if (layoutManager instanceof GridLayoutManager) {
lastVisibleItem = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition();
firstVisibleItem = ((GridLayoutManager) layoutManager).findFirstVisibleItemPosition();
spanCount = ((GridLayoutManager) layoutManager).getSpanCount();
} else if (layoutManager instanceof LinearLayoutManager) {
lastVisibleItem = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
firstVisibleItem = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
int[] lastVisibleItemPositions = ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(null);
int[] firstVisibleItemPositions = ((StaggeredGridLayoutManager) layoutManager).findFirstVisibleItemPositions(null);
// get maximum element within the list
lastVisibleItem = getLastVisibleItem(lastVisibleItemPositions);
firstVisibleItem = getFirstVisibleItem(firstVisibleItemPositions);
spanCount = ((StaggeredGridLayoutManager) layoutManager).getSpanCount();
}
if (nestedScroll) {
checkLastItemCorrectness();
}
try {
totalItemCount = layoutManager.getItemCount();
} catch (NullPointerException e) {
totalItemCount = 0;
}
if (totalItemCount > threshold && itemsPerPage > 0) {
if (checkReachedTheThreshold(spanCount * threshold) && onLoadMoreListener != null) {
int step = (totalItemCount + itemsPerPage - 1) / itemsPerPage;
if (step > page) {
page = step;
onLoadMoreListener.onLoadMore(page);
Log.i("InfiniteScroll", "End Of the List This is step : " + page);
}
}
}
super.onScrolled(recyclerView, dx, dy);
}
/**
* Checking correctness of the {@see lastVisibleItem}
* Some parent views like nested scroll view affects
* recycler view scrolling behaviour and this prevents
* the layout manager from returning the last item correctly
*/
private void checkLastItemCorrectness() {
View item = layoutManager.getChildAt(0);
if (item != null) {
int itemHeight = item.getHeight();
int maxVisibleItem = spanCount + firstVisibleItem + ((windowHeight + itemHeight - 1) / itemHeight) * spanCount;
if (lastVisibleItem > maxVisibleItem) {
lastVisibleItem = maxVisibleItem;
}
}
}
/**
* @return check if scroll position is on threshold or not
* it depends on {@param isReverse} which specifies
* the direction of the infinite scroll
*/
private boolean checkReachedTheThreshold(int threshold) {
if (isReverse) {
return firstVisibleItem < threshold;
} else {
return lastVisibleItem > (totalItemCount - threshold);
}
}
/**
* @return Last visible item position for staggeredGridLayoutManager
*/
private int getLastVisibleItem(int[] lastVisibleItemPositions) {
int maxSize = 0;
for (int position : lastVisibleItemPositions) {
if (position > maxSize) {
maxSize = position;
}
}
return maxSize;
}
/**
* @return First visible item position for staggeredGridLayoutManager
*/
private int getFirstVisibleItem(int[] firstVisibleItemPositions) {
int minSize = 0;
if (firstVisibleItemPositions.length > 0) {
minSize = firstVisibleItemPositions[0];
for (int position : firstVisibleItemPositions) {
if (position < minSize) {
minSize = position;
}
}
}
return minSize;
}
public interface OnLoadMoreListener {
/**
* Created by Ali Hosseini on 8/8/16.
* callback for notify View when user reached to list ends.
*
* @param step indicating steps of the distance between the beginning
* and current point by counting them from zero to n.
*/
void onLoadMore(int step);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment