Skip to content

Instantly share code, notes, and snippets.

@pishguy
Forked from pratamawijaya/EndlessListActivity.java
Created September 21, 2017 08:16
Show Gist options
  • Save pishguy/7f338a21231d471420f518fda45e80d5 to your computer and use it in GitHub Desktop.
Save pishguy/7f338a21231d471420f518fda45e80d5 to your computer and use it in GitHub Desktop.
Android Endless Scroll using RecyclerView

Endless Scroll Listener for Recycler Views

Endless Scrolling for Paginated APIs as shown in : Google Design - Progress Activity Behavior which has the following components:

  1. Endless Scroll Listener for Recycler Views
  • concrete class implements loadMore(pageNumber) to signal the API to load more data
  1. List Adapter which shows:
    • Empty View when given an empty Item List (TextView)
    • Progress View when we are currently loading (ProgressBar)
    • addAll(...) to append data to the end of the list
    • setIsAppending(boolean) for signaling the start/end of data loading

Resources:

public class EndlessListActivity<D> extends Activity {
private EndlessRecyclerOnScrollListener mEndlessScrollListener;
private EndlessListAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
RecyclerView listView = (RecyclerView) findViewById(R.id.list);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
mEndlessScrollListener = new EndlessRecyclerOnScrollListener(mLinearLayoutManager) {
@Override
public void onLoadMore(int current_page) {
// try to get adapter from the recycler view, and cast it appropriately
EndlessListAdapter adapter = (EndlessListAdapter) mListView.getAdapter();
if (adapter != null) {
// signal adapter that loading attempt has started
adapter.setIsAppending(true);
adapter.notifyItemInserted(adapter.getItemCount());
// Call your API Here
loadMore(current_page);
}
}
};
listView.setLayoutManager(linearLayoutManager);
listview.addOnScrollListener(mEndlessScrollListener);
// Do your RecyclerView stuff here
mAdapter = new EndlessListAdapter(...);
}
// When your Data from the API is loaded,
// set the Total Entries on your Endless Scroll Listener
public void onDataLoaded(int serverTotalEntries) {
mEndlessScrollListener.setTotalEntries(serverTotalEntries);
}
// After finishing loading of new data,
// add it to your adapter
public void onLoadMore(List<D> dataList) {
mAdapter.addAll(dataList);
mAdapter.setIsAppending(false);
}
}
public abstract class EndlessListAdapter<VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> {
private boolean mIsAppending = false;
protected static final int VIEW_TYPE_PROGRESS = 333;
public EndlessListAdapter(List<D> dataList) {
super(dataList);
}
public boolean isAppending() {
return mIsAppending;
}
public void setIsAppending(boolean isAppending) {
mIsAppending = isAppending;
}
@Override
public int getItemCount() {
return isAppending() ?
super.getItemCount() + 1 : super.getItemCount();
}
@Override
public int getItemViewType(int position) {
return (isAppending() && position >= super.getItemCount()) ?
VIEW_TYPE_PROGRESS : super.getItemViewType(position);
}
@Override
public final VH onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder vh;
if (viewType == VIEW_TYPE_PROGRESS) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout_progress_bar, parent, false);
vh = new ProgressViewHolder(v);
} else {
vh = super.onCreateViewHolder(parent, viewType);
}
return (VH) vh;
}
@Override
public final void onBindViewHolder(VH holder, int position) {
if (holder instanceof ProgressViewHolder) {
// do nothing
} else {
super.onBindViewHolder(holder, position);
}
}
public static class ProgressViewHolder extends RecyclerView.ViewHolder {
public ProgressViewHolder(View v) {
super(v);
}
}
}
public abstract class EndlessRecyclerOnScrollListener extends RecyclerView.OnScrollListener {
private boolean mLoading = false; // True if we are still waiting for the last set of data to load
private int previousItemCount = 0; // The total number of items in the dataset after the last load
private int mTotalEntries; // The total number of entries in the server
private int current_page = 1; // Always start at Page 1
private LinearLayoutManager mLinearLayoutManager;
public EndlessRecyclerOnScrollListener(LinearLayoutManager linearLayoutManager) {
mLinearLayoutManager = linearLayoutManager;
}
// Concrete classes should implement the Loading of more data entries
public abstract void onLoadMore(int current_page);
public void setTotalEntries(int totalEntries) {
mTotalEntries = totalEntries;
}
// when you're RecyclerView supports refreshing, also refresh the count
public void refresh() {
current_page = 1;
previousItemCount = 0;
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int visibleItemCount = recyclerView.getChildCount();
int totalItemCount = mLinearLayoutManager.getItemCount();
int firstVisibleItem = mLinearLayoutManager.findFirstVisibleItemPosition();
if (mLoading) {
int diffCurrentFromPrevious = totalItemCount - previousItemCount;
// check if current total is greater than previous (diff should be greater than 1, for considering placeholder)
// and if current total is equal to the total in server
if ((diffCurrentFromPrevious > 1) ||
totalItemCount >= mTotalEntries) {
mLoading = false;
previousItemCount = totalItemCount;
}
} else {
if (totalItemCount >= mTotalEntries) {
// do nothing, we've reached the end of the list
} else {
// check if the we've reached the end of the list,
// and if the total items is less than the total items in the server
if ((firstVisibleItem + visibleItemCount) >= totalItemCount &&
totalItemCount < mTotalEntries) {
onLoadMore(++current_page);
mLoading = true;
previousItemCount = totalItemCount;
}
}
}
}
}
public abstract class ListAdapter<D, VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> {
protected List<D> mDataList;
protected static final int VIEW_TYPE_NORMAL = 0;
protected static final int VIEW_TYPE_EMPTY = 111;
public ListAdapter(List<D> dataList) {
mDataList = dataList;
}
@Override
public int getItemCount() {
return mDataList.isEmpty() ? 1 : mDataList.size();
}
@Override
public int getItemViewType(int position) {
if (mDataList.isEmpty()) {
return VIEW_TYPE_EMPTY;
} else {
return VIEW_TYPE_NORMAL;
}
}
public void clear() {
int size = mDataList.size();
mDataList.clear();
notifyItemRangeRemoved(0, size);
}
public void addAll(List<D> data) {
mDataList.addAll(data);
notifyDataSetChanged();
}
protected D getItemAt(int position) {
return mDataList.get(position);
}
protected D replaceItemAt(int position, D newItem) {
D item = mDataList.set(position, newItem);
notifyItemChanged(position);
return item;
}
protected void addItem(int index, D newItem) {
mDataList.add(index, newItem);
notifyItemInserted(index);
}
protected void addItem(D newItem) {
mDataList.add(newItem);
notifyItemInserted(mDataList.size() - 1);
}
protected D removeItem(int index) {
if (mDataList.size() > 0) {
D item = mDataList.remove(index);
notifyItemRemoved(index);
return item;
}
return null;
}
@Override
public VH onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder vh;
if (viewType == VIEW_TYPE_EMPTY) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout_empty_view, parent, false);
vh = new EmptyViewHolder(v);
} else {
vh = createNormalViewHolder(parent, viewType);
}
return (VH) vh;
}
@Override
public void onBindViewHolder(VH holder, int position) {
if (holder instanceof EmptyViewHolder) {
// do nothing
} else {
// Prevent binding of child view holder when List is NULL/Empty
if (mDataList != null && !mDataList.isEmpty()) {
bindNormalViewHolder(holder, position);
}
}
}
protected abstract VH createNormalViewHolder(ViewGroup parent, int viewType);
protected abstract void bindNormalViewHolder(VH holder, int position);
public static class EmptyViewHolder extends RecyclerView.ViewHolder {
public EmptyViewHolder(View v) {
super(v);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment