package com.iamtheib.infiniterecyclerview; import android.support.v7.widget.RecyclerView; import android.view.ViewGroup; /** * Modified by iamtheib. * * original version created by Saurabh on 6/2/16. * * This supports a callback to notify when to load more items. * * Implementing Activities/fragments should also call moreDataLoaded(int, int) * to inform the adapter that more data has been loaded. */ public abstract class InfiniteAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter { public interface OnLoadMoreListener { void onLoadMore(); } private static final int VIEW_TYPE_LOADING = 0; private boolean mShouldLoadMore = true; private boolean mIsLoading = false; // Used to indicate the infinite scrolling should be bottom up private boolean mIsReversedScrolling = false; private OnLoadMoreListener mLoadMoreListener; @Override public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if(viewType == VIEW_TYPE_LOADING) { return getLoadingViewHolder(parent); } else { return onCreateView(parent, viewType); } } /** * Subclasses should override this method, to actually bind the view data, * but always call <code>super.onBindViewHolder(holder, position)</code> * to enable the adapter to calculate whether the load more callback should be invoked * * @param holder * @param position */ @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (mShouldLoadMore && !mIsLoading) { int threshold = getVisibleThreshold(); boolean hasReachedThreshold = mIsReversedScrolling ? position <= threshold : position >= getCount() - threshold; if (hasReachedThreshold) { mIsLoading = true; if (mLoadMoreListener != null) { mLoadMoreListener.onLoadMore(); } } } } @Override public final int getItemCount() { int actualCount = getCount(); // So as to avoid nasty index calculations, having reversed scrolling does // not affect the item count. // The consequence of this is, while there is more data to load, the first item on // the list will be replaced by the loading view if(actualCount == 0 || !mShouldLoadMore || mIsReversedScrolling) { return actualCount; } else { return actualCount + 1; } } @Override public final int getItemViewType(int position) { if(isLoadingView(position)) { return VIEW_TYPE_LOADING; } else { int viewType = getViewType(position); if (viewType == VIEW_TYPE_LOADING) { throw new IndexOutOfBoundsException("0 index is reserved for the loading view"); } else { return viewType; } } } private boolean isLoadingView(int position) { // For reversed scrolling, the loading view is always the top one int loadingViewPosition = mIsReversedScrolling ? 0 : getCount(); return position == loadingViewPosition && mShouldLoadMore; } /** * Set as false when you don't want the recycler view to load more data. * This will also remove the loading view */ public void setShouldLoadMore(boolean shouldLoadMore) { this.mShouldLoadMore = shouldLoadMore; } /** * Set as true if you want the endless scrolling to be as the user scrolls * to the top of the list, instead of bottom */ public void setIsReversedScrolling(boolean reversed) { this.mIsReversedScrolling = reversed; } /** * Registers a callback to be notified when there is a need to load more data */ public void setOnLoadMoreListener(OnLoadMoreListener listener) { this.mLoadMoreListener = listener; } /** * This informs the adapter that <code>itemCount</code> more data has been loaded, * starting from <code>positionStart</code> * * This also calls <code>notifyItemRangeInserted(int, int)</code>, * so the implementing class only needs to call this method * * @param positionStart Position of the first item that was inserted * @param itemCount Number of items inserted * */ public void moreDataLoaded(int positionStart, int itemCount) { mIsLoading = false; notifyItemRemoved(positionStart); // remove the loading view notifyItemRangeInserted(positionStart, itemCount); } /** * Returns the number of scrollable items that should be left (threshold) in the * list before <code>OnLoadMoreListener</code> will be called * * You can override this to return a preffered threshold, * or leave it to use the default * * @return integer threshold */ public int getVisibleThreshold() { return 5; } /** * Returns the loading view to be shown at the bottom of the list. * @return loading view */ public abstract RecyclerView.ViewHolder getLoadingViewHolder(ViewGroup parent); /** * The count of the number of items in the list. This does not include the loading item * @return number of items in list */ public abstract int getCount(); /** * Return the view type of the item at <code>position</code> for the purposes * of view recycling. * * <p>0 index is reserved for the loading view. So this function cannot return 0. * * @param position position to query * @return integer value identifying the type of the view needed to represent the item at * <code>position</code>. Type codes need not be contiguous. */ public abstract int getViewType(int position); /** * Called when RecyclerView needs a new ViewHolder of the given type to represent * an item. This is the same as the onCreateViewHolder method in RecyclerView.Adapter, * except that it internally detects and handles the creation on the loading footer * @param parent * @param viewType * @return */ public abstract VH onCreateView(ViewGroup parent, int viewType); }