一、概述
对于RecyclerView的学习,主要是需要掌握以下几点:
- 数据:
Adapter - 使用:RecyclerView - Adapter
- 进阶:BaseRecyclerViewAdapterHelper
- 布局:
LayoutManager - 使用:RecyclerView - LayoutManager
- 进阶:自定义
- 动画:
ItemAnimator - 使用
- 进阶:RecyclerViewItemAnimators
- 装饰:
ItemDecorator - 使用:RecyclerView - ItemDecoration
- 手势:
ItemTouchHelper - 使用:RecyclerView - ItemTouchHelper
要理解整个RecyclerView的思想,有一个视频是一定要看的:RecyclerView ins and outs - Google I/O 2016。今天,我们就通过这个视频,把上面所学到的东西串联起来。
二、为什么要使用RecyclerView
RecyclerView诞生的目的就是为了替代ListView,我们先总结一下在使用ListView过程当中所遇到的问题:
- 复用
Item需要编写很多的代码
在使用ListView的时候,有经验的程序员一定会告诉你在getView中要这么写,如果忘了,那么会产生很严重的性能问题。
if (convertView == null) {
//通过LayoutInflator生成convertView,并产生一个ViewHolder,通过setTag关联起来.
} else {
//通过getTag获取ViewHolder,进行更新操作.
}
- 焦点冲突问题
当Item有焦点时,Item的子控件就无法获取到焦点;而如果子控件抢夺了焦点,那么Item的点击事件又不能响应,这个相信大家都遇到过。 - 重复的
API
ListView中提供了很多的API,但是这些API又和View的一些API重复了,例如我们可以给ListView设置setOnItemClickListener,也可以在getView中给某个View设置setOnClickListener,这就让人很疑惑,到底应当选用哪个。 - 动画
当我们需要在ListView中进行添加、删除、移动等操作的时候,如果希望加上动画,那么是很困难的,根本原因是我们是通过Adapter通知ListView进行更新,然而ListView根本就没法确定到底是哪些View发生了变化。 - 更加复杂的布局需求
ListView在布局是规整的列表的时候能满足大多数人的使用,然而如果想要实现像瀑布流这种复杂的布局,并且保证View能够复用,那么需要编写很多的代码。
如果之前有了解过RecyclerView的基本用法,那么你会发现,对于上述这些问题,它都给出了自己的解决方案:
- 强制使用开发者使用
ViewHolder,提供了onCreateViewHolder和onBindViewHolder这两个方法,把创建View和绑定View的操作分离开。 - 把焦点交给系统处理。
- 去掉了
onItemClickListener,以及一些重复的API。 - 在
Adapter中增加了notifyItemChanged()等方法,让我们可以指定变化的类型和范围,并且提供了setItemAnimator()方法,让开发者能够方便地定义添加、删除、移动的动画。 - 把布局的工作抽象出来,放到了
LayoutManager当中,并预制了瀑布流布局。
了解了这些,我们就能知道RecyclerView能帮我们解决什么问题,也就能更好地理解它为什么要这么设计,下面就开始进入真正的RecyclerView的学习。
三、RecyclerView架构

