看完本文你大概需要 8.3分 的毅力
相关系列文章
上一篇文章讲了RecyclerView的布局流程,发现里面大多数内容都是和动画相关的。那么这边文章就先讲RecyclerView中,数据改变发出通知到播放动画的一系列流程。
RecyclerView的动画流程
对于RecyclerView的动画流程,是一个非常的长的流程,那么我们先把大的东西分部分来看,会轻松一点。首先,回想一下,我们通常在RecyclerView中数据改变的时候,调用什么函数来使其播放动画的。通常我们都是调用Adapter的以下函数。
notifyDataSetChanged()
notifyItemChanged(int position)
notifyItemRangeChanged(int positionStart, int itemCount)
notifyItemRangeChanged(int positionStart, int itemCount, Object payload)
notifyItemInserted(int position)
notifyItemMoved(int fromPosition, int toPosition)
notifyItemRangeInserted(int positionStart, int itemCount)
notifyItemRemoved(int position)
notifyItemRangeRemoved(int positionStart, int itemCount)
下面我们把这些函数统称为Adapter的通知函数
那么我们就从这里出发开始RecyclerView的动画流程。而在上一篇布局流程源码分析中,在最后总结时提到的。
RecyclerView的数据改变的动画是在布局过程的第三步中统一触发的。并不是一调用notify之后立即触发。
可以看出动画的一部分的处理是在布局过程中完成的,那么我们可以把动画流程分为两个部分。布局过程到动画触发的部分,和Adapter发出通知到布局之前的部分。
下面我们以插入作为例子分析,其他过程大致类似。
Adapter通知到布局之前的处理
首先,盗用该系列文章中的第一篇文章介绍Adapter通知流程的图:
Adapter的通知过程是一个观察者模式。结合源码:
|
|
我们可以看到Adapter中包含一个AdapterDataObservable
的对象mObservable
,这个是一个可观察者,在可观察者中可以注册一系列的观察者AdapterDataObserver。在我们调用的notify函数的时候,就是可观察者发出通知,这时已经注册的观察者都可以收到这个通知,然后依次进行处理。
那么我们看一下注册观察者的地方。
注册观察者的地方就是在RecyclerView的这个函数中。这个是setAdapter方法最终调用的地方。它主要做了:
- 如果之前存在Adapter,先移除原来的,注销观察者,和从RecyclerView Detached。
- 然后根据参数,决定是否清除原来的ViewHolder
- 然后重置AdapterHelper,并更新Adapter,注册观察者。
|
|
从这里我们可以看出,mObserver这个成员变量就是注册的观察者,那么我们去看看这个成员变量的内容。
该成员变量是一个RecyclerViewDataObserver的实例,那么RecyclerViewDataObserver实现了AdapterDataObserver中的方法。其中onItemRangeInserted(int positionStart, int itemCount)就是观察者接受到有数据插入通知的方法。那么我们来分析这个方法。看注释。
|
|
AdapterHelper中onItemRangeInserted函数即相关内容,请看注释 3)。
|
|
|
|
|
|
AdapterHelper preProcess方法源码:
|
|
该回调是在RecyclerView初始化的时候,初始化AdapterHelper的时候设置进来的。我们先只研究offsetPositionsForAdd这个回调分析流程。
|
|
|
|
布局流程到触发动画的过程
根据上面的分析,如果我们没有设置mHasFixedSize=true,那么我们最早在 4) 步就会requestLayout,从而使View重新Layout,就是在那么现在我们再来看布局流程:
首先是step1:
|
|
这里我觉得有必要讲一个mViewInfoStore,mViewInfoStore是一个ViewInfoStore。来看一下这个类:这个类是用来追踪View所要做的动画的。其中有一个内部类InfoRecord,该类用来存储ViewHolder前后的信息,以及ViewHolder状态的flag。
其中有两个比较重要的成员变量,mLayoutHolderMap和mOldChangedHolders,分别用来存储我们的布局改变的ViewHolder对应的动画信息和内容改变的ViewHolder。
ViewInfoStore通过addToPostLayout,addToPreLayout……等方法添加ViewHolder到mLayoutHolderMap中,并且为其InfoRecord设置对应的flag。
在layout的过程中就是分析View的各种状态,然后使用对应的方法添加到ViewInfoStore中
ViewInfoStore中还有一个非常重要的方法,process
,该方法会处理所有的mLayoutHolderMap中的值,并根据其flag和前后的信息来判断ViewHolder的动作,并将这个动作反应给ProcessCallback。分别有4种行为:消失,出现,一直存在,为使用。然后交给外面去处理。
|
|
好,简单了解一下ViewInfoStore之后,继续看布局流程。dispatchLayoutStep2中没有对动画的相关处理,直接看第三步。在判断里面,我不去一行一行的讲了,它们就是遍历所有的ViewHolder,然后判断各种情况,最后添加到mViewInfoStore中。遍历结束之后执行mViewInfoStore的process处理所有的在其mLayoutHolderMap中的ViewHolder。
|
|
之后我们看一下mViewInfoStore的ProcessCallback的实现mViewInfoProcessCallback,这里只拿processAppeared做分析:
|
|
然后看一下animateAppearance方法:
|
|
该方法中不要忽略if中的内容:mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)
那么进入该方法:看注释。
|
|
真正实现是在DefaultItenAnimator中:这里做了三件事情,重置holder的动画,设置显示属性,然后添加到mPendingAdditions中,mPendingAdditions是一个存储添加ViewHolder的List,表示待处理的添加动画的ViewHolder。同样在DefaultItenAnimator总也有,移动的,移除的列表。
|
|
最后返回true,进入if,执行postAnimationRunner
方法。
|
|
尼玛逗我,去看mItemAnimatorRunner,其中调用的ItemAnimator的runPendingAnimations方法。
|
|
然后分析runPendingAnimations方法:该方法并不难,按照移除,移动,改变,添加,依次处理之前的待处理列表中的内容。这里还是以添加的做为例子来分析,看注释。
|
|
这个方法其实就是通过属性动画对ViewHolder中的View做渐变动画。
|
|
到这里,终于是触发了我们的动画。其它的动作,流程类似,细节不同而已。
总结
其实这个流程具体的细节不是很重要,因为View的状态很多,情况很复杂,我们也没有必要把那些算法都弄清楚。我们知道它的流程即可。那么通过流程我们可以深入理解以下2点:
- 如果我们的RecyclerView的高度和宽度不变,那么通过手动执行
setHasFixedSize(true)
,可以在一定程度上减少计算,提高性能。可以在 4) 步的时候绕过requestLayout,只走自身的布局流程。而requestLayout是申请父控件重新布局流程,两者的计算量是不一样的。 - 自定义ItemAnimator的时候,如果在animateAppearance,animateDisappearance……方法中直接运行了动画,就返回false,如果是暂存起来,就返回true,然后将真正执行动画的操作放在runPendingAnimations方法中。
花了这么大力气才知道这么点东西。哈哈!fuck the souce code!!