RecyclerView+SwipeRefreshLayou封装下拉刷新,上拉加载功能
来源:互联网 发布:npoi 导出excel java 编辑:程序博客网 时间:2024/05/16 17:34
随着Android系统的进步,我们不该老有老旧的ListView来封装下拉刷新,上拉加载了,今天记录一下如何用RecyclerView+SwipeRefreshLayou 封装下拉刷新上拉加载。
Android很奇怪。。以前是没有提供瀑布流,刷新控件等。现在是提供了刷新的SwipeRefreshLayou 但又没提供加载更多的功能╮(╯▽╰)╭。总是要劳烦大家自己弄出五花八门的东西来。而且这个SwipeRefreshLayou 和RecyclerView封装下拉刷新还会出现一些问题。比如:
1、手动调用SwipeRefreshLayou 的setRefresh(true)方法,是可以显示刷新动画,但并不会回调onRefresh()方法。
2、假定已经封装好了,写一个方法供外部调用,内部手动调用setRefresh(true)并且手动调用onRefresh()方法,其他情况都好用,但在首次进入页面的时候,会发现刷新动画不显示。
3、我们实现加载更多的思路是:当滚动到最后一个Item的时候,再就可以加载更多了。但是RecyclerView的布局管理器如何获取到这个情况呢?
4、瀑布流不规则,如何在最后一行显示一个充满屏幕宽度的加载更多布局?
先来看一下效果:
用户体验非常的好,这只是简单的演示版本,成品程序更完善。
这里来贴一下主要实现类的代码:
RefreshLoadingRecyclerView.java 封装SwipeRefreshLayout与RecyclerView的类,以及主要逻辑:
/** * Created by Ade-rui on 2016/12/19. */public class RefreshLoadingRecyclerView extends FrameLayout implements SwipeRefreshLayout.OnRefreshListener{ /** * 空闲状态 */ public static final int STATE_IDLE = 1002; /** * 加载更多中 */ public static final int STATE_LOAD_MORE = 1003; /** * 刷新中 */ public static final int STATE_REFRESH = 1004; /** * 刷新器 */ private SwipeRefreshLayout swipeRefreshLayout; /** * recyclerview */ private RecyclerView recyclerView; /** * 刷新与加载的回调监听 */ private OnRefreshLoadingListener listener; /** * 布局管理器 */ private ILayoutManager layoutManager; /** * 适配器 */ private BaseRecyclerAdapter adapter; /** * 当前状态 * 默认为空闲 */ private int mCurrentState = STATE_IDLE; /** * 是否开启加载更多功能 */ private boolean isEnableLoadMore; public RefreshLoadingRecyclerView(Context context) { super(context); init(); } public RefreshLoadingRecyclerView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { LayoutInflater.from(getContext()).inflate(R.layout.layout_recycler_view,this,true); swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout); recyclerView = (RecyclerView) findViewById(R.id.recycler_view); swipeRefreshLayout.setOnRefreshListener(this); //设置recyclerView默认的LayoutManager -- > 相当于普通listview样式 this.layoutManager = new MyLinearLayoutManager(getContext()); recyclerView.setLayoutManager(layoutManager.getLayoutManager()); recyclerView.addOnScrollListener(onScrollListener); } /** * 滚动监听 */ private RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); /** * mCurrentState == STATE_IDLE 因为如果是加载中的话,继续有下拉的动作,还会回调加载 * ,所以必须是空闲状态下才能回调加载。进入加载回调就立刻将状态修改为加载状态,避免再次回聊。 * isEnableLoadMore : 用来手动设置是否开启加载更多的功能 * loadingSecurity() :判断是否可以加载更多,如何实现由具体的布局管理器而定 * dy>0 : 为了返回有时候数据不满一屏,都没有滚动,便发生了加载的情况 */ if(mCurrentState == STATE_IDLE&&isEnableLoadMore && loadingSecurity() && dy > 0){ loadMore(); } } }; /** * 设置是否开启加载更多的功能 * @param isEnableLoadMore */ public void isEnableLoadMore(boolean isEnableLoadMore){ this.isEnableLoadMore = isEnableLoadMore; } /** * 加载更多 */ private void loadMore() { if(listener == null){ return; } //修改状态 mCurrentState = STATE_LOAD_MORE; //禁止swipeRefreshLayout功能 swipeRefreshLayout.setEnabled(false); //通知adapter显示加载更多的布局 adapter.loadMoreChangeState(true); listener.onLoading(); } /** * 完成加载或者刷新 * 由用户调用 */ public void complete(){ if(mCurrentState == STATE_LOAD_MORE){ //通知adapter隐藏加载更多布局 adapter.loadMoreChangeState(false); //回复SwipeRefreshLayout功能 swipeRefreshLayout.setEnabled(true); }else if(mCurrentState == STATE_REFRESH){ //让刷新动画消失 swipeRefreshLayout.setRefreshing(false); } mCurrentState = STATE_IDLE; } /** * 是否可以加载 * 根据具体的LayoutManager实现来判断 * @return */ private boolean loadingSecurity() { return layoutManager.loadMore(); } /** * 设置适配器 * @param adapter */ public void setAdapter(BaseRecyclerAdapter adapter){ this.adapter = adapter; recyclerView.setAdapter(adapter); } /** * 设置布局管理器 * @param layoutManager */ public void setLayoutManager(ILayoutManager layoutManager){ this.layoutManager = layoutManager; recyclerView.setLayoutManager(layoutManager.getLayoutManager()); } /** * 设置刷新加载监听 * @param listener */ public void setOnRefreshLoadingListener(OnRefreshLoadingListener listener){ this.listener = listener; } /** * 刷新 */ public void setRefresh(){ /** * 使用post加入消息队列调用---》 可以防止第一次进入页面的时候, * 虽然执行swipeRefreshLayout.setRefreshing(true),但并不会显示刷新动画的bug */ post(new Runnable() { @Override public void run() { swipeRefreshLayout.setRefreshing(true); onRefresh(); } }); } /** * RereshLayout监听到下拉手势的回调,然后我们在这个回调里面修改自身控件的状态 * 并且回调总结的listener给用户 */ @Override public void onRefresh() { if(listener!=null){ mCurrentState = STATE_REFRESH; Log.i("mydata","onRefresh"); listener.onRefresh(); } } /** * 监听回调 */ public interface OnRefreshLoadingListener{ void onRefresh(); void onLoading(); }}
短短的200行代码便可以实现这个功能了。在XML文件声明以及在Activity中的使用:<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"> <drawapp.cw.com.recyclerviewdemo.RefreshLoadingRecyclerView android:id="@+id/pull_view" android:layout_width="match_parent" android:layout_height="match_parent"></drawapp.cw.com.recyclerviewdemo.RefreshLoadingRecyclerView></RelativeLayout>
Activity:pullView = (RefreshLoadingRecyclerView) findViewById(R.id.pull_view); adapter = new BaseRecyclerAdapter(new ArrayList<String>()); pullView.setAdapter(adapter); pullView.setOnRefreshLoadingListener(new RefreshLoadingRecyclerView.OnRefreshLoadingListener() { @Override public void onRefresh() { refresh(); } @Override public void onLoading() { load(); } }); pullView.isEnableLoadMore(true); pullView.setRefresh();
使用起来与流行的第三方库等没有任何差别。现在来统一总结一下都需要解决哪些问题:1、手动调用SwipeRefreshLayou 的setRefresh(true)方法,是可以显示刷新动画,但并不会回调onRefresh()方法。
解决方法与2是一体的,向外提供一个setRefresh()方法,外部调用的时候,内部同时调用显示动画和onRefresh方法。
2、假定已经封装好了,写一个方法供外部调用,内部手动调用setRefresh(true)并且手动调用onRefresh()方法,其他情况都好用,但在首次进入页面的时候,会发现刷新动画不显示。
在内部使用post加入消息队列中使用,在第一次进入页面就也可以看到刷新动画了
post(new Runnable() { @Override public void run() { swipeRefreshLayout.setRefreshing(true); onRefresh(); } });3、我们实现加载更多的思路是:当滚动到最后一个Item的时候,再就可以加载更多了。但是RecyclerView的布局管理器如何获取到这个情况呢?
LinearLayoutManager和GridLayoutManager的方法://最后一个可见Item的索引 int lastVisiableItemIndex = findLastVisibleItemPosition(); //总共Item的数量 int totalItemIndex = getItemCount(); //这里取<=1的时候返回true表示可以加载更多,也就是说当滑动到倒数第二个Item出现的时候就代表可以加载了
这两个布局管理器都有获取最后一个课件Item的方法,但是StaggeredGridLayoutManager 没有这个方法,用以前方法获取:因为瀑布流一行中不等高,所以从概念上来讲,就是得到一组最后出现的Item索引,我们拿到第一个进行比较就行了,不会差很多的。int[] positions = null; positions = findLastVisibleItemPositions(positions); positions[0];4、瀑布流不规则,GridLayoutManager一列不占全屏幕,那么如何在最后一行显示一个充满屏幕宽度的加载更多布局?
一、GridLayoutManager的解决方案:GrildLayoutManager源码对于类的解释大概有如下:
/** * A {@link RecyclerView.LayoutManager} implementations that lays out items in a grid. * <p> * By default, each item occupies 1 span. You can change it by providing a custom * {@link SpanSizeLookup} instance via {@link #setSpanSizeLookup(SpanSizeLookup)}. */大致意思就是,每一列显示1span单位, 如果你想改变这一列所占的单位span 就调用setSpanSizeLookup方法,那么span是什么?是我们在new GridLayoutManager时传入的第二个参数就是spanCount,几列就传入几。所以重写GrildLayoutManager:/** * Created by lenovo on 2016/12/20. */public class MyGridLayoutManager extends GridLayoutManager implements ILayoutManager { public MyGridLayoutManager(BaseRecyclerAdapter adapter,Context context, int spanCount) { super(context, spanCount); setSpanSizeLookup(new BaseRecyclerAdapter.FooterSpanSize(adapter,this)); } public MyGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) { super(context, spanCount, orientation, reverseLayout); } @Override public boolean loadMore() { //第一个可见Item的索引 int lastVisiableItemIndex = findLastVisibleItemPosition(); //总共Item的数量 int totalItemIndex = getItemCount(); //这里取<=1的时候返回true表示可以加载更多,也就是说当滑动到倒数第二个Item出现的时候就代表可以加载了 //你也可以写成<5 或者 == 0 随便,由自己的体验来决定 return totalItemIndex - lastVisiableItemIndex <= 1; } @Override public RecyclerView.LayoutManager getLayoutManager() { return this; } @Override public SpanSizeLookup getSpanSizeLookup() { return super.getSpanSizeLookup(); }}在构造的时候便调用setSpanSizeLookUp设置我们自定义的lookUpSize:
public static class FooterSpanSize extends GridLayoutManager.SpanSizeLookup { private BaseRecyclerAdapter adapter; private GridLayoutManager layoutManager; public FooterSpanSize(BaseRecyclerAdapter adapter,GridLayoutManager layoutManager){ this.layoutManager = layoutManager; this.adapter = adapter; } /** * 每显示一个Item的时候这个方法就会被回调,并传入item的索引position; * 为什么要持有Adapater的引用? 因为我们需要让Adapter来判断是否是加载更多的布局 * 为什么要持有自身LayoutManager的引用? 因为我们需要知道总共几列 * @param position * @return */ @Override public int getSpanSize(int position) { if(adapter.enableLoadMore(position)){ return layoutManager.getSpanCount(); } return 1; } }
那么StaggeredGridLayoutManager是否也有这样的方法? 答案,没有,那么该如何让加载更多的Item显示整行呢?public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { //如果是最后一个Item并且要加载更多,则只显示加载更多的布局,不进行bind数据操作 if(position == getItemCount() - 1 && isLoadMore){ if(holder.itemView.getLayoutParams() instanceof StaggeredGridLayoutManager.LayoutParams){ StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams(); params.setFullSpan(true); } return; } //这里其实应该写一个BaseViewHolder 调用其中的onBind抽象方法,让其具体的子类去绑定 //不要用强转这种方式,这里为了做例子方便所以强转 ((ContentViewHolder)holder).tvContent.setText(datas.get(position)); }我们只有在onBind的时候来动态判断,此viewHolder的布局的LayoutParams是不是StaggeredGridLayoutManager.LayoutParams 如果是的话就取出来,调用去setFullSpan(true),来让此Item显示一整行。有人说为什么不在ViewHolder初始化的时候就调用,我只想说。。那时候你的View还没有添加进入瀑布流的父View,自身的LayoutParams应该还不是为StaggeredGridLayoutManager.LayoutParams的吧。个人臆测,没有实际测验。
为什么要有ILayoutManager接口?
因为每一个layoutManager判断是否可以加载更多的逻辑不一样,所以必须用接口来编程。
文章中填出的代码示例直接拷贝不能立即使用,想使用请下载源码查看修改自己对应需求来使用就行。
后期我会将此Demo整理成可用项目上传至GitHub
---------------------------------------------------------------
所有的难点都已经解决,代码敲得灵活度并不是很大,不适合开源,但对应每一个想为自己项目封装一套的童鞋来说,这个完全没有问题,覆盖了所有的解决的方案。如果有什么问题,请留言。
源码链接-------http://download.csdn.net/detail/sinat_31311947/9717971
- RecyclerView+SwipeRefreshLayou封装下拉刷新,上拉加载功能
- 封装RecyclerView,实现下拉刷新,上拉加载功能
- 封装RecyclerViewAdapter实现RecyclerView下拉刷新上拉加载更多
- RecyclerView封装--添加下拉刷新和上拉加载更多
- RecyclerView上拉刷新与下拉加载封装
- SwipeRefreshLayout+RecyclerView实现下拉刷新上拉加载功能
- 自定义RecyclerView添加下拉刷新和上拉加载功能
- RecyclerView实现上拉加载,下拉刷新
- RecyclerView 下拉刷新上拉加载更多
- RecyclerView实现上拉加载,下拉刷新
- RecyclerView 下拉刷新上拉加载更多
- RecyclerView的上拉加载,下拉刷新
- RecyclerView 下拉刷新和上拉加载
- RecyclerView下拉刷新上拉加载
- RecyclerView下拉刷新上拉加载
- RecyclerView下拉刷新上拉加载
- RecyclerView 下拉刷新、上拉加载更多
- RecyclerView 下拉刷新上拉加载更多
- SeetaFace人脸检测体验
- python实现树莓派生成并识别二维码
- c 之 ping
- Python中的异常如何处理?
- IDEA中使用MyBatis Generator快速开发
- RecyclerView+SwipeRefreshLayou封装下拉刷新,上拉加载功能
- 一直在用的一篇ctex模板
- JavaScript模块化编程 - CommonJS, AMD 和 RequireJS之间的关系
- POJ 1785 : Binary Search Heap Construction
- STM32跑马灯例程总结
- 我的世界 Unity3D MineCraft 用Unity3D制作类似MineCraft我的世界的游戏 正经梳理一下开发01
- 算法第一弹:时间复杂度和空间复杂度
- VMware下安装Ubuntu遇到VMware Easy Install
- 网络流24题1 飞行员配对方案问题