整个
RecyclerView体系包含三大组件:
-
LayoutManager:position the view -
ItemAnimator:animate the view -
Adapter:provide the view
这三大组件各司其职,而RecyclerView负责管理,就组成了整个RecyclerView的架构。
3.1 LayoutManager
LayoutManager需要负责以下几部分的工作:
-
Position
它负责View的摆放,可以是线性、宫格、瀑布流式或者任意类型,而RecyclerView不知道也不关心这些,这是LayoutManager的职责。 -
Scroll
对于滚动事件的处理,RecyclerView负责接收事件,但是最终还是由LayoutManager进行处理滚动后的逻辑,因为只有它在知道View具体摆放的位置。 -
Focus traversal
当焦点转移导致需要一个新的Item出现在可视区域中时,也是由LayoutManager处理的。
3.2 Adapter
Adapter需要负责以下几部分的工作:
- 创建
View和ViewHolder,后者作为整个复用机制的跟踪单元。 - 把具体位置的
Item和ViewHolder进行绑定,并存储相关的信息。 - 通知
RecyclerView数据变化,支持局部的更新,在提高效率的同时也有效地支持了动画。 -
Item点击事件的处理。 - 多类型布局的支持。
四、ViewHolder的生命周期
4.1 LayoutManager请求RecyclerView提供指定position的View
ViewHolder是和View相绑定的,同时它也是整个复用框架的跟踪单元。在RecyclerView体系中,对ViewHolder采用了二级缓存,分为Cache和Recycled Pool,当LayoutManager向RecyclerView请求位于某个Position的View时,Recycled View会先去Cache中寻找,如果找到,那么直接返回;如果找不到,那么再去Recycled Pool中寻找,下面就是整个寻找过程的几种情况:
- 命中
Cache
这种情况下,不会调用Adapter的onCreateViewHolder或者onBindViewHolder方法:
-
Cache不存在,Recycled Pool也不存在
这种情况下,会调用Adapter的onCreateViewHolder方法,让它提供一个对应viewType的ViewHolder,我们在其中建立ViewHolder和View之间的关联。
-
Cache不存在,Recycled Pool存在
这种情况下,会回调Adapter的onBindViewHolder方法,我们在其中使用当前的数据集合来更新ViewHolder所绑定的itemView的状态。
4.2 LayoutManager找到对应位置的View
LayoutManager通过addView方法把之前找到的View添加进RecyclerView,RecyclerView通过onViewAttachToWindow(VH viewHolder)方法,通知Adapter这个viewHolder所关联的itemView已经被添加到了布局当中,

4.3 LayoutManager请求RecyclerView移除某一个位置的View
4.3.1 普通情况
当LayoutManager发现不再需要某一个position的View时,它会通知RecyclerView,RecyclerView通过onViewDetachFromWindow(VH viewHolder)通知Adapter和它绑定的itemView被移出了。同时,RecyclerView判断它是否能够被缓存,假设能够被缓存,那么它会先被放到Cache当中,在Cache中又会判断它内部是否有需要转移到Recycled Pool中的ViewHolder,在放入之后回收池后,通过onViewRecycled(VH viewHolder)方法通知Adapter它被回收了。

4.3.2 特殊情况
在上面的普通的情况中,onViewDetachFromWindow(VH viewHolder)是立即被回调的。然而在实际当中,由于我们需要对View的添加、删除做一些过度动画,这时候,我们需要等待ItemAnimator进行完动画操作之后,才做detach和recycle的逻辑,这一过程对于LayoutManager是不可见的。

4.4 ViewHolder的销毁
在一般情况下,我们不会去销毁ViewHolder,而是把它放入到缓存当中,除非出现以下两种情况。
4.4.1 ViewHolder所绑定的itemView当前状态异常
在放入Recycled Pool时,会去检查itemView的状态是否正常。这一操作的目的主要是为了避免出现诸如此类的情况:当前itemView正在执行动画,此时它可能呈现半透明的状态,如果此时把它放入到回收池中,那么当另一个位置的position需要复用它时就可能会出现问题。
当出现上面的情况后,Recycled Pool会先通过Adapter的onFailedToRecycled(VH viewHolder)告诉它我们现在出现了异常的情况,由Adapter的实现者通过返回值来决定是否仍然要把它放入到Recycled Pool,默认是返回false,也就是不放入,那么这个ViewHolder就会被销毁了。

4.4.2 Recycled Pool中已经没有足够的空间
Recycled Pool的空间并不是无限大的,因此,如果没有足够的空间存放要被回收的ViewHolder,那么它也会被销毁。

造成这种情况的一般是动画引起的,例如,我们调用了
notifyItemRangeChanged(0, getItemCount())方法,这时候为了进行渐出渐进的动画,那么我们就需要创建两倍的ViewHolder,出现这种情况时一般有两种解决方法:
- 只通知具体发生变化的
Item - 通过
pool.setMaxRecycledViews(type, count)改变回收池的大小。
五、ItemAnimator
对于Item的动画,主要有以下几种情况:
- 添加:
Fade In - 删除:
Fade Out - 移动:
Translate - 更新:
Cross Fade
RecyclerView对于动画的处理采用了Predictive的方式,除了当前已经在RecyclerView布局中的View(实线框部分),它还需要知道在屏幕意外的信息(虚线框部分),这样在H被删除的时候,它才能够对J-K进行上移动画,并把原来不在屏幕内的L上移到可视范围之内。

