微信号:appjiagou

介绍:分享最有价值的APP技术干货文章,做一个有逼格的APP架构师,拒绝平庸,打造最有价值的APP社区!

【实战】封装RecyclerView自动加载更多 & 下拉刷新

2017-11-30 23:31 APP架构师

原文作者:容华谢后

原文地址:http://www.jianshu.com/p/b502c5b59998

特别声明:未经原作者允许请勿转载,转载请联系原作者


1

写在前面


本文主要实现的是上拉加载更多功能,下拉刷新使用的是Google官方的SwipeRefreshLayout控件,因为在实现这个功能的时候走了不少弯路,所以在此记录下来分享给大家,先看下效果图:



github传送门:

https://github.com/alidili/Demos/tree/master/RecyclerViewRefreshDemo


2

实现


上拉加载更多功能实际上就是给RecyclerView增加一个FooterView,然后通过判断是否滑动到了最后一条Item,来控制FooterView的显示和隐藏,接下来我们来看下如何实现:


Adapter添加FooterView


小二,上代码:


public class LoadMoreAdapter 
   extends RecyclerView.Adapter<RecyclerView.ViewHolder>
{
   private List<String> dataList;
   // 普通布局
   private final int TYPE_ITEM = 1;
   // 脚布局
   private final int TYPE_FOOTER = 2;
   // 当前加载状态,默认为加载完成
   private int loadState = 2;
   // 正在加载
   public final int LOADING = 1;
   // 加载完成
   public final int LOADING_COMPLETE = 2;
   // 加载到底
   public final int LOADING_END = 3;
   public LoadMoreAdapter(List<String> dataList) {
       this.dataList = dataList;
   }
   @Override
   public int getItemViewType(int position) {
       // 最后一个item设置为FooterView
       if (position + 1 == getItemCount()) {
           return TYPE_FOOTER;
       } else {
           return TYPE_ITEM;
       }
   }
   @Override
   public RecyclerView.ViewHolder
       onCreateViewHolder(ViewGroup parent, int viewType)
{
       //进行判断显示类型,来创建返回不同的View
       if (viewType == TYPE_ITEM) {
           View view = LayoutInflater.from(parent.getContext())
               .inflate(R.layout.adapter_recyclerview, parent, false);
           return new RecyclerViewHolder(view);
       } else if (viewType == TYPE_FOOTER) {
           View view = LayoutInflater.from(parent.getContext())
               .inflate(R.layout.layout_refresh_footer, parent, false);
           return new FootViewHolder(view);
       }
       return null;
   }
   @Override
   public void onBindViewHolder(ViewHolder holder, int position) {
       if (holder instanceof RecyclerViewHolder) {
           RecyclerViewHolder recyclerViewHolder = (RecyclerViewHolder) holder;
           recyclerViewHolder.tvItem.setText(dataList.get(position));
       } else if (holder instanceof FootViewHolder) {
           FootViewHolder footViewHolder = (FootViewHolder) holder;
           switch (loadState) {
               case LOADING: // 正在加载
                   footViewHolder.pbLoading.setVisibility(View.VISIBLE);
                   footViewHolder.tvLoading.setVisibility(View.VISIBLE);
                   footViewHolder.llEnd.setVisibility(View.GONE);
                   break;
               case LOADING_COMPLETE: // 加载完成
                   footViewHolder.pbLoading.setVisibility(View.INVISIBLE);
                   footViewHolder.tvLoading.setVisibility(View.INVISIBLE);
                   footViewHolder.llEnd.setVisibility(View.GONE);
                   break;
               case LOADING_END: // 加载到底
                   footViewHolder.pbLoading.setVisibility(View.GONE);
                   footViewHolder.tvLoading.setVisibility(View.GONE);
                   footViewHolder.llEnd.setVisibility(View.VISIBLE);
                   break;
               default:
                   break;
           }
       }
   }
   @Override
   public int getItemCount() {
       return dataList.size() + 1;
   }
   /**
    * 设置上拉加载状态
    *
    * @param loadState 0.正在加载 1.加载完成 2.加载到底
    */

   public void setLoadState(int loadState) {
       this.loadState = loadState;
       notifyDataSetChanged();
   }    
}


ViewHolder类:


