RecyclerView 大家肯定很熟悉,平常使用的时候也难免会遇到下拉刷新和上拉加载更多,网上相关的控件也是多如牛毛。我特别期待谷歌什么时候能够自己开发一个,可惜一直到现在,也只有一个SwipeRefreshLayout下拉刷新控件,上拉加载连个影子都没。。。

如果不想使用第三方的控件,又想有上拉加载更多功能怎么办呢?于是上网查了相关资料,发现RecyclerView有一个监听方法addOnScrollListener,可以监听到RecyclerView的滑动状态,欣喜若狂,好好研究了一番,并且自己封装了下

Demo 地址
https://github.com/linqinen708/MyDatabindingRecyclerView

使用方式:

首先在xml布局中使用

 <MyRefreshLayoutandroid:id="@+id/refresh_layout"android:layout_width="match_parent"android:layout_height="match_parent"/>

然后在Activity中初始化,大功告成
其中adapter 就是RecyclerView的adapter

private void initRefreshLayout() {mRefreshLayout.setAdapter(mAdapter);mRefreshLayout.setRefreshListener(new MyRefreshLayout.RefreshListener() {@Overridepublic void loadMore() {}@Overridepublic void refresh() {}});}

那么其中的原理是什么呢?
下面简单说一下:

/**最后一个可见的item的索引*/
private int lastVisibleItemPosition;
/*** 是否正在上拉加载更多*/
private boolean isLoadingMore;
***
***
***
private void initLoadMoreListener() {mLayoutManager = new LinearLayoutManager(this);mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {@Overridepublic void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {if (newState == RecyclerView.SCROLL_STATE_IDLE) {//                    LogT.i("停止滑动:");int visibleItemCount = mLayoutManager.getChildCount();int totalItemCount = mLayoutManager.getItemCount();LogT.i("visibleItemCount:" + visibleItemCount + ", totalItemCount:" + totalItemCount + ",lastVisibleItemPosition:" + lastVisibleItemPosition);if (!isLoadingMore && lastVisibleItemPosition == totalItemCount - 1) {isLoadingMore = true;}}}/**仅对LinearLayoutManager 有效,其他LayoutManager未验证*/@Overridepublic void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {lastVisibleItemPosition = mLayoutManager.findLastVisibleItemPosition();}});}

首先需要通过onScrolled方法,获得屏幕中可见的最后一个item的position,然后在onScrollStateChanged方法中,判断RecyclerView是否已经停止滚动,也就是newState == RecyclerView.SCROLL_STATE_IDLE,
然后判断最后一个item的position是否是总数的最后一个lastVisibleItemPosition == totalItemCount - 1,如果是,则说明滑到底部了,整个逻辑就是这么简单粗暴

然后我就自己封装了下RecyclerView,变成了LoadMoreRecyclerView

public class LoadMoreRecyclerView extends RecyclerView {private LinearLayoutManager mLayoutManager;private int lastVisibleItemPosition;/*** 是否可以上拉加载更多* 比如当暂无更多数据之后,就没必要再支持上拉加载更多了* 可以关闭上拉功能*/private boolean isLoadMoreEnable = true;public boolean isLoadMoreEnable() {return isLoadMoreEnable;}public void setLoadMoreEnable(boolean loadMoreEnable) {isLoadMoreEnable = loadMoreEnable;}/*** 是否正在上拉加载更多,* 如果不做额外判断,而用户连续快速上拉,则会出现多次请求*/private boolean isLoadingMore;public boolean isLoadingMore() {return isLoadingMore;}public void completeLoadMore() {isLoadingMore = false;}private LoadMoreListener mLoadMoreListener;public void setLoadMoreListener(LoadMoreListener loadMoreListener) {init();mLoadMoreListener = loadMoreListener;}public interface LoadMoreListener {void loadMore();}public LoadMoreRecyclerView(@NonNull Context context) {super(context);initView();}public LoadMoreRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {super(context, attrs);initView();}public LoadMoreRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initView();}/**如果不取消动画,会导致数据不一致的报错* IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter* */private void initView(){setItemAnimator(null);}private void init() {LogT.i("初始化:" + getLayoutManager());if (getLayoutManager() != null && getLayoutManager() instanceof LinearLayoutManager) {mLayoutManager = (LinearLayoutManager) getLayoutManager();}if (mLayoutManager != null) {addOnScrollListener(new RecyclerView.OnScrollListener() {/*** 当显示的item数量不够多,无法撑满屏幕高度时* 无论是上拉还是下拉,都会触发该方法,* 所以下拉需要满足条件* 否则下拉也会触发加载更多* 所以当 visibleItemCount < totalItemCount 才触发加载更多* 即 item的数量够多,并超过屏幕高度时,才触发加载更多* */@Overridepublic void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {if (isLoadMoreEnable && newState == RecyclerView.SCROLL_STATE_IDLE) {//                    LogT.i("停止滑动:");int visibleItemCount = mLayoutManager.getChildCount();int totalItemCount = mLayoutManager.getItemCount();
//                        LogT.i(":" + getAdapter().getItemViewType(0));
//                        LogT.i("isLoadingMore:"+isLoadingMore + ",visibleItemCount:" + visibleItemCount + ", totalItemCount:" + totalItemCount + ",lastVisibleItemPosition:" + lastVisibleItemPosition);if (!isLoadingMore && visibleItemCount < totalItemCount && lastVisibleItemPosition == totalItemCount - 1) {isLoadingMore = true;if (getAdapter() != null && getAdapter() instanceof BaseBindingAdapter) {LogT.i("加载更多:");((BaseBindingAdapter) getAdapter()).showLoadMore(true);}LogT.i("mLoadMoreListener:" + mLoadMoreListener);if (mLoadMoreListener != null) {mLoadMoreListener.loadMore();}}}}/**仅对LinearLayoutManager 有效,其他LayoutManager未验证*/@Overridepublic void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {//                    LogT.i("dx:" +dx + ", dy:"  + dy);if (isLoadMoreEnable) {lastVisibleItemPosition = mLayoutManager.findLastVisibleItemPosition();}}});}}
}

使用方式:

mRecyclerView.setLoadMoreListener(new LoadMoreRecyclerView.LoadMoreListener() {@Overridepublic void loadMore() {httpRequest();}});

不过这样子只完成了一半,还不是一个完整的控件,于是我自己也封装了一个控件MyRefreshLayout,整个控件非常简单,就是把SwipeRefreshLayout和LoadMoreRecyclerView 整合在一起,这样,就是一个简单的下拉刷新和上拉加载更多控件

public class MyRefreshLayout extends FrameLayout implements SwipeRefreshLayout.OnRefreshListener {private Context mContext;private SwipeRefreshLayout mSwipeRefreshLayout;private LoadMoreRecyclerView mRecyclerView;private BaseBindingAdapter mAdapter;private RefreshListener mRefreshListener;/*** 页数*/private int page = 1;public int getPage() {return page;}public void setPage(int page) {this.page = page;}public void setRefreshListener(RefreshListener refreshListener) {mRefreshListener = refreshListener;mSwipeRefreshLayout.setEnabled(true);mSwipeRefreshLayout.setOnRefreshListener(this);mRecyclerView.setLoadMoreListener(new LoadMoreRecyclerView.LoadMoreListener() {@Overridepublic void loadMore() {page++;if (mRefreshListener != null) {mRefreshListener.loadMore();}}});}public interface RefreshListener {void loadMore();void refresh();}public MyRefreshLayout(@NonNull Context context) {super(context);mContext = context;initView();}public MyRefreshLayout(@NonNull Context context, @Nullable AttributeSet attrs) {super(context, attrs);mContext = context;initView();}public MyRefreshLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);mContext = context;initView();}/*** 初始化控件*/private void initView() {mSwipeRefreshLayout = new SwipeRefreshLayout(mContext);mSwipeRefreshLayout.setEnabled(false);mRecyclerView = new LoadMoreRecyclerView(mContext);mRecyclerView.setLayoutManager(new LinearLayoutManager(mContext));/*因为自定义的LoadMoreListener 需要LinearLayoutManager支持* 所以setLoadMoreListener 需要在setLayoutManager 之后 设置* */mSwipeRefreshLayout.addView(mRecyclerView);addView(mSwipeRefreshLayout);}@Overridepublic void onRefresh() {page = 1;if (mRefreshListener != null) {mRefreshListener.refresh();}}/*** 下拉刷新完成*/public void completeRefresh() {/*如果下拉刷新后,mRecyclerView 自动加载到底部,则让其返回到顶部*/mRecyclerView.scrollToPosition(0);mSwipeRefreshLayout.setRefreshing(false);}/*** 上拉加载更多完成*/public void completeLoadMore() {mRecyclerView.completeLoadMore();}public void complete() {completeLoadMore();completeRefresh();}public void completeHttpRequest(Collection collection) {if (mAdapter == null) {return;}if (page == 1) {mAdapter.getItems().clear();completeRefresh();mAdapter.toggleFootView(collection);setLoadMoreEnable(true);} else {completeLoadMore();setLoadMoreEnable(mAdapter.toggleLoadMore(collection));}}public void setAdapter(BaseBindingAdapter adapter) {mAdapter = adapter;mRecyclerView.setAdapter(adapter);}public void addItemDecoration(@NonNull RecyclerView.ItemDecoration decor) {mRecyclerView.addItemDecoration(decor);}public void setLoadMoreEnable(boolean loadMoreEnable) {mRecyclerView.setLoadMoreEnable(loadMoreEnable);}
}

坑1:
在使用的过程当中,如果用的是notifyItemRangeInserted 或则 notifyItemRangeRemoved 刷新数据,而不是notifyDataSetChanged(),有可能会出错IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter,上网查了很多方法,解决的方式是mRecyclerView.setItemAnimator(null);真是一脸懵逼,加个动画居然还有问题。。。还是RecyclerView默认的自带动画。。。

坑2:
如果mAdapter.getItems().clear()之后,再加入数据,很有可能会发现RecyclerView仍然在底部,所以需要mRecyclerView.scrollToPosition(0); 返回顶部

坑3:
当显示的item数量不够多,无法撑满屏幕高度时无论是上拉还是下拉,都会触发onScrollStateChanged该方法,所以下拉需要满足条件否则下拉也会触发加载更多 所以当 visibleItemCount < totalItemCount 才触发加载更多 即 item的数量够多,并超过屏幕高度时,才触发加载更多

坑4:
如果有人连续上拉加载更多,可能会导致数据错乱,所以加一个boolean值isLoadMoreEnable,当上拉加载更多还没有结束时,不让其继续上拉加载更多

温馨提示:
自己封装的RefreshLayout采用的adapter 是自己封装的BaseBindingAdapter
我自己又额外封装了一个方法
当请求后台数据,获得List后,直接调用completeHttpRequest方法,
就可以动态的实现FooterView 是 “正在加载…” 还是 “暂无更多数据”
非常方便

mRefreshLayout.completeHttpRequest(bean.getRooms());

MVVM模式下RecyclerView与databinding的结合
MVVM模式下RecyclerView与databinding的结合(2)

参考资料 https://blog.csdn.net/weixin_37577039/article/details/79214663

RecyclerView 自带的上拉加载更多相关推荐

  1. Android RecyclerView封装下拉刷新与上拉加载更多

    1 scanlistlibrary 基础组件说明(基于 RecyclerView的封装) 基本数据列表(支持下拉刷新与上拉加载更多) 九宫格数据显示封装(支持下拉刷新与上拉加载更多) 瀑布流数据显示封 ...

  2. android 加载更多动画效果,Android实践之带加载效果的下拉刷新上拉加载更多

    前言 之前写的一个LoadingBar,这次把LoadingBar加到下拉刷新的头部.从头写一个下拉刷新,附赠上拉加载更多.下面话不多说了,来一起看看详细的介绍吧. 效果图: 实现过程 首先是自定义属 ...

  3. Android使用RecyclerView实现上拉加载更多,下拉刷新,分组显示

    项目地址:点击打开链接(https://github.com/MrGaoGang/luckly_recyclerview) 使用RecyclerView封装headerview,footerView, ...

  4. RecyclerView的基础使用 +点击添加列表数据 +下拉刷新、上拉加载更多

    一.RecyclerView的基础使用. 第一步:添加recyclerview控件. 第二步:创建布局文件(xml) + 单独的类控制布局里面的控件(MyViewHolder). 第三步:创建一个适配 ...

  5. RecyclerView实现上拉加载更多的正确姿势

    最近项目上的需求需要实现下拉刷新和上拉加载更多的功能,RecyclerView下拉刷新我相信安卓的同学都会做,无非是利用SwipeRefreshLayout,然后给swipeRefreshLayout ...

  6. Android滑动冲突解决方式(下拉刷新上拉加载更多,适配RecyclerView/ListView/ScrollView)

    一.Android事件的分发机制 这里需要了解下Andorid事件的分发机制.事件分发一般是针对一组事件,即ACTION_DOWN > ACTION_UP 或 ACTION_DOWN > ...

  7. Android——RecyclerView自定义OnScrollListener实现下拉刷新监听,上拉加载更多功能

    目录 [前言] 1.OnScrollListener滑动事件监听抽象类 2.利用onScrollStateChanged及onScrolled方法实现下拉刷新及上拉加载更多

  8. android官方上拉加载,Android-RecycleView上拉加载更多

    5.0之后 推出的RecycleView来代替ListView,可以说RecycleView和ListView比起有过之而无不及,下面这篇博客主要来实现RecyclerView的上拉加载更多功能. 基 ...

  9. android SwipeRefreshLayout 增加上拉加载更多

    2019独角兽企业重金招聘Python工程师标准>>> 大家可能有的没有swipeRefreshLayout这个类,简单说一下,这是v4包里面的,19.1版本的时候就有了,但是当时的 ...

最新文章

  1. 网页中如何获取客户端系统已安装的所有字体?
  2. 禁用Grid上的自动排序功能
  3. debian6 xen4.0安装 guest半虚拟化--debootstrap安装
  4. 位、字,字节与KB的关系
  5. 20145209 2016-2017-2 《Java程序设计》第5周学习总结
  6. 【开源工程】之裸码流提取工具--H264/H265
  7. Angular 5和ASP.NET Core入门
  8. 转载 Log4j2在WEB项目中配置
  9. 基于springboot的贫困帮扶系统
  10. 微信小程序架构图与开发
  11. java加入md5_javamd5加密解密
  12. Windows10系统服务优化及分析(批处理)
  13. axios请求失败,如何获取接口返回的状态码及错误信息
  14. 利用Chrome翻译搞定大部分英文文件翻译的工作流
  15. win10查看无线密码
  16. 计算机的了解以及组装
  17. QIIME 2教程. 03老司机上路指南Experience(2021.2)
  18. Jlink v9仿真器PCB原理图自动升级固件
  19. Redis学习总结和相关资料
  20. Xz1android9打电话延迟,索尼Xperia XZ与XZ1系列正式获得Android 9升级;但这新

热门文章

  1. Android登录界面防劫持提醒处理
  2. 华清远见-重庆中心-JAVA前端JavaScript阶段技术总结
  3. 【网页打不开的解决方法总汇】
  4. 【CV】图像分类中的max pooling和average pooling区别
  5. VMware12 无法识别U盘
  6. Excel 2010 VBA 入门 032 将列进行分组
  7. 关于2010年7月日本语能力测试报名的通知
  8. AI创作——disco diffusion入门使用
  9. shader性能优化总结
  10. Unity之Shader