RecyclerView的那些事

RecyclerView是什么

RecyclerView是V7兼容包下的一个列表布局,可以实现复杂的列表,我们经常拿他与ListView做比较,相较于原来的列表布局实现,RecyclerView提供的方法更加开放,给用户提供了更多的可选择实现方式。
在动画和加载效率方面做了更多优化。

使用RecyclerView 有什么优势?

1.自定义LayoutManager

RecyclcerView不止支持实现ListView结构,也支持实现GridView结构,甚至以前用ListView,GridView实现的瀑布流和一些复杂的样式都能用RecyclcerView来实现。

RecyclerView默认实现了三种LayoutManager(LinearLayoutManager,GridLayoutManager,StaggeredGridLayoutManager)分别用来实现ListView,GridView和瀑布流的效果,如果这些布局效果不满足你的应用,那你可以自己重写LayoutManager写一个你喜欢的样式。

CustomLayoutManage2
CustomLayoutManage2

2.自定义ItemDecoration(分割线)

ListView的实现支持我们设置一个自定义的Drawable,来做分割线。

RecyclerView开放了一个ItemDecoration类,我们可以自己实现这个类来做出花哨的装饰样式,包括分割线。

以前GridView实现不了分割线,RecyclerView可以!!

RecyclerView提供了自带的分割线实现类DividerItemDecoration。
CustomLayoutManage2

3.自定义ItemTouchHelper

RecyclerView支持自己实现ItemTouchHelper,用来做Item的一些移动等手势操作。
MainTouchHelper

4.Adapter中获取item的类型

RecyclerView支持实现getItemViewType根据不同的数据或者某些条件返回不同的itemType,而在Inflate的时候用户可以根据不同的itemType inflate不同的布局。

5.列表局部刷新

RecyclerView提供了局部刷新的接口,通过局部刷新,就能避免调用许多无用的bindView.
rv

(RecyclerView和ListView添加,移除Item效果对比)

结合RecyclerView的缓存机制,看看局部刷新是如何实现的:
以RecyclerView中notifyItemRemoved(1)为例,最终会调用requestLayout(),使整个RecyclerView重新绘制,过程为:
onMeasure()–>onLayout()–>onDraw()

其中,onLayout()为重点,分为三步:

  1. dispathLayoutStep1():记录RecyclerView刷新前列表项ItemView的各种信息,如Top,Left,Bottom,Right,用于动画的相关计算;
  2. dispathLayoutStep2():真正测量布局大小,位置,核心函数为layoutChildren();
  3. dispathLayoutStep3():计算布局前后各个ItemView的状态,如Remove,Add,Move,Update等,如有必要执行相应的动画.

其中,layoutChildren()流程图:
s

s
当调用notifyItemRemoved时,会对屏幕内ItemView做预处理,修改ItemView相应的pos以及flag(流程图中红色部分):
s

当调用fill()中RecyclerView.getViewForPosition(pos)时,RecyclerView通过对pos和flag的预处理,使得bindview只调用一次.

需要指出,ListView和RecyclerView最大的区别在于数据源改变时的缓存的处理逻辑,ListView是”一锅端”,将所有的mActiveViews都移入了二级缓存mScrapViews,而RecyclerView则是更加灵活地对每个View修改标志位,区分是否重新bindView。

6.View缓存

缓存层级

RecyclerView比ListView多两级缓存,支持多个离ItemView缓存,支持开发者自定义缓存处理逻辑,支持所有RecyclerView共用同一个RecyclerViewPool(缓存池)。

具体来说:
ListView(两级缓存):
ListView
RecyclerView(四级缓存):
RecyclerView

ListView和RecyclerView缓存机制基本一致:

1). mActiveViews和mAttachedScrap功能相似,意义在于快速重用屏幕上可见的列表项ItemView,而不需要重新createView和bindView;

2). mScrapView和mCachedViews + mReyclerViewPool功能相似,意义在于缓存离开屏幕的ItemView,目的是让即将进入屏幕的ItemView重用.

3). RecyclerView的优势在于a.mCacheViews的使用,可以做到屏幕外的列表项ItemView进入屏幕内时也无须bindView快速重用;b.mRecyclerPool可以供多个RecyclerView共同使用,在特定场景下,如viewpaper+多个列表页下有优势.客观来说,RecyclerView在特定场景下对ListView的缓存机制做了补强和完善。

缓存方式

1). RecyclerView缓存RecyclerView.ViewHolder,抽象可理解为:
View + ViewHolder(避免每次createView时调用findViewById) + flag(标识状态);
2). ListView缓存View。

缓存不同,二者在缓存的使用上也略有差别,具体来说:
ListView获取缓存的流程:
listview缓存
RecyclerView获取缓存的流程:
recyclerView缓存

1). RecyclerView中mCacheViews(屏幕外)获取缓存时,是通过匹配pos获取目标位置的缓存,这样做的好处是,当数据源数据不变的情况下,无须重新bindView:
ss
而同样是离屏缓存,ListView从mScrapViews根据pos获取相应的缓存,但是并没有直接使用,而是重新getView(即必定会重新bindView)

2). ListView中通过pos获取的是view,即pos–>view;
RecyclerView中通过pos获取的是viewholder,即pos –> (view,viewHolder,flag);
从流程图中可以看出,标志flag的作用是判断view是否需要重新bindView,这也是RecyclerView实现局部刷新的一个核心.

只用一个RecyclerView如何实现复杂布局

有个需求是这样的,我们需要做一个图片列表的展示但是不同的图片我要给他不一样的权重来让他显示不同的占比。比如下图。

imageRecyclerView

如果页面结构比较简单的情况下(默认顶部区域和最底部区域是一样的)可能会把中间那两块区域做成一个特殊的ViewType.只有中间的八个小方格和两个大方格是特殊区域,这个时候就可以把八个小方格做一个特殊的ViewType,两个小方格做一个特殊的ViewType.加载进去。

如果所有的方格都是列表中的一个项,这边有可能需要解决数据源的问题。

但其实这整个布局用一个GridLayoutManager能够一次实现。

这都要归功于GridLayoutManager提供的一个方法setSpanSizeLookup,它提供了一个SpanSizeLookup的抽象,我们可以实现它的getSpanSize方法。
有这个方法,我们可以把不同大小的布局设置不同的ViewType,getSpanSize给不同的ViewType设置不同的占位。最终达到一个RecylcerView实现复杂布局的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
switch (displayAdapter.getItemViewType(position)) {
case 1:
return 4;
case 2:
return 1;
case 3:
return 2;
default:
return 4;
}
}
});

GridLayoutManager其实就是LayoutManager的一个实现,从这个方法可以看出只要我们掌握了自定义的LayoutManager,整个RecyclerView想长什么样子就都掌握在你的手中了。

实现效果

demo地址:https://github.com/guoxiaolongonly/RecyclerViewDemo

参考:

Android ListView与RecyclerView对比浅析–缓存机制:https://blog.csdn.net/tencent_bugly/article/details/52981210