BaseRecyclerViewAdapterHelper源码解读(四) 上拉加载更多
来源:互联网 发布:网络渗透 pdf 编辑:程序博客网 时间:2024/06/03 03:24
上拉加载
上拉加载无需监听滑动事件,可自定义加载布局,显示异常提示,自定义异常提示。
此篇文章为BaseRecyclerViewAdapterHelper源码解读第四篇,开源库地址,如果没有看过之前3篇文章的同学可以先去看看,大神可直接跳过.
BaseRecyclerViewAdapterHelper源码解读(一) 封装简单的adapter和万能的BaseViewHolder
BaseRecyclerViewAdapterHelper源码解读(二) 添加header和footer
BaseRecyclerViewAdapterHelper源码解读(三) 添加动画
今天给大家带来BaseRecyclerViewAdapterHelper是如何添加动画的.由于本人才学尚浅,如有有不对的地方,欢迎指正,谢谢.
开源库使用自动加载方法
上拉加载
// 滑动最后一个Item的时候回调onLoadMoreRequested方法setOnLoadMoreListener(RequestLoadMoreListener);
默认第一次加载会回调onLoadMoreRequested()加载更多这个方法,如果不需要可以配置如下:
mQuickAdapter.disableLoadMoreIfNotFullPage();
回调处理代码
mQuickAdapter.setOnLoadMoreListener(new BaseQuickAdapter.RequestLoadMoreListener() { @Override public void onLoadMoreRequested() { mRecyclerView.postDelayed(new Runnable() { @Override public void run() { if (mCurrentCounter >= TOTAL_COUNTER) { //数据全部加载完毕 mQuickAdapter.loadMoreEnd(); } else { if (isErr) { //成功获取更多数据 mQuickAdapter.addData(DataServer.getSampleData(PAGE_SIZE)); mCurrentCounter = mQuickAdapter.getData().size(); mQuickAdapter.loadMoreComplete(); } else { //获取更多数据失败 isErr = true; Toast.makeText(PullToRefreshUseActivity.this, R.string.network_err, Toast.LENGTH_LONG).show(); mQuickAdapter.loadMoreFail(); } } } }, delayMillis); } }, mReyclerView);这里可能看的不是很清楚,详情请看官方demo,https://github.com/CymChad/BaseRecyclerViewAdapterHelper/blob/d296d1fb4e7a64b9fa8a2601f3f896d3a9518be5/app/src/main/java/com/chad/baserecyclerviewadapterhelper/PullToRefreshUseActivity.java
加载完成(注意不是加载结束,而是本次数据加载结束并且还有下页数据)
mQuickAdapter.loadMoreComplete();
加载失败
mQuickAdapter.loadMoreFail();
加载结束
mQuickAdapter.loadMoreEnd();
设置监听器,开启监听上拉加载
/** * 设置监听RecyclerView上拉加载更多 并设置监听器 * @param requestLoadMoreListener * @param recyclerView */ public void setOnLoadMoreListener(RequestLoadMoreListener requestLoadMoreListener, RecyclerView recyclerView) { openLoadMore(requestLoadMoreListener); if (getRecyclerView() == null) { setRecyclerView(recyclerView); } } /** * * 开启上拉加载更多 * @param requestLoadMoreListener */ private void openLoadMore(RequestLoadMoreListener requestLoadMoreListener) { this.mRequestLoadMoreListener = requestLoadMoreListener; mNextLoadEnable = true; mLoadMoreEnable = true; mLoading = false; }
设置什么时候回调?
/** * 设置当列表滑动到倒数第N个Item的时候(默认是1)回调onLoadMoreRequested()方法 * @param preLoadNumber */ public void setPreLoadNumber(int preLoadNumber) { if (preLoadNumber > 1) { mPreLoadNumber = preLoadNumber; } }
先来说简单的,上面这个方法比较简单,属于配置型的方法.
就是设置当列表滑动到倒数第N个Item的时候(默认是1)回调onLoadMoreRequested()方法.待会儿下面会用到这个参数,先放着.
另外,这个方法可以在使用时不必调用,因为已经有默认值了.
/** * To bind different types of holder and solve different the bind events * * @param holder * @param position * @see #getDefItemViewType(int) */ @Override public void onBindViewHolder(K holder, int position) { //Add up fetch logic, almost like load more, but simpler. //这里是判断是否需要下拉加载更多 autoUpFetch(position); //Do not move position, need to change before LoadMoreView binding //判断是否需要进行上拉加载更多 autoLoadMore(position); int viewType = holder.getItemViewType(); switch (viewType) { case 0: convert(holder, getItem(position - getHeaderLayoutCount())); break; case LOADING_VIEW: mLoadMoreView.convert(holder); break; case HEADER_VIEW: break; case EMPTY_VIEW: break; case FOOTER_VIEW: break; default: convert(holder, getItem(position - getHeaderLayoutCount())); break; } } /** * 根据position位置判断当前是否需要进行加载更多 * * @param position 当前onBindViewHolder()的Position */ private void autoLoadMore(int position) { // 判断是否可以进行加载更多的逻辑 if (getLoadMoreViewCount() == 0) { return; } //只有在当前列表的倒数mPreLoadNumber个item开始绑定数据时才进行加载更多的逻辑 if (position < getItemCount() - mPreLoadNumber) { return; } //判断当前加载状态,如果不是默认状态(可能正处于 正在加载中 的状态),则不进行加载 if (mLoadMoreView.getLoadMoreStatus() != LoadMoreView.STATUS_DEFAULT) { return; } //设置当前状态:加载中 mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_LOADING); if (!mLoading) { mLoading = true; if (getRecyclerView() != null) { getRecyclerView().post(new Runnable() { @Override public void run() { //回调 让调用者去处理加载更多的逻辑 mRequestLoadMoreListener.onLoadMoreRequested(); } }); } else { mRequestLoadMoreListener.onLoadMoreRequested(); } } } /** * Load more view count * 判断是否可以进行加载更多的逻辑 * @return 0 or 1 */ public int getLoadMoreViewCount() { //参数合法性 加载更多状态 if (mRequestLoadMoreListener == null || !mLoadMoreEnable) { return 0; } //可加载下一页 有无更多数据 if (!mNextLoadEnable && mLoadMoreView.isLoadEndMoreGone()) { return 0; } //当前数据项个数 if (mData.size() == 0) { return 0; } return 1; }
重点来了,加载更多的主要逻辑就在这里:当在onBindViewHolder()的时候,根据当前item的position位置,然后去判断是否应该执行加载更多.
具体判断逻辑:当一个item第一次进入window界面时,会调用onBindViewHolder()去绑定数据,这个时候我们知道该position的位置,
于是我们就可以这样干:设置一个mPreLoadNumber标志位置( 当列表滑动到倒数第N个Item的时候(默认是1)回调onLoadMoreRequested()方法 ),
当onBindViewHolder()在绑定数据时的position是最后mPreLoadNumber个时,我们即进行加载更多的回调,然后让调用者去处理.
当然,在回调之前,我们需要进行一些判断,确定当前是否可以进行加载更多.
- mRequestLoadMoreListener监听器是否为null,当前是否处于可以加载更多的状态(mLoadMoreEnable标志位控制)
- 当前有无更多数据(这个由外界调用者决定)
- 当前的数据项个数是否为0,如果没有数据项,那就不必加载更多
- 是否进入倒数的那mPreLoadNumber区域
- 判断当前(mLoadMoreView 这是加载更多的View )加载状态,如果不是默认状态(可能正处于 正在加载中 的状态),则不进行加载
好吧,细心的观众可能已经发现了,上面的这种方式其实有一个缺点:当数据项个数小于1屏幕,那么最后倒数的mPreLoadNumber个肯定是可见的,既然可见那么肯定会执行该item的onBindViewHolder(),执行该方法即会判断是否需要执行加载更多,显然这时是符合条件的,于是就会出现数据未满一屏幕会自动回调onLoadMoreRequested()并且还在那里显示正在加载中.
明显,这时不符合我们的需求的.于是官方有一个解决方案.往下看.
/** * bind recyclerView {@link #bindToRecyclerView(RecyclerView)} before use! * * @see #disableLoadMoreIfNotFullPage(RecyclerView) */ public void disableLoadMoreIfNotFullPage() { //检查当前RecyclerView是否为null checkNotNull(); disableLoadMoreIfNotFullPage(getRecyclerView()); } /** * check if full page after {@link #setNewData(List)}, if full, it will enable load more again. * <p> * 不是配置项!! * <p> * 这个方法是用来检查是否满一屏的,所以只推荐在 {@link #setNewData(List)} 之后使用 * 原理:先关闭 load more,检查完了再决定是否开启 * 数据项个数未满一屏幕,则不开启load more * 数据项个数 > 一屏幕,则继续开启load more * <p> * 不是配置项!! * * @param recyclerView your recyclerView * @see #setNewData(List) */ public void disableLoadMoreIfNotFullPage(RecyclerView recyclerView) { // 设置加载状态为false setEnableLoadMore(false); if (recyclerView == null) return; RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); if (manager == null) return; if (manager instanceof LinearLayoutManager) { final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) manager; recyclerView.postDelayed(new Runnable() { @Override public void run() { //数据项个数 > 一屏幕,则继续开启load more if ((linearLayoutManager.findLastCompletelyVisibleItemPosition() + 1) != getItemCount()) { setEnableLoadMore(true); } } }, 50); } else if (manager instanceof StaggeredGridLayoutManager) { final StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) manager; recyclerView.postDelayed(new Runnable() { @Override public void run() { //返回StaggeredGridLayoutManager布局的跨度数 final int[] positions = new int[staggeredGridLayoutManager.getSpanCount()]; //返回每一个跨度(列)的最后一个可见的item的位置 赋值到该数组里面 staggeredGridLayoutManager.findLastCompletelyVisibleItemPositions(positions); //找出数组中最大的数(即StaggeredGridLayoutManager布局的当前可见的最下面那个item) int pos = getTheBiggestNumber(positions) + 1; // 数据项个数 > 一屏幕,则继续开启load more if (pos != getItemCount()) { setEnableLoadMore(true); } } }, 50); } } /** * 返回数组中的最大值 * @param numbers * @return */ private int getTheBiggestNumber(int[] numbers) { int tmp = -1; if (numbers == null || numbers.length == 0) { return tmp; } for (int num : numbers) { if (num > tmp) { tmp = num; } } return tmp; } /** * Set the enabled state of load more. * 设置上拉加载更多是否可用 * * @param enable True if load more is enabled, false otherwise. */ public void setEnableLoadMore(boolean enable) { //之前的状态需要和现在的状态做对比 int oldLoadMoreCount = getLoadMoreViewCount(); mLoadMoreEnable = enable; int newLoadMoreCount = getLoadMoreViewCount(); if (oldLoadMoreCount == 1) { if (newLoadMoreCount == 0) { //之前有 现在没有 需要移除 notifyItemRemoved(getLoadMoreViewPosition()); } } else { if (newLoadMoreCount == 1) { //将加载布局插入 mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_DEFAULT); notifyItemInserted(getLoadMoreViewPosition()); } } }
这段代码我看到在开源项目的讨论区异常热门,好像很多人都遇到了使用disableLoadMoreIfNotFullPage()无效的事件.
可能是他们用错了吧,可能.disableLoadMoreIfNotFullPage()是需要在setNewData()之后调用才有效.
disableLoadMoreIfNotFullPage()里面想做的事情就是:判断是否需要load more,他判断的依据是:
查看当前屏幕内的最底部的那个item的索引是否与总的数据项个数相等.
- 如果相等,那么说明未满一屏幕,不需要开启load more
- 如果不相等,那么说明满了一屏幕,需要开启laod more
创建加载布局item 并 设置加载布局的点击事件
@Override public K onCreateViewHolder(ViewGroup parent, int viewType) { K baseViewHolder = null; this.mContext = parent.getContext(); this.mLayoutInflater = LayoutInflater.from(mContext); switch (viewType) { case LOADING_VIEW: baseViewHolder = getLoadingView(parent); break; case HEADER_VIEW: baseViewHolder = createBaseViewHolder(mHeaderLayout); break; case EMPTY_VIEW: baseViewHolder = createBaseViewHolder(mEmptyLayout); break; case FOOTER_VIEW: baseViewHolder = createBaseViewHolder(mFooterLayout); break; default: baseViewHolder = onCreateDefViewHolder(parent, viewType); bindViewClickListener(baseViewHolder); } baseViewHolder.setAdapter(this); return baseViewHolder; } private K getLoadingView(ViewGroup parent) { //加载 加载布局 View view = getItemView(mLoadMoreView.getLayoutId(), parent); //生成baseviewholder K holder = createBaseViewHolder(view); //设置加载布局的点击事件 holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mLoadMoreView.getLoadMoreStatus() == LoadMoreView.STATUS_FAIL) { //之前是加载失败状态时 前去刷新 notifyLoadMoreToLoading(); } if (mEnableLoadMoreEndClick && mLoadMoreView.getLoadMoreStatus() == LoadMoreView .STATUS_END) { //加载更多布局可以被点击 并且 之前状态是结束状态 notifyLoadMoreToLoading(); } } }); return holder; } /** * The notification starts the callback and loads more * 通知启动回调并加载更多 */ public void notifyLoadMoreToLoading() { //如果当前正在加载中,则不用管 if (mLoadMoreView.getLoadMoreStatus() == LoadMoreView.STATUS_LOADING) { return; } //将加载更多布局的状态设置为默认状态 这样当下面刷新adapter时会回调onBindViewHolder()从而触发 //autoLoadMore()方法去判断是否需要加载更多,这时候刚好又是默认状态是可以更新的,于是就去回调onLoadMoreRequested()方法 mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_DEFAULT); notifyItemChanged(getLoadMoreViewPosition()); }
这里的目标是给加载更多布局设置点击事件,可以看到其实在代码里面把加载更多布局是直接设置了点击事件的,只是根据不同的状态决定是否需要执行加载更多的逻辑.只有下面2种情况需要去加载更多.
- 之前是加载失败状态时 加载布局被点击
- 之前是结束状态 并且 加载更多布局可以被点击
满足这两种情况时,就把加载布局view的状态设置成默认状态,并且刷新adapter的最后一项(即加载更多布局那一项),这样adapter会回调onBindViewHolder(),而在onBindViewHolder()又调用了autoLoadMore()方法去判断是否需要加载更多,
显然此时是符合条件的,需要刷新,于是回调onLoadMoreRequested(),并且把加载布局的状态改为STATUS_LOADING正在加载的状态,这样加载布局的样式也跟着改变了.
加载完成
注意不是加载结束,而是本次数据加载结束并且还有下页数据
/** * Refresh complete * 刷新完成时调用 * */ public void loadMoreComplete() { if (getLoadMoreViewCount() == 0) { return; } //将当前加载状态改为false 表示未在加载 mLoading = false; //可进行下一页加载 mNextLoadEnable = true; // 恢复加载更多布局的状态 mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_DEFAULT); // 告知加载更多布局被更新了,需要刷新一下 notifyItemChanged(getLoadMoreViewPosition()); } /** * Gets to load more locations * 获取加载更多的布局的索引 * @return */ public int getLoadMoreViewPosition() { return getHeaderLayoutCount() + mData.size() + getFooterLayoutCount(); }
刷新完成之后,需要做一些善后操作,如上所示,代码注释已经很清楚了.
加载失败
/** * Refresh failed * 加载失败 */ public void loadMoreFail() { if (getLoadMoreViewCount() == 0) { return; } //当前加载状态 切换为未在加载中 mLoading = false; //加载布局设置为加载失败 mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_FAIL); //通知加载布局更新了,需要刷新 notifyItemChanged(getLoadMoreViewPosition()); }
就是简单地做一下判断,是否可以继续加载,并且更新布局.
加载结束
/** * Refresh end, no more data * 加载更多,并且没有更多数据了 调用此方法即表示无更多数据了 * 这里设置加载更多布局依然可见 */ public void loadMoreEnd() { loadMoreEnd(false); } /** * Refresh end, no more data * 加载更多,并且没有更多数据了 调用此方法即表示无更多数据了 * gone:设置加载更多布局是否可见 true:不可见 false:可见 * @param gone if true gone the load more view */ public void loadMoreEnd(boolean gone) { if (getLoadMoreViewCount() == 0) { return; } ////当前加载状态 切换为未在加载中 mLoading = false; //不能再加载下一页了 因为已经没有更多数据了 mNextLoadEnable = false; //设置加载更多布局是否可见 mLoadMoreView.setLoadMoreEndGone(gone); if (gone) { //如果布局不可见,则更新 notifyItemRemoved(getLoadMoreViewPosition()); } else { //如果布局可见,则先更新布局(切换为STATUS_END状态那种布局) mLoadMoreView.setLoadMoreStatus(LoadMoreView.STATUS_END); //并更新adapter notifyItemChanged(getLoadMoreViewPosition()); } }
设置加载结束,即表示没有更多的数据可以加载了,于是把mNextLoadEnable标志位设为false,表示无法再加载下一页.
然后根据是否需要显示加载布局,进行刷新adapter.
上拉加载布局
在源码里面有一个抽象类LoadMoreView.
public abstract class LoadMoreView { public static final int STATUS_DEFAULT = 1; /** * 加载中 */ public static final int STATUS_LOADING = 2; /** * 加载失败 */ public static final int STATUS_FAIL = 3; /** * 加载结束 没有更多数据 */ public static final int STATUS_END = 4; /** * 当前加载更多的状态 */ private int mLoadMoreStatus = STATUS_DEFAULT; private boolean mLoadMoreEndGone = false; public void setLoadMoreStatus(int loadMoreStatus) { this.mLoadMoreStatus = loadMoreStatus; } public int getLoadMoreStatus() { return mLoadMoreStatus; } public void convert(BaseViewHolder holder) { //根据不同的状态 switch (mLoadMoreStatus) { case STATUS_LOADING: visibleLoading(holder, true); visibleLoadFail(holder, false); visibleLoadEnd(holder, false); break; case STATUS_FAIL: visibleLoading(holder, false); visibleLoadFail(holder, true); visibleLoadEnd(holder, false); break; case STATUS_END: visibleLoading(holder, false); visibleLoadFail(holder, false); visibleLoadEnd(holder, true); break; case STATUS_DEFAULT: visibleLoading(holder, false); visibleLoadFail(holder, false); visibleLoadEnd(holder, false); break; } } private void visibleLoading(BaseViewHolder holder, boolean visible) { holder.setVisible(getLoadingViewId(), visible); } private void visibleLoadFail(BaseViewHolder holder, boolean visible) { holder.setVisible(getLoadFailViewId(), visible); } private void visibleLoadEnd(BaseViewHolder holder, boolean visible) { final int loadEndViewId = getLoadEndViewId(); if (loadEndViewId != 0) { holder.setVisible(loadEndViewId, visible); } } /** * 设置标志 有无更多数据 * @param loadMoreEndGone true:无更多数据 */ public final void setLoadMoreEndGone(boolean loadMoreEndGone) { this.mLoadMoreEndGone = loadMoreEndGone; } public final boolean isLoadEndMoreGone() { if (getLoadEndViewId() == 0) { return true; } return mLoadMoreEndGone; } /** * No more data is hidden * * @return true for no more data hidden load more * @deprecated Use {@link BaseQuickAdapter#loadMoreEnd(boolean)} instead. */ @Deprecated public boolean isLoadEndGone() { return mLoadMoreEndGone; } /** * load more layout * * @return */ public abstract @LayoutRes int getLayoutId(); /** * loading view * * @return */ protected abstract @IdRes int getLoadingViewId(); /** * load fail view * * @return */ protected abstract @IdRes int getLoadFailViewId(); /** * load end view, you can return 0 * * @return */ protected abstract @IdRes int getLoadEndViewId();}
该类是用于管理加载布局的,不同的状态显示不同的布局.
源码里面已经给我们提供了一个默认的加载布局,可以直接使用,当然了,是支持自定义的,只需要继承LoadMoreView就行.
默认的加载布局如下:
public final class SimpleLoadMoreView extends LoadMoreView { @Override public int getLayoutId() { return R.layout.quick_view_load_more; } @Override protected int getLoadingViewId() { return R.id.load_more_loading_view; } @Override protected int getLoadFailViewId() { return R.id.load_more_load_fail_view; } @Override protected int getLoadEndViewId() { return R.id.load_more_load_end_view; }}
下面是xml
<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="@dimen/dp_40"> <LinearLayout android:id="@+id/load_more_loading_view" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="horizontal"> <ProgressBar android:id="@+id/loading_progress" android:layout_width="wrap_content" android:layout_height="wrap_content" style="?android:attr/progressBarStyleSmall" android:layout_marginRight="@dimen/dp_4"/> <TextView android:id="@+id/loading_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/dp_4" android:text="@string/loading" android:textColor="@android:color/black" android:textSize="@dimen/sp_14"/> </LinearLayout> <FrameLayout android:id="@+id/load_more_load_fail_view" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone"> <TextView android:id="@+id/tv_prompt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="@string/load_failed"/> </FrameLayout> <FrameLayout android:id="@+id/load_more_load_end_view" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="@string/load_end" android:textColor="@android:color/darker_gray"/> </FrameLayout></FrameLayout>
其实就是一个布局,根据根据状态,动态的显示和隐藏某一种容器就行.
总结
这一块,感觉稍微复杂一些,用了2天的琐碎时间才看完,可能是因为比较菜吧.
大体实现思路就是当检测到滑动到RecyclerView的最后倒数N项时,就开始去刷新,并显示加载布局和回调接口,让外部去实现刷新.
道理虽然很简单,但是实现起来的话,有很多很多细节在里面,很多很多的坑,再次感谢开源库的作者们.
- BaseRecyclerViewAdapterHelper源码解读(四) 上拉加载更多
- 上拉加载更多
- 上拉加载更多
- 上拉加载更多
- 上拉加载更多
- BaseRecyclerViewAdapterHelper开源项目之BaseQuickAdapter源码学习上拉加载的实现代码(三)
- BaseRecyclerViewAdapterHelper更改加载更多视图
- listview 上拉加载更多
- ListView上拉加载更多
- ListView上拉加载更多
- ionic 上拉加载更多
- Android上拉加载更多
- android 上拉加载更多
- 上拉加载更多数据
- RecyclerView 上拉加载更多
- vue上拉加载更多
- 简单好用的上拉加载下拉刷新 BaseRecyclerViewAdapterHelper
- BaseRecyclerViewAdapterHelper 上拉加载过程产生的问题
- 【NOIP模拟赛】 思维+离线+递推 数列(好题)
- 运算符及表达式
- java五子棋源码
- markDown语法
- kali 2107.10 搜狗输入法突然消失
- BaseRecyclerViewAdapterHelper源码解读(四) 上拉加载更多
- redis集群原理
- 蟠桃记 --简单递归
- 数组方法实现(七)————数组方法splice()
- 【深入PHP 面向对象】读书笔记(十八)
- devtools
- 剑指offer面试题5——链表之从尾到头打印链表
- (一)切片
- Java 不同方式,不同位置创建String字符串的区别