前言
- 随着RecyclerView的越来越流行,我看着项目里ListView、GridView陷入沉思,是时候开始改变了!(认真脸)我决定将项目中的这些控件都改用RecyclerView。然而,像下拉刷新等功能是必不可少的,虽然有很多现成的可以用,但是,我毅然决定自己动手。
思路
- 下定决心了,那么接下来就是考虑该怎么实现了。
- 由于RecyclerView并没有像ListView一样为我们提供方便的addHeaderView()、addFooterView()方法来添加头布局和脚布局(这也是我们要实现的),所以就不能像ListView一样通过添加头布局、脚布局实现下拉刷新、上拉加载更多功能了。
- 而在RecyclerView里,展示多少条数据,有多少条目,这些,都是由适配器控制的,所以,要想实现以上的功能,就要从适配器入手了。
实现
思路有了,那么接下来就是如何实现了。
首先,自然是新建RLRecyclerView继承RecyclerView,实现构造方法。
-
接着,重写setAdapter()方法,将传递进来的适配器对象保存下来,实际上设置的是封装好的实现以上功能的适配器。
@Override public void setAdapter(Adapter adapter) { // 保存设置的适配器 mAdapter = adapter; // 设置封装的适配器 innerAdapter = new InsideAdapter(); super.setAdapter(innerAdapter); // 注册观察者 mAdapter.registerAdapterDataObserver(mObserver); }
-
至于下面的注册观察者,我们晚点再说,接下来就是这个 InsideAdapter,直接以内部类形式定义在RLRecyclerView中,继承RecyclerView.Adapter
/** * 添加了头布局、脚布局、下拉刷新、上拉加载更多功能的适配器类 */ class InsideAdapter extends Adapter { /** 布局类型-刷新布局 */ private static final int VIEW_TYPE_REFRESH = 0; /** 布局类型-头布局 */ private static final int VIEW_TYPE_HEADER = 1; /** 布局类型-普通布局 */ private static final int VIEW_TYPE_NORMAL = 2; /** 布局类型-脚布局 */ private static final int VIEW_TYPE_FOOTER = 3; /** 布局类型-加载更多布局 */ private static final int VIEW_TYPE_LOADMORE = 4; @Override public int getItemViewType(int position) { // 重写方法,根据下标判断布局类型 if (isRefresh(position)) { return VIEW_TYPE_REFRESH; } else if (isHeader(position)) { return VIEW_TYPE_HEADER; } else if (isFooter(position)) { return VIEW_TYPE_FOOTER; } else if (isLoadMore(position)) { return VIEW_TYPE_LOADMORE; } else { return VIEW_TYPE_NORMAL; } } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { ViewHolder holder; // 根据布局类型,返回不同的ViewHolder对象,SimpleViewHolder不做任何操作 switch (viewType) { case VIEW_TYPE_REFRESH: holder = new SimpleViewHolder(mRefresh); break; case VIEW_TYPE_HEADER: holder = new SimpleViewHolder(mHeaders.get(headerPosition++)); break; case VIEW_TYPE_NORMAL: // 普通布局类型返回设置的Adpter的ViewHolder对象 holder = mAdapter.onCreateViewHolder(parent, viewType); break; case VIEW_TYPE_FOOTER: holder = new SimpleViewHolder(mFooters.get(footerPosition++)); break; case VIEW_TYPE_LOADMORE: holder = new SimpleViewHolder(mLoadMore); break; default: holder = new SimpleViewHolder(null); break; } return holder; } @Override public void onBindViewHolder(ViewHolder holder, int position) { // 刷新布局、加载更多、头布局、脚布局不做处理 if (isRefresh(position) || isLoadMore(position) || isHeader(position) || isFooter(position)) { return; } mAdapter.onBindViewHolder(holder, realPosition(position)); } @Override public int getItemCount() { // 根据功能开启情况以及头布局脚布局返回实际的条目数 if (REFRESH_MODE_BOTH.equals(mode)) { return mAdapter.getItemCount() + mHeaders.size() + mFooters.size() + 2; } else if (REFRESH_MODE_REFRESH.equals(mode) || REFRESH_MODE_LOADMORE.equals(mode)) { return mAdapter.getItemCount() + mHeaders.size() + mFooters.size() + 1; } else { return mAdapter.getItemCount() + mHeaders.size() + mFooters.size(); } } class SimpleViewHolder extends RecyclerView.ViewHolder { SimpleViewHolder(View itemView) { super(itemView); } } }
-
先不说下拉刷新、上拉加载控件需要隐藏,实际运行起来,你会发现,如果使用 LinnerLayoutManager 是没有问题的,但是如果使用的是 GridLayoutManager 或者是 StaggeredGridLayoutManager 你就会发现并没有达到想象中的效果,这是因为我们的代码中实际上只是在设置适配器的时候,添加了几条数据,并没有改变他的展示效果。而 RecyclerView 把布局展示的工作都交给了 LayoutManager,所以这个时候,为了能够实现头布局等能在 GridLayoutManager 和 StaggeredGridLayoutManager 下宽度也能MATCH_PARENT,我们就需要在 InsideAdapter 中重写 onAttachedToRecyclerView 和 onViewAttachedToWindow 两个方法:
@Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); if (manager instanceof GridLayoutManager) { // 如果是Grid布局 final GridLayoutManager gridManager = ((GridLayoutManager) manager); gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { // 这个方法是返回当前对象所在行有几列 return (isRefresh(position) || isLoadMore(position) || isHeader(position) || isFooter(position)) ? gridManager.getSpanCount() : 1; // 如果是刷新、加载更多或头布局、脚布局独占一行,否则按照设置展示 } }); } } @Override public void onViewAttachedToWindow(ViewHolder holder) { ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams && (isRefresh(holder.getLayoutPosition()) || isLoadMore(holder.getLayoutPosition()) || isHeader(holder.getLayoutPosition()) || isFooter(holder.getLayoutPosition()))) { StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp; // 如果是刷新、加载更多或头布局、脚布局独占一行,否则按照设置展示 p.setFullSpan(true); // 设置独占一行 } }
这样,即使布局为 GridLayoutManager 或者 StaggeredGridLayoutManager 刷新、加载更多、头布局、脚布局都是单独的一行了。
接下来要实现的就是下拉刷新、上拉加载更多的功能了,这个实现思路其实和ListView下拉刷新是一致的,同样通过设置刷新布局和加载更多布局的margin值来实现,其核心就是设置触摸事件监听,然后在 onTouch 方法中判断不同的情况,做不同的处理。由于这部分内容比较复杂,笔者就不在这里细说了,有兴趣的朋友可以在文章最后找到Github地址查看源码,源码里都有详细注释。
在前文有说明一段代码后面解释,那就是 mAdapter.registerAdapterDataObserver(mObserver)
-
这是给使用者设置适配器的时候同时给这个适配器注册了一个观察者:
private final RecyclerView.AdapterDataObserver mObserver = new RecyclerView.AdapterDataObserver() { @Override public void onChanged() { innerAdapter.notifyDataSetChanged(); } @Override public void onItemRangeInserted(int positionStart, int itemCount) { innerAdapter.notifyItemRangeInserted(positionStart, itemCount); } @Override public void onItemRangeChanged(int positionStart, int itemCount) { innerAdapter.notifyItemRangeChanged(positionStart, itemCount); } @Override public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { innerAdapter.notifyItemRangeChanged(positionStart, itemCount, payload); } @Override public void onItemRangeRemoved(int positionStart, int itemCount) { innerAdapter.notifyItemRangeRemoved(positionStart, itemCount); } @Override public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { innerAdapter.notifyItemMoved(fromPosition, toPosition); } };
这个观察者所做的就是在使用者调用适配器的notifyDataSetChanged方法时,同步调用InnerAdapter的方法,因为通过setAdapter方法设置的适配器实际上是我们封装的InnerAdapter,所以,当数据变更时,需要调用InnerAdapter的方法才能同步更新界面。
总结
- 说到这,RecyclerView的刷新、加载更多的功能就差不多都实现了,由于文章篇幅原因,很多东西都没有详细的写,大家如果有兴趣的,这里贴上Github地址:RLRecyclerView ,欢迎Star。
- 最后,在这里感谢前辈们的无私分享,项目中借鉴了郭霖大大的 Android下拉刷新完全解析,教你如何一分钟实现下拉刷新功能以及XRecyclerView的部分源码,欢迎大家讨论学习!