private class RecyclerViewHolder extends RecyclerView.ViewHolder {
   TextView tvItem;
   RecyclerViewHolder(View itemView) {
       super(itemView);
       tvItem = (TextView) itemView.findViewById(R.id.tv_item);
   }
}
private class FootViewHolder extends RecyclerView.ViewHolder {
   ProgressBar pbLoading;
   TextView tvLoading;
   LinearLayout llEnd;
   FootViewHolder(View itemView) {
       super(itemView);
       pbLoading = (ProgressBar) itemView.findViewById(R.id.pb_loading);
       tvLoading = (TextView) itemView.findViewById(R.id.tv_loading);
       llEnd = (LinearLayout) itemView.findViewById(R.id.ll_end);
   }
}


首先定义了布局和数据加载状态的一些标志,然后在getItemViewType方法中设置最后一个Item为FooterView,在onCreateViewHolder方法中根据viewType来加载不同的布局,最后在onBindViewHolder方法中设置一下加载的状态显示就OK了,对了,由于多了一个FooterView,所以要记得在getItemCount方法的返回值中加上1。


到这里一个线性布局列表的Adapter就完成了,注意,是线性布局列表(只有一列的那种),那网格布局怎么办,先看下这个Adapter在网格布局中使用会发生什么:




可以看到加载更多的进度条显示在了一个Item上,如果想要正常显示的话,进度条需要横跨两个Item,这该怎么办呢,别担心,继续往下看:


@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
   super.onAttachedToRecyclerView(recyclerView);
   RecyclerView.LayoutManager manager
               = recyclerView.getLayoutManager();
   if (manager instanceof GridLayoutManager) {
       final GridLayoutManager gridManager = ((GridLayoutManager) manager);
       gridManager.setSpanSizeLookup(
           new GridLayoutManager.SpanSizeLookup() {
               @Override
               public int getSpanSize(int position) {
                   // 如果当前是footer的位置,
                   // 那么该item占据2个单元格,正常情况下占据1个单元格
                   return getItemViewType(position) == TYPE_FOOTER ?
                               gridManager.getSpanCount() : 1;
               }
       });
   }
}


在Adapter中重写onAttachedToRecyclerView方法,首先判断当前是否为网格布局,然后给GridLayoutManager设置一个SpanSizeLookup,这是一个抽象类,里面有一个抽象方法getSpanSize,这个方法的返回值决定了每个Item占据的单元格数。


以上文为例,是一个两列的网格布局,如果当前Item是FooterView的话需要占据两个单元格才能横向充满屏幕,所以需要返回2(GridLayoutManager的getSpanCount方法获取到的是当前一行中单元格的数量),正常情况下每个Item占据一个单元格。


RecyclerView设置滑动监听


设置好FooterView之后,我们还需要判断一下什么时候显示出来,这就需要对RecyclerView设置一下滑动监听,当滑动到最后一个Item的时候,显示加载更多UI并且开始请求下一页列表的数据,看下代码:


public abstract class EndlessRecyclerOnScrollListener 
                   extends RecyclerView.OnScrollListener
{
   //用来标记是否正在向上滑动
   private boolean isSlidingUpward = false;
   @Override
   public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
       super.onScrollStateChanged(recyclerView, newState);
       LinearLayoutManager manager =
               (LinearLayoutManager) recyclerView.getLayoutManager();
       // 当不滑动时
       if (newState == RecyclerView.SCROLL_STATE_IDLE) {
           //获取最后一个完全显示的itemPosition
           int lastItemPosition = manager.findLastCompletelyVisibleItemPosition();
           int itemCount = manager.getItemCount();
           // 判断是否滑动到了最后一个item,并且是向上滑动
           if (lastItemPosition == (itemCount - 1) && isSlidingUpward) {
               //加载更多
               onLoadMore();
           }
       }
   }
   @Override
   public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
       super.onScrolled(recyclerView, dx, dy);
       // 大于0表示正在向上滑动,小于等于0表示停止或向下滑动
       isSlidingUpward = dy > 0;
   }
   /**
    * 加载更多回调
    */

   public abstract void onLoadMore();
}


代码中已经写了很全的注释,重点看下onScrolled这个回调方法,里面有dx、dy这两个参数,当向上滑动的时候dy是大于0的,向左滑动的时候dx是大于0的,反方向滑动则小于0,所以这段代码稍稍修改一下就可以适用于横向滑动列表的监听。


在Activity中使用


准备工作已经完成了,接下来看看如何使用吧:


