作者:TeaOf
博客:https://www.jianshu.com/p/1ae2f2fcff2c
前言
系列文章的目的在于帮助 Android 开发者提高对 RecyclerView 的认知,本文是整个系列的第一章。
已经出来很久了,很多开发者对于的使用早已信手拈来。如下就是一张使用网格布局的:
不过,对于这种明星控件的了解仅仅停留在使用的程度,显然是不能够让我们成为高级工程师的。如果你看过包中的源码,那你应该和我的心情一样复杂,光一个文件的源码就多达 13000 行。
对于源码阅读方式,我很赞成郭神在 Glide 源码分析中所说:
抽丝剥茧、点到即止。抽丝剥茧、点到即止。应该认准一个功能点,然后去分析这个功能点是如何实现的。但只要去追寻主体的实现逻辑即可,千万不要试图去搞懂每一行代码都是什么意思,那样很容易会陷入到思维黑洞当中,而且越陷越深。
所以,我在阅读源码的时候先确定好自己想好了解的功能点:
数据转化为具体的子视图。
视图回收利用方式。
布局多样性原因。
布局动画多样性原因。
阅读姿势:我选择了版本为的,不知道什么原因,我点进版本的中查看代码时,虽然类缩短至 7000 行,但是注释没了以及其他的问题,我不得不使用其他版本的库。
想要深入原理,没有什么是一遍调试解决不了的,如果有,那就是调试第二遍。
一、RecyclerView 使用和介绍
以为例,我们看一下的使用方式:
以及各个部分的作用:
以上是我们使用的时候能够直观看到的部分,还有一个很重要但是不直接使用的类:
二,源码分析
的源码那么多,我们先按照使用时的路线进行分析。
通常,我们会在布局文件中使用,所以我们的入口就变成了:
由于我们可以在的布局文件中使用指定,如果指定了具体的,最终会在上面的方法中利用反射生成一个具体的实例。
研究自定义 View 的时候,最快的研究方法就是直接查看、和三大方法,研究也是如此。
上面我们说到了布局文件,之后,我们会在或者其他地方获取,再往下,我们会为设置(如未在布局文件中设置的情况下)、以及可能使用的,这些方法都会调用方法,从而刷新。
先从讲起:
会刷新布局,所以该跳到绘制的相关方法了?不,因为中的为空,为空,就没有数据,那看一个空视图还有什么意思呢?So,我们还需要看设置适配器的方法:
继续深入查看方法:
可以看出,上面的代码主要是针对发生变化的情况下做出的一些修改,是数据变化接口,当适配器中的数据发生增删改的时候最终会调用该接口的实现类,从该接口的命名以及注册操作和取消注册操作可以看出其使用的是观察者模式。和设置完成以后就可以直奔主题了。
View 工作流程的第一步:
显然,从上面的代码我们可以得出结论:为 或者 为空,我们会提前结束 的测量过程。
如果看过的同学应该对很熟悉,什么情况下会为呢?以为例,通常情况下,如果的宽为具体数值或者 的时候,那么它的 很大程度就为 。为需要保证高和宽的都为,当然,的 还与父布局有关,不了解的的同学可以查阅一下相关的资料。
如果你的代码中的 RecyclerView 没有使用,那么大部分使用场景中的长宽的都为,我这么说,不是意味着我要抛弃下方的关键方法和,因为它们在另一个工作流程中也会执行,所以我们放到中讲解。
View 工作流程的第二步:
在实例初始化中,默认为,方法肯定是要进入的:
我们需要关注和为 true 时机,从代码上来看,这两个属性为 true 必须存在,是否意味着子 View 动画的执行者,另外,也得关注,帮记录了中子 View 的位置信息和状态。
再看方法:
在方法中我们可以看到,自身没有实现给子布局,而是将布局方式交给了,的深入研究我会在之后的博客和大家讨论。
打铁趁热,我们查看,代码较多,精简后如下:
调用执行动画函数的时候,可以看到放入参数,从名字可以看出,这是一个回调的接口,所以,我猜动画的真实的执行应该在实现接口的方法中实现,不过,我们还是要先看中的动画如何执行:
之前存储的和位置状态相关被一个个取出,然后将和交给,如我们所料,只是对进行分类,具体的实现还是在中的回调,最后查看一下具体实现:
限于篇幅,这里我只展示了中实现的一个方法,在该方法中,它调用了方法,动画的任务最终也交给了,可由用户自定义实现。
这里有必要说明一下,一些删除或者新增操作,通过使用适配器中通知删除或者新增的方法,最终还是会通知界面进行重绘。
到这儿,我们可以总结一下,过程中,将子视图布局的任务交给了,同样的,子视图动画也不是自身完成的,动画任务被交给了,这也就解决了我们一开始提出的两个问题:
布局多样性的原因
布局动画多样性的原因
至于和更深层次的探讨,我将会在后面的博客中进行。
中的方法比较简单,仅仅绘制了,同样需要用户自定义实现:
而子 View 的绘制其实在实现的,这里不再继续讨论了。
如果你没看懂,没关系,在三大工程流程中大概做了如下的事:
2. View 管理 - Recycler
在上文中,我们简要了解RecyclerView 绘制的三大流程以及和 承担的任务。显然,我们忽略了适配器和缓存管理,下面我们就重点谈谈这两位。
上文中,我们了解到在方法中,给子 View 定位的任务交给了:
简要的介绍一下的工作内容:
如果当前中还存在子,移除所有的子,将移除的添加进。
一次通过获取一个子 View。
重复进行 2,直到获取的子 View 填充完即可。
虽然上面的内容很简单,但是的实际工作内容要复杂的多,那么工作机制是怎样的呢?我们来一探究竟。
先看组成部分:
入口是,有一个位置的参数:
通过名字就可以猜到函数的意思了,中的就是我们要获取的子视图,是如何获取的呢?
从注释中我们可以看到,前三步的获取是利用的的一级缓存和二级缓存,第四步通过获取,第五步通过的方式获取,如果连缓存池中都没有,那么只好调用重新创建,这个名称是我们的老朋友了,而且还是在中,我们简单了解一下:
真正创建的是方法,这也是我们继承适配器必须要实现的抽象方法,通常,我们在继承不会只创建,还会做子和数据的绑定,在返回视图之前,视图的绑定肯定是完成了的,我们看看视图绑定发生在哪里?
我们再返回上一个方法中,可以看到在倒数第四行,在执行方法:
成功见到我们必须实现的方法,这些完成以后,子就会被交给管理了。
回收的场景有很多种,比如说滑动、数据删除等等。我们在这里以滑动作为回收的场景,并且只分析手指触摸时的滑动,滑动的入口在:
代码简化以后,我们仅需要关注:
最后还是交给了处理,除去函数嵌套之后,最后又回到了的视图填充的过程,在中,我们仅仅讨论了该过程中视图的获取,其实,该过程中,还会涉及到视图的回收,在回收的过程中,大概做了如下的事情:
找出需要回收的视图。
通知父布局也就是移除子视图。
通知进行回收管理。
我们着重探究进行回收管理,回收的入口是:
从上述的方法可以看出,会被优先加入,当数量大于 2 的时候,会调用方法:
因为是 2,所以中数量为 2 的时候,会先添加到,然后从中移除先进来的添加进缓存池。
我在这里选取了一些常用的场景,整合出如下图片:
需要指明的是:
实际并未参加真实的缓存过程,它的添加和移除都出现在方法中的过程中。
对于中已经显示并将继续展示的,重绘过程中,会将以及其中的子从移出,添加进中,并在后续的填充子过程中,从取出。
最多只能缓存两个,如果大于最大缓存数量,会将先进来的取出加入。
针对每种的提供最大最大数量为 5 的缓存。
有了以后:
灰色的是小 T 同学的手机屏幕,查看聊天记录的时候,不会每次都创建新的,也不会一次性将所有的都建好,减少了内存和时间的损耗,所以,小 T 同学就可以流畅的查看和女友的上千条聊天记录了~
三、浅谈设计模式
阅读源码的过程中,发现运用了很多设计模式。
看类这个名字,就可以看出它使用了适配器模式,因为涉及到将数据集转变成需要的子视图。除了适配器模式之外,中还使用观察者模式,这一点可以从方法中可以看出,设置适配器的时候,会对旧的取消注册监听器,接着对新的注册监听器,等到数据发生变化的时候,通知给观察者,观察者就可以在内愉快地删除或者新增子视图了。
接着,看这个类,将给布局这个任务交给了抽象类,根据不同需求,比如线性布局可以用实现,网格布局可以用。应对同一个布局问题,使用了策略模式,给出了不同的解决方案,也是如此。
如果感兴趣的话,同学们可以查看对应的源码。
四、总结
本文中,除了对进行深层次研究外,其他则点到为止,大致得到如下结论:
后续博客中,我将和大家一起学习中的其他部分。敬请期待!
近期文章:
今日问题:
没想到每天都在用的RecyclerView这么复杂吧?
领取专属 10元无门槛券
私享最新 技术干货