RecyclerView源码解析

RecyclerView源码解析

总体框架

内部调用流程

内部调用流程

设计思路

RecyclerView官网给出的定义是: A flexible view for providing a limited window into a large data set. 也就是在限定的视图内展示大量的数据

适配器模式

如上所示,RecyclerView表示只会和ViewHolder进行接触,Adapter的工作是将Data转换为RecyclerView认识的ViewHolder,因此RecyclerView就间接地认识Datas。

而RecyclerView仍然不想管理子view,于是交给了LayoutManager来帮助完成布局(桥接模式)。

桥接模式

但是LayoutManager这个大管家也有弱点,它只知道怎么将这一个个View布局在RecyclerView上,但是并不知道如何管理这些View,此时要有个回收期Recycler回收器做管理。当LayoutManager在需要View的时候向Recycler进行索取,当LayoutManager不需要View(试图滑出)的时候,就直接将废弃的View丢给Recycler。

Recycler

到了这里,看似一切都很完美,但是RecyclerView还想让子View变动的动画要优雅,所以通过观察者模式,引入了ItemAnimator(观察者模式)。

设计模式

  • 通过桥接模式,使RecyclerView将布局方式独立成LayoutManager,实现对布局的定制化。
  • 通过组合模式,使RecyclerView通过dispatchLayout对item View进行布局绘制。
  • 通过适配器模式,ViewHolder将RecyclerView与ItemView联系起来,使得RecyclerView方便操作ItemView。
  • 通过观察者模式,给ViewHolder注册观察者,当调用notifyDataSetChanged时,就能重新绘制。

源码分析

设计结构

设计结构

类关系图

类关系图

  • RecyclerViewDataObserver: 数据观察器
  • Recycler: View 循环复用系统,核心部件
  • SavedState: RecyclerView状态
  • AdapterHelper:适配器更新
  • ChildHelper:管理子View
  • ViewInfoStore:存储子VIEW的动画信息
  • Adapter:数据适配器
  • LayoutManager:负责子VIEW的布局,核心部件
  • ItemAnimator:Item动画
  • ViewFlinger:快速滑动管理
  • NestedScrollingChildHelper:管理子VIEW嵌套滑动

绘制详情

以我们平时使用RecyclerView的行为举例:

1
2
3
4
5
6
7
8
9
10
11
12
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);  
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
//设置布局管理器
recyclerView.setLayoutManager(layoutManager);
//设置为垂直布局,这也是默认的
layoutManager.setOrientation(OrientationHelper. VERTICAL);
//设置Adapter
recyclerView.setAdapter( recycleAdapter);
//设置分隔线
recyclerView.addItemDecoration( new DividerGridItemDecoration(this ));
//设置增加或删除条目的动画
recyclerView.setItemAnimator( new DefaultItemAnimator());

初始化整体流程图如下

流程图

RecyclerView layout(setLayoutManager)流程是什么样的?

RecyclerView Layout流程

layout过程中onLayoutChildren是什么流程?

  • 找到anchor点
  • 根据anchor一直向前布局,直到充满anchor点前面的所有区域
  • 根据anchor一直向后布局,直到填充满anchor点后面所有区域,以垂直布局来说明,mAnchorInfo为布局锚点信息,包含了子控件在Y轴上起始绘制偏移量(coordinate),ItemView在Adapter中的索引信息(position)和布局方向(mLayoutFromEnd) ——指start、end方向。确定布局锚点,以此为起点向开始和结束方向填充ItemView。

setAdapter方法最终调用的流程?

  1. 如果之前存在Adapter,先移除原来的,注销观察者,从RecyclerView Detached。
  2. 根据参数,决定是否清除原来的ViewHolder。
  3. 重置AdapterHelper,更新Adapter,注册观察者。

RecyclerView的滑动过程是什么样的?

滑动过程

缓存逻辑是什么样的?

总共分为两级缓存

  1. mCachedViews,先进先出的数据结构,将新的View存入mCachedViews,移除头元素,并将头元素放入mRecyclerPool
  2. mRecyclerPool,可以多个ReyclerView共享,减少内存开销。

AdapterView与RecyclerView的区别?

区别

RecyclerView中有哪些设计精巧的地方?

Bucket

用来表示不设上限的状态,Bucket是一个链表结构,当index大于64的时候,它便会去下一个Bucket寻找,Bucket不设上限的表示状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static class Bucket {
final static int BITS_PER_WORD = Long.SIZE;
final static long LAST_BIT = 1L << (Long.SIZE - 1);
long mData = 0;
Bucket next;
void set(int index) {
if (index >= BITS_PER_WORD) {
ensureNext();
next.set(index - BITS_PER_WORD);
} else {
mData |= 1L << index;
}
}
...
}

Pools

相当于Messages的池的概念,通过Message.obtain()可以在很多情况下避免创建新的对象

  • RecycleView将Item的增删改封装为UpdateOp类
  • ViewInfoStore类中的静态内部类infoRecord

使用RecyclerView有没有遇到不好用的地方?

RecyclerView也不是万能的,它的灵活性也是有一定限制的,比如我就遇到了一不是很好解决的问题:Recyler的缓存级别是一个Item的整个View,而我们没办法自定义缓存级别,这样说比较抽象,举个例子,我的某些Item的某个子View加载很耗时,所以我希望我在上下滑动的时候,Item的其它View是可以被回收利用的,但这个加载很耗时的View是不要重复使用的。即我希望用空间换取时间来获取滑动的流畅性。当然,这样的需求不常见,RecyclerView也不能很好的满足这一点。