public class LoadMoreActivity extends AppCompatActivity {
   private RecyclerView recyclerView;
   private LoadMoreAdapter loadMoreAdapter;
   private List<String> dataList = new ArrayList<>();
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_recyclerview);
       init();
   }
   private void init() {
       recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
       // 模拟获取数据
       getData();
       loadMoreAdapter = new LoadMoreAdapter(dataList);
       recyclerView.setLayoutManager(new LinearLayoutManager(this));
       recyclerView.setAdapter(loadMoreAdapter);
       // 设置加载更多监听
       recyclerView.addOnScrollListener(new EndlessRecyclerOnScrollListener() {
           @Override
           public void onLoadMore() {
               loadMoreAdapter.setLoadState(loadMoreAdapter.LOADING);
               if (dataList.size() < 52) {
                   // 模拟获取网络数据,延时1s
                   new Timer().schedule(new TimerTask() {
                       @Override
                       public void run() {
                           runOnUiThread(new Runnable() {
                               @Override
                               public void run() {
                                   getData();
                                   loadMoreAdapter.setLoadState(
                                       loadMoreAdapter.LOADING_COMPLETE);
                               }
                           });
                       }
                   }, 1000);
               } else {
                   // 显示加载到底的提示
                   loadMoreAdapter.setLoadState(loadMoreAdapter.LOADING_END);
               }
           }
       });
   }
   private void getData() {
       char letter = 'A';
       for (int i = 0; i < 26; i++) {
           dataList.add(String.valueOf(letter));
           letter++;
       }
   }
}


调用RecyclerView的addOnScrollListener方法设置一下加载更多监听,在onLoadMore回调方法中,首先显示正在加载进度UI,然后模拟获取网络数据,完成之后隐藏加载进度UI,加载完两页数据之后显示到底了的提示。


3

封装


到这里,我们已经完成了RecyclerView的上拉加载更多功能,但是大部分的逻辑都写在了Adapter中,这样每写一个Adapter都要写一遍加载逻辑,这是很不优雅的,接下来我们对加载更多功能做一个封装,使其和Adapter完全解构,看下代码:

public class LoadMoreWrapper 
   extends RecyclerView.Adapter<RecyclerView.ViewHolder>
{
   private RecyclerView.Adapter adapter;
   // 普通布局
   private final int TYPE_ITEM = 1;
   // 脚布局
   private final int TYPE_FOOTER = 2;
   // 当前加载状态,默认为加载完成
   private int loadState = 2;
   // 正在加载
   public final int LOADING = 1;
   // 加载完成
   public final int LOADING_COMPLETE = 2;
   // 加载到底
   public final int LOADING_END = 3;
   public LoadMoreWrapper(RecyclerView.Adapter adapter) {
       this.adapter = adapter;
   }
   @Override
   public int getItemViewType(int position) {
       // 最后一个item设置为FooterView
       if (position + 1 == getItemCount()) {
           return TYPE_FOOTER;
       } else {
           return TYPE_ITEM;
       }
   }
   @Override
   public RecyclerView.ViewHolder
       onCreateViewHolder(ViewGroup parent, int viewType)
{
       //进行判断显示类型,来创建返回不同的View
       if (viewType == TYPE_FOOTER) {
           View view = LayoutInflater.from(parent.getContext())
                   .inflate(R.layout.layout_refresh_footer, parent, false);
           return new FootViewHolder(view);
       } else {
           return adapter.onCreateViewHolder(parent, viewType);
       }
   }
   @Override
   public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
       if (holder instanceof FootViewHolder) {
           FootViewHolder footViewHolder = (FootViewHolder) holder;
           switch (loadState) {
               case LOADING: // 正在加载
                   footViewHolder.pbLoading.setVisibility(View.VISIBLE);
                   footViewHolder.tvLoading.setVisibility(View.VISIBLE);
                   footViewHolder.llEnd.setVisibility(View.GONE);
                   break;
               case LOADING_COMPLETE: // 加载完成
                   footViewHolder.pbLoading.setVisibility(View.INVISIBLE);
                   footViewHolder.tvLoading.setVisibility(View.INVISIBLE);
                   footViewHolder.llEnd.setVisibility(View.GONE);
                   break;
               case LOADING_END: // 加载到底
                   footViewHolder.pbLoading.setVisibility(View.GONE);
                   footViewHolder.tvLoading.setVisibility(View.GONE);
                   footViewHolder.llEnd.setVisibility(View.VISIBLE);
                   break;
               default:
                   break;
           }
       } else {
           adapter.onBindViewHolder(holder, position);
       }
   }
   @Override
   public int getItemCount() {
       return adapter.getItemCount() + 1;
   }
   @Override
   public void onAttachedToRecyclerView(RecyclerView recyclerView) {
       super.onAttachedToRecyclerView(recyclerView);
       RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
       if (manager instanceof GridLayoutManager) {
           final GridLayoutManager gridManager = ((GridLayoutManager) manager);
           gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
               @Override
               public int getSpanSize(int position) {
                   // 如果当前是footer的位置,那么该item占据2个单元格,正常情况下占据1个单元格
                   return getItemViewType(position) == TYPE_FOOTER ?
                                               gridManager.getSpanCount() : 1;
               }
           });
       }
   }
   private class FootViewHolder extends RecyclerView.ViewHolder {
       ProgressBar pbLoading;
       TextView tvLoading;
       LinearLayout llEnd;
       FootViewHolder(View itemView) {
           super(itemView);
           pbLoading = (ProgressBar) itemView.findViewById(R.id.pb_loading);
           tvLoading = (TextView) itemView.findViewById(R.id.tv_loading);
           llEnd = (LinearLayout) itemView.findViewById(R.id.ll_end);
       }
   }
   /**
    * 设置上拉加载状态
    *
    * @param loadState 0.正在加载 1.加载完成 2.加载到底
    */

   public void setLoadState(int loadState) {
       this.loadState = loadState;
       notifyDataSetChanged();
   }
}


