Skip to content

Instantly share code, notes, and snippets.

@polbins
Last active January 7, 2025 02:57

Revisions

  1. polbins revised this gist Nov 24, 2015. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions EndlessListAdapter.java
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,5 @@
    public abstract class EndlessListAdapter<D extends StellarData, VH extends RecyclerView.ViewHolder>
    extends StellarListAdapter<D, VH> {
    public abstract class EndlessListAdapter<VH extends RecyclerView.ViewHolder>
    extends RecyclerView.Adapter<VH> {
    private boolean mIsAppending = false;

    protected static final int VIEW_TYPE_PROGRESS = 333;
  2. polbins revised this gist Sep 30, 2015. No changes.
  3. polbins revised this gist Sep 21, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion EndlessRecyclerOnScrollListener.java
    Original file line number Diff line number Diff line change
    @@ -38,7 +38,7 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {

    // 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 && previousItemCount > 0) ||
    if ((diffCurrentFromPrevious > 1) ||
    totalItemCount >= mTotalEntries) {
    mLoading = false;
    previousItemCount = totalItemCount;
  4. polbins created this gist Sep 19, 2015.
    48 changes: 48 additions & 0 deletions EndlessListActivity.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,48 @@
    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);
    }

    }
    61 changes: 61 additions & 0 deletions EndlessListAdapter.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,61 @@
    public abstract class EndlessListAdapter<D extends StellarData, VH extends RecyclerView.ViewHolder>
    extends StellarListAdapter<D, 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);
    }
    }

    }
    64 changes: 64 additions & 0 deletions EndlessRecyclerOnScrollListener.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,64 @@
    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 && previousItemCount > 0) ||
    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;
    }
    }
    }
    }

    }
    105 changes: 105 additions & 0 deletions ListAdapter.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,105 @@
    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);
    }
    }
    }

    17 changes: 17 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,17 @@
    #### Endless Scroll Listener for Recycler Views

    Endless Scrolling for Paginated APIs as shown in
    : <a href="http://www.google.com/design/spec/components/progress-activity.html#progress-activity-behavior">Google Design - Progress Activity Behavior</a> 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
    2. **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:
    * https://github.com/codepath/android_guides/wiki/Endless-Scrolling-with-AdapterViews
    * http://benjii.me/2010/08/endless-scrolling-listview-in-android/
    * https://gist.github.com/ssinss/e06f12ef66c51252563e