背景知识
RecyclerView的Adapter有几个notify相关的方法:
- notifyDataSetChanged()
- notifyItemChanged(int)
- notifyItemInserted(int)
- notifyItemRemoved(int)
- notifyItemRangeChanged(int, int)
- notifyItemRangeInserted(int, int)
- notifyItemRangeRemoved(int, int)
- notifyItemMoved(int, int)
notifyDataSetChanged()与其他方法的区别:
- 会导致整个列表刷新,其它几个方法则不会;
- 不会触发RecyclerView的动画机制,其它几个方法则会触发各种不同类型的动画。
1. 布局放置
1.1 核心方法
RecyclerView#dispatchLayout()
1.2 作用
- 将View放置到合适的位置
- 记录布局阶段View的信息
- 处理动画
RecyclerView的布局我们可以分成三个阶段,也可以精细分成五个阶段。
1.2.1 三个阶段
1.2.1.1 预布局阶段
当需要做动画时,预布局阶段才会工作,否则没有实际意义,它对应dispatchLayoutStep1方法。动画有开始状态和结束状态,预布局完成后的RecyclerView是动画的开始状态。
1.2.1.2 布局阶段
无论是否需要做动画,布局阶段都会工作,它对应dispatchLayoutStep2方法。布局完成后的状态是用户最终看到的状态,也是动画的结束状态。
1.2.1.3 布局后阶段
布局完成后,需要执行动画操作,它对应的是dispatchLayoutStep3方法。当动画完成后,还会进行View回收操作。
1.2.2 五个阶段
1.2.2.1 预布局前
在dispatchLayoutStep1方法调用onLayoutChildren方法之前。它会保存当前RecyclerView上所有子View的信息到ViewInfoStore中,FLAG增加FLAG_PRE。表示View在预布局前就显示在RecyclerView上。
1.2.2.2 预布局中
在dispatchLayoutStep1方法调用onLayoutChildren方法时。它会根据算法,重新布置RecyclerView的子View,该阶段可能会添加新的子View。该阶段能够确定哪些View最终是不会展示给用户看的,FLAG增加FLAG_DISAPPEARED(例如:removed的View)。
1.2.2.3 预布局后
在dispatchLayoutStep1方法调用onLayoutChildren方法之后,将预布局完成后的子View与预布局前的子View对比,将新增的View的FLAG增加FLAG_APPEAR(调用notifyItemRemoved后,新填充的View)。
1.2.2.4 布局中
在dispatchLayoutStep2方法调用onLayoutChildren方法时。该阶段会把被挤出屏幕的View的FLAG增加FLAG_DISAPPEARED。
1.2.2.5 布局后
在dispatchLayoutStep3方法中。会将最终的子View的FLAG增加FLAG_POST。
1.2.3 动画类型
1.2.3.1 PERSISTENT
预布局前和布局后都存在的View所做的动画,位置有可能发生变化了,也有可能没有发生变化。
1.2.3.2 REMOVED
在布局前对用户可见,布局后不可见,而且数据已经从数据源中删除掉了。
1.2.3.3 ADDED
新增数据到数据源中,并且在布局后对用户可见。
1.2.3.4 DISAPPEARING
数据一直都存在于数据源中,但是布局后从可见变成不可见状态(例如因为其它View插入操作,导致被挤出屏幕外了)。
1.2.3.5 APPEARING
数据一直都存在于数据源中,但是布局后从不可见变成可见状态(例如因为其它View被删除,导致补位到屏幕内了)。
1.3 源码解析
1.3.1 RecyclerView#dispatchLayout()
- dispatchLayoutStep1()执行预布局,记录ViewHolder位置信息;
- dispatchLayoutStep2()执行布局,用户最终看到的效果;
- dispatchLayoutStep3()执行动画操作。
2. 预布局阶段
2.1 核心方法
- RecyclerView#dispatchLayoutStep1()
- RecyclerView#processAdapterUpdatesAndSetAnimationFlags()
- LinearLayoutManager#onLayoutChildren()
- LinearLayoutManager#updateAnchorInfoForLayout()
2.2 作用
- 处理Adapter变化
- 决定该执行哪种类型动画
- 保存当前RecyclerView上的子View的信息
- 如果需要执行动画,进行预布局
2.3 源码解析
2.3.1 RecyclerView#dispatchLayoutStep1()
- 判断是否需要开启动画功能
- 如果开启动画,将当前屏幕上的Item相关信息保存起来供后续动画使用
- 如果开启动画,调用mLayout.onLayoutChildren方法预布局
- 预布局后,与第二步保存的信息对比,将新出现的Item信息保存到Appeared中
2.3.2 RecyclerView#processAdapterUpdatesAndSetAnimationFlags()
作用:判断是否需要开启动画
2.3.3 LinearLayoutManager#onLayoutChildren()
以垂直方向的RecyclerView为例子,我们填充RecyclerView的方向有两种,从上往下填充和从下往上填充。开始填充的位置不是固定的,可以从RecyclerView的任意位置处开始填充。
- 寻找填充的锚点(最终调用findReferenceChild方法);
- 移除屏幕上的Views(最终调用detachAndScrapAttachedViews方法);
- 从锚点处从上往下填充(调用fill和layoutChunk方法);
- 从锚点处从下往上填充(调用fill和layoutChunk方法);
- 如果还有多余的空间,继续填充(调用fill和layoutChunk方法);
- 布局完成后有可能产生GAP,需要修复GAP;
- dispatchLayoutStep2阶段调用layoutForPredictiveAnimation将scrapList中多余的ViewHolder填充(调用fill和layoutChunk方法)。
2.3.3.1 寻找填充的锚点
- 优先返回全部在屏幕内,未标记removed的View;
- 次优先级返回不可见的View;
- 最低优先级返回删掉的view。
2.3.3.2 移除屏幕上的Views
- 调用notifyItemChanged(position),position对应的ViewHolder会放入到mChangedScrap缓存中;
- 否则会放入到mAttachedScrap缓存中
2.3.3.3 ~ 2.3.3.5 填充
调用LinearLayoutManager#fill()和LinearLayoutManager#layoutChunk()
- 从缓存中获取View或者创建View
- 如果是step1预布局阶段,调用addView(),将标记为removed的view放入到DISAPPEARED动画列表中
- 如果是step2布局阶段,调用addDisappearingView(),将被挤出屏幕的view放入到DISAPPEARED动画列表中
- 如果是removed的或者changed,不会记录消耗的填充量
2.3.3.6 修复GAP
通过mOrientationHelper.offsetChildren(gap)直接填补GAP
2.3.3.7 layoutForPredictiveAnimation
为了做动画,增加额外的Item
- 不需要做动画,或者是预布局直接返回
- 从mAttachedScrap中遍历到非removed的ViewHolder,但是返回的结果可能包含removed ViewHolder
- 如果遍历找到了非Removed ViewHolder,填充View