乍一看好像和上文中的LoadMoreAdapter没什么区别,都是继承了RecyclerView.Adapter并实现了其中的一些方法,但是仔细看会发现,构造方法中的参数变成了RecyclerView.Adapter,在LoadMoreWrapper中我们只处理加载更多功能相关的逻辑,其他逻辑交由Adapter本身处理,相当于扩展了Adapter的一些功能,嗯,这种方式还有一个学名,叫【装饰者模式】。


封装之后,Adapter中的代码变成了这样:


public class LoadMoreWrapperAdapter 
       extends RecyclerView.Adapter<RecyclerView.ViewHolder>
{
   private List<String> dataList;
   public LoadMoreWrapperAdapter(List<String> dataList) {
       this.dataList = dataList;
   }
   @Override
   public RecyclerView.ViewHolder
           onCreateViewHolder(ViewGroup parent, int viewType)
{
       View view = LayoutInflater.from(parent.getContext())
               .inflate(R.layout.adapter_recyclerview, parent, false);
       return new RecyclerViewHolder(view);
   }
   @Override
   public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
       RecyclerViewHolder recyclerViewHolder = (RecyclerViewHolder) holder;
       recyclerViewHolder.tvItem.setText(dataList.get(position));
   }
   @Override
   public int getItemCount() {
       return dataList.size();
   }
   private class RecyclerViewHolder extends RecyclerView.ViewHolder {
       TextView tvItem;
       RecyclerViewHolder(View itemView) {
           super(itemView);
           tvItem = (TextView) itemView.findViewById(R.id.tv_item);
       }
   }
}


瞬间减少了一大半,使用起来也很简单,在原有Adapter的基础上包上一层就可以了:


LoadMoreWrapperAdapter loadMoreWrapperAdapter 
       = new LoadMoreWrapperAdapter(dataList);
LoadMoreWrapper loadMoreWrapper
       = new LoadMoreWrapper(loadMoreWrapperAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(loadMoreWrapper);


源码已经上传到GitHub上了,欢迎Fork,觉得还不错就Start一下吧!


GitHub传送门

https://github.com/alidili/Demos/tree/master/RecyclerViewRefreshDemo

点我下载本文Demo的Apk

https://github.com/alidili/Demos/raw/master/RecyclerViewRefreshDemo/RecyclerViewRefreshDemo.apk


如果你有好的文章想和大家分享欢迎投稿,直接向我投递文章链接即可。



 
APP架构师 更多文章 【高级框架】Android全面插件化RePlugin流程与源码解析 良心推荐:总结 Android 开发中必备的代码 Review 清单 安卓高级进阶视频分享 【博文精选】基于Android Architecture Components的应用架构指南 【福利】3980元的Hadoop大数据视频教程限量领取!!!
猜您喜欢 8分钟掌握Linux内核分析的核心科技 SSD性能测试之“砖” 机器学习再次升级,实战大神为你开路! 平安金融壹账通测试技术周报(第三十七期) golang实现Raft(一):选主