12/08/2018, 15:36
RecyclerView handle load more, load failed and reach end
Khi làm việc với dự án Android chúng ta sẽ thường cần làm việc với RecyclerView khá nhiều. Dưới đây là demo cách làm việc với RecyclerView có xử lý các trường hợp load more, load failed, reach end Các bước để thêm nó vào project là Bước 1: Thêm các class và file xml bên dưới vào trong project ...
Khi làm việc với dự án Android chúng ta sẽ thường cần làm việc với RecyclerView khá nhiều. Dưới đây là demo cách làm việc với RecyclerView có xử lý các trường hợp load more, load failed, reach end
Các bước để thêm nó vào project là
Bước 1: Thêm các class và file xml bên dưới vào trong project của bạn
EndlessRecyclerViewScrollListener.class
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(RecyclerView.LayoutManager layoutManager) { this.mLayoutManager = layoutManager; } 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); 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); }
BaseRecyclerViewAdapter.class
public abstract class BaseRecyclerViewAdapter<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> { protected LayoutInflater mInflater; protected List<T> mDataList; protected ItemClickListener mItemClickListener; protected BaseRecyclerViewAdapter(@NonNull Context context, ItemClickListener itemClickListener) { mInflater = LayoutInflater.from(context); mItemClickListener = itemClickListener; mDataList = new ArrayList<>(); } public void add(List<T> itemList) { mDataList.addAll(itemList); notifyDataSetChanged(); } public void set(List<T> dataList) { List<T> clone = new ArrayList<>(dataList); mDataList.clear(); mDataList.addAll(clone); notifyDataSetChanged(); } public void clear() { mDataList.clear(); notifyDataSetChanged(); } @Override public int getItemCount() { return mDataList.size(); } public interface ItemClickListener { void onItemClick(View view, int position); } }
LoadMoreRecyclerViewAdapter.class
* Any class extend this class * + should implement getCustomItemViewType * + should call super.onBindViewHolder(holder, position); inside onBindViewHolder */ public abstract class LoadMoreRecyclerViewAdapter<T> extends BaseRecyclerViewAdapter<T> { private static final int TYPE_PROGRESS = 0xFFFF; private RetryLoadMoreListener mRetryLoadMoreListener; private boolean mOnLoadMoreFailed; private boolean mIsReachEnd; protected LoadMoreRecyclerViewAdapter(@NonNull Context context, ItemClickListener itemClickListener, @NonNull RetryLoadMoreListener retryLoadMoreListener) { super(context, itemClickListener); mRetryLoadMoreListener = retryLoadMoreListener; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { switch (viewType) { case TYPE_PROGRESS: View view = mInflater.inflate(R.layout.item_recyclerview_bottom, parent, false); return new BottomViewHolder(view, mRetryLoadMoreListener); } throw new RuntimeException("LoadMoreRecyclerViewAdapter: ViewHolder = null"); } @Override public int getItemViewType(int position) { if (position == bottomItemPosition()) { return TYPE_PROGRESS; } return getCustomItemViewType(position); } private int bottomItemPosition() { return getItemCount() - 1; } protected abstract int getCustomItemViewType(int position); @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (holder instanceof BottomViewHolder) { ((BottomViewHolder) holder).mTvNoMoreItem.setVisibility( mIsReachEnd ? View.VISIBLE : View.GONE); ((BottomViewHolder) holder).mProgressBar.setVisibility( mIsReachEnd ? View.GONE : mOnLoadMoreFailed ? View.GONE : View.VISIBLE); ((BottomViewHolder) holder).layoutRetry.setVisibility( mIsReachEnd ? View.GONE : mOnLoadMoreFailed ? View.VISIBLE : View.GONE); } } @Override public int getItemCount() { return mDataList.size() + 1; // +1 for progress } private static class BottomViewHolder extends RecyclerView.ViewHolder { private ProgressBar mProgressBar; private TextView mTvNoMoreItem; private Button mBtnRetry; private View layoutRetry; private RetryLoadMoreListener mRetryLoadMoreListener; BottomViewHolder(View itemView, RetryLoadMoreListener retryLoadMoreListener) { super(itemView); mRetryLoadMoreListener = retryLoadMoreListener; mProgressBar = (ProgressBar) itemView.findViewById(R.id.progress); layoutRetry = itemView.findViewById(R.id.layout_retry); mBtnRetry = (Button) itemView.findViewById(R.id.button_retry); mTvNoMoreItem = (TextView) itemView.findViewById(R.id.text_no_more_item); layoutRetry.setVisibility(View.GONE); // gone layout retry as default mTvNoMoreItem.setVisibility(View.GONE); // gone text view no more item as default mBtnRetry.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mRetryLoadMoreListener.onRetryLoadMore(); } }); } } /** * It help visible progress when load more */ public void startLoadMore() { mOnLoadMoreFailed = false; notifyDataSetChanged(); } /** * It help visible layout retry when load more failed */ public void onLoadMoreFailed() { mOnLoadMoreFailed = true; notifyItemChanged(bottomItemPosition()); } public void onReachEnd() { mIsReachEnd = true; notifyItemChanged(bottomItemPosition()); } public interface RetryLoadMoreListener { void onRetryLoadMore(); } }
item_recyclerview_bottom.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_awidth="match_parent" android:layout_height="60dp" android:orientation="horizontal" android:gravity="center" > <ProgressBar android:id="@+id/progress" android:layout_awidth="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" /> <LinearLayout android:id="@+id/layout_retry" android:layout_awidth="match_parent" android:layout_height="wrap_content" android:gravity="center" > <TextView android:layout_awidth="wrap_content" android:layout_height="wrap_content" android:text="Load failed" /> <Button android:id="@+id/button_retry" android:layout_marginLeft="30dp" android:layout_awidth="wrap_content" android:layout_height="wrap_content" android:text="Try Again" /> </LinearLayout> <TextView android:id="@+id/text_no_more_item" android:layout_awidth="match_parent" android:layout_height="match_parent" android:text="No more item" android:gravity="center" /> </RelativeLayout>
Bước 2: Chỉnh sửa lại RecyclerView adapter của bạn lại, dạng như thế này
Có lưu ý là phải implement getCustomItemViewType và phải gọi super.onBindViewHolder(holder, position) trong onBindViewHolder
public class MyRecyclerViewAdapter extends LoadMoreRecyclerViewAdapter<Item> { private static final int TYPE_ITEM = 1; public MyRecyclerViewAdapter(Context context, ItemClickListener itemClickListener, RetryLoadMoreListener retryLoadMoreListener) { super(context, itemClickListener, retryLoadMoreListener); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == TYPE_ITEM) { View view = mInflater.inflate(R.layout.recyclerview_item, parent, false); return new ItemViewHolder(view); } return super.onCreateViewHolder(parent, viewType); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (holder instanceof ItemViewHolder) { Item item = mDataList.get(position); ((ItemViewHolder) holder).myTextView.setText(item.getTitle()); } super.onBindViewHolder(holder, position); } @Override protected int getCustomItemViewType(int position) { return TYPE_ITEM; } private class ItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { private TextView myTextView; ItemViewHolder(View itemView) { super(itemView); myTextView = (TextView) itemView.findViewById(R.id.info_text); itemView.setOnClickListener(this); } @Override public void onClick(View view) { if (mItemClickListener != null) { mItemClickListener.onItemClick(view, getAdapterPosition()); } } } }
Bước 3: Ở fragment hoặc Activity có load more
public class MainActivity extends AppCompatActivity implements MyRecyclerViewAdapter.ItemClickListener, MyRecyclerViewAdapter.RetryLoadMoreListener { private RecyclerView mRecyclerView; private MyRecyclerViewAdapter adapter; private int currentPage; @Override protected void onCreate(Bundle savedInstanceState) { ... LinearLayoutManager layoutManager = new LinearLayoutManager(this); mRecyclerView.setLayoutManager(layoutManager); adapter = new MyRecyclerViewAdapter(this, this, this); mRecyclerView.setAdapter(adapter); mRecyclerView.addOnScrollListener(new EndlessRecyclerViewScrollListener(layoutManager){ @Override public void onLoadMore(final int page) { currentPage = page; loadMore(page); } }); } @Override public void onItemClick(View view, int position) { } @Override public void onRetryLoadMore() { loadMore(currentPage); } private void loadMore(final int page){ adapter.startLoadMore(); // example read end if(page == 3){ adapter.onReachEnd(); return; } // start load more } // example load failed //void onAPIFailed(exception){ // adapter.onLoadMoreFailed(); //} }
https://github.com/PhanVanLinh/RecyclerWithLoadmore-Retrieve-ReachEnd