六、ChildHelper和AdapterHelper
6.1 ChildHelper
对于ChildHelper的作用是:Provide a virtual children list to layoutmanager,下面我们就首先看一下为什么需要它。
6.1.1 解决什么问题
我们看下面这种情况,假如LayoutManager想要移除一个View,而ItemAnimator又希望给这一移除的操作增加一个动画,那么这时候就会产生冲突,到底应该怎么办,为此,RecyclerView通过ChildHelper来把它们隔离开。

6.1.2 解决问题的方法
当RecyclerView收到LayoutManager要求改变布局的请求时,它并不是直接去更改ViewGroup,而是让ChildHelper和ItemAnimator去协调,并由它来操作ViewGroup。

最明显的例子是,假如我们当前列表中状态为
0,1,2,3,此时我们移除了position=0的Item,这时候假如删除的动画还没有完成,那么LayoutManager和RecyclerView的getChildAt(0)返回值将会不同,因为在LayoutManager并不清楚ChildHelper的存在,在它看来,position=0的Item已经被移除了。
layoutManager.getChildAt(0); //return 1;
recyclerView.getChildAt(0); //return 0;
6.2 AdapterHelper
而AdapterHelper所解决的问题和ChildHelper类似,ChildHelper是处理View的,而AdapterHelper用来跟踪ViewHolder的,其作用为:
Tracks ViewHolder positionsVirtual Adapter for LayoutManager
说起来可能比较抽象,我们用下面这种图理解一下,当我们移动某个Item并且它的onLayout方法还没有完成,那么Adapter和Layout的postion是不相同的:

七、ItemDecoration
ItemDecoration用来在RecyclerView的Canvas上进行额外的绘制操作,我们不仅可以在单个Item(例如给每个Item添加分割线)的Canvas上进行绘制,也可以在整个RecyclerView的Canvas上进行绘制,此外,我们还可以指定Item之间的间隔:
Custom Drawing on RecyclerViews CanvasAdd offset to View boundsHave multiple ItemDecoration
需要注意的点:
Do not try to access to adapterKeep necessary information in viewHolderGeneral onDraw rules applyrecyclerView.getChildViewHolder(View view)
参考文章:Android RecyclerView 使用完全解析 体验艺术般的控件。
八、RecycledViewPool
RecyclerViewPool用来缓存那些回收的View,这些缓存不仅可以提供给单个RecyclerView使用,还可以提供和别的自定义控件共享。
Sanctuary for reserve ViewHoldersCan be shared between RecyclerViews or Custom ViewGroupsPerActivity Context
九、ItemTouchHelper
之前使用ListView的时候,如果需要支持侧滑删除、拖动排序这种操作,那么我们一般用引入一些开源库,现在RecyclerView已经帮我们提供了实现的接口,通过重写ItemTouchHelper的方法,就可以实现上面提到的那些操作。
Drag & DropSwipe to dismiss
参考文章:RecyclerView 进阶:使用 ItemTouchHelper 实现拖拽和侧滑删除
十、Tips
-
onBind Position != final,use holder.getAdapterPostion()
如果我们像下面这样,在onBindViewHolder中绑定了监听:
public void onBindViewHolder(final ViewHolder, final int position) {
holder.itemView.setOnClickListener(new View.onClickListener) {
@Override
public void onClick(View view) {
removeAtPostion(position);
}
}
}
由于Item会被添加、删除、移动,因此,我们在onBindViewHolder中获得位置,并不一定是当前的位置,例如像下面这样:
onBindViewHolder(holder, 5);
notifyItemMoved(5, 15);
holder.itemView.callOnClick();
那么就会得到错误的位置,这时候应当使用holder.getAdapterPostion()来保证能够得到预期的结果。
-
Payloads
通过onBindViewHolder中的List payloads,我们可以指定在bind的时候只更新某一部分的信息,而不是全部更新。 -
onCreate means create
在onCreateViewHolder中,始终应当返回一个新的ViewHolder,而不是返回一个缓存的ViewHolder。 -
Adapter position and Layout position
就像我们前面在AdapterHelper中讨论的那样,在某些时刻,Adapter Position和Layout Position并不相等,我们应当根据情况选择需要使用哪个,Adapter Position数据所处的位置,而Layout Position则对应当前View的所处的位置。
更多文章,欢迎访问我的 Android 知识梳理系列:
- Android 知识梳理目录:http://www.jianshu.com/p/fd82d18994ce
- 个人主页:http://lizejun.cn
- 个人知识总结目录:http://lizejun.cn/categories/


