3. 布局阶段
3.1 核心方法
- RecyclerView#dispatchLayoutStep2()
- LinearLayoutManager#layoutChunk()
- LinearLayoutManager#addDisappearingView()
- ViewInfoStore#addToDisappearedInLayout()
3.2 作用
- 根据数据源中的数据进行布局,真正展示给用户看的最终界面
- 如果开启动画,将被挤出屏幕的View的保存到消失动画列表中
3.3 源码解析
3.3.1 RecyclerView#dispatchLayoutStep2()
- 将预布局模式改为false
- 布局填充View
3.3.2 LinearLayoutManager#layoutChunk()
布局阶段将被挤出屏幕的View放入到DISAPPEARED动画列表中
3.3.3 LinearLayoutManager#addDisappearingView()
把Removed的View或被挤出屏幕的View添加到Disappearing动画列表
3.3.4 ViewInfoStore#addToDisappearedInLayout()
加入到Disappeared动画列表
4. 触发动画阶段
4.1 核心方法
- RecyclerView#dispatchLayoutStep3()
- ViewInfoStore#addToPostLayout()
- ViewInfoStore#process()
- ItemAnimator#animateAppearance()
4.2 作用
- 清理工作
- 保存布局后的view的信息
- 触发动画
- 动画执行完回收工作
4.3 源码解析
4.3.1 RecyclerView#dispatchLayoutStep3()
- 将当前屏幕上的View信息记录到postLayout动画列表中
- 执行动画
- 清理操作
- 布局完成回调
4.3.2 ViewInfoStore#addToPostLayout()
View信息记录到postLayout动画列表中
4.3.3 ViewInfoStore#process()
作用:执行动画
工作流程,按优先级执行
- 调用unuse() 将view回收掉
- 执行消失动画
- 2.1 预布局中不可见调用unuse()
- 2.2 调用processDisappeared()
- 调用processPersistent()执行move或者change动画
- 执行remove动画
- 执行insert动画
4.3.4 ViewInfoStore$InfoRecord
作用:定义动画类型
- FLAG_DISAPPEARED:消失动画,包含move和remove动画
- FLAG_APPEAR:出现动画,包含move和insert动画
- FLAG_PRE:预布局前已经显示在RecyclerView上
- FLAG_POST:布局后显示在RecyclerView上
- FLAG_APPEAR_AND_DISAPPEAR:先做出现动画,再做消失动画,无意义
- FLAG_PRE_AND_POST:预布局前和布局后一直显示在RecyclerView上
- FLAG_APPEAR_PRE_AND_POST:在FLAG_PRE_AND_POST基础上做出现动画
4.3.5 ViewInfoStore$ProccessCallback
作用:定义四种处理动画的接口
- processDisappeared 处理消失动画
- processAppeared 处理出现动画
- processPersistent 处理一直存在动画,包含move和change动画
- unused 不需要处理动画,执行回收
4.3.6 接口实现
4.3.7 ProccessCallback#processAppeared
兵分两路
- 调用ItemAnimator#animateAppearance()
- 调用RecyclerView#postAnimationRunner()
4.3.8 一路兵:ItemAnimator#animateAppearance()
4.3.8.1 SimpleItemAnimator#animateAppearance
- 该方法返回true表示需要做动画
- 否则不需要做动画
- 如果预布局前View已经存在而且位置发生改变,处理MOVE动画
- 否则,处理ADD动画
4.3.8.2 DefaultItemAnimator.animateMove
- 该方法并没有真正执行动画
- 将MoveInfo保存到mPendingMoves中,以便RecyclerView#postAnimationRunner()使用
- 判断是否有必要执行MOVE动画
- 回到preLayout的位置
4.3.8.3 DefaultItemAnimator.animateAdd
先调用setAlpha(0),以便做淡入动画
4.3.9 二路兵:RecyclerView#postAnimationRunner()
4.3.9.1 RecyclerView#postAnimationRunner
最终调用到ItemAnimator.runPendingAnimations
4.3.9.2 DefaultItemAnimator.runPendingAnimations
- 首先执行Remove动画
- 然后同时执行Move和Change动画
- 最后执行Add动画
动画的总时长为removeDuration + Math.max(moveDuration, changeDuration) + addDuration
4.3.10 RecyclerView$ItemAnimatorRestoreListener
作用:动画结束后执行回收操作
- 动画执行完毕,removeAnimatingView
- 调用Recycler.recycleViewHolderInternal执行回收操作
5. 场景篇
5.1 notifyItemRemoved场景
5.1.1 场景描述
- 调用notifyItemRemoved()
- Adapter数据有100条,屏幕上有Item1~Item6 6个View,删除Item1和Item2
5.1.2 布局过程
- 将Item1 Item2对应的ViewHolder设置为REMOVE状态
- 将所有的Item对应的ViewHolder的mPreLayoutPosition字段赋值为当前的position
5.1.2.1 dispatchLayoutStep1阶段
- 寻找填充的锚点,寻找锚点的逻辑是,从上往下,找到第一个非remove状态的Item。在本Case中,找到Item3
- 移除屏幕上的Views,将它们的ViewHolder放入到Recycler的mAttachedScrap缓存中,这个缓存的好处是如果position对应上了,无需重新绑定,直接拿来用。
从锚点Item3处往下填充,mAttachedScrap只剩下ViewHolder2和ViewHolder1
从锚点Item3处往上填充Item2 Item1,因为Item2,Imte1已经被remove掉了,它消耗的空间不会被记录,那么到步骤5的时候还可以填充
还有多余的空间,继续填充,把Item7、Item8填充到屏幕中
- 因为当前是预布局,直接返回
5.1.2.2 dispatchLayoutStep2阶段
- 寻找填充的锚点,寻找锚点的逻辑是,从上往下,找到第一个非remove状态的Item,找到Item3
- 移除屏幕上的Views,将它们的ViewHolder放入到Recycler的mAttachedScrap缓存中
从锚点Item3处往下填充,填充到Item6为止,就没有足够的距离了,mAttachedScrap只剩下ViewHolder8,ViewHolder7,ViewHolder2,ViewHolder1
往上填充,虽然此时还有两个View的高度,但是此时,上边没有数据了,此处不填充
此时还有两个View的高度,继续往下填充
修复GAP
- 当前是布局阶段,但是因为ViewHolder1和ViewHolder2都是被Remove掉的,所以跳过
5.1.2.3 dispatchLayoutStep3阶段
- Item1、Item2做消失动画
- Item3、Item4~Item8做移动动画
- 动画结束后,Item1、Item2会被回收到mCachedViews缓存池中
5.2 notifyItemInserted场景
5.2.1 场景描述
假设在Item1下面插入两条数据AddItem1,AddItem2
5.2.2 布局过程
5.2.2.1 dispatchLayoutStep1阶段
- 寻找锚点,找到Item1
2. 移除屏幕上的Views,放入到mAttachedScrap中
3. 锚点处从上往下填充
4. 锚点处从下往上填充,由上图可知,上面没有空间了,不填充 5. 判断是否还有剩余的空间,如果有在末尾填充,下面没空间了,不填充 6. 因为当前是预布局阶段,不填充
5.2.2.2 dispatchLayoutStep2阶段
- 寻找锚点,找到Item1
2. 移除屏幕上的Views,放入到mAttachedScrap中
3. 锚点处从上往下填充,此时将变化后的数据填充到屏幕上,addItem1和addItem2被填充到item1下面
4. 锚点处从下往上填充,由图可知,没有空间不填充 5. 判断是否还有剩余的空间,由图可知,没有空间不填充 6. 当前是layoutStep2阶段,会将mAttachScrap的内容,填充到屏幕末尾,ViewHolder5和ViewHolder6对应的ItemView被填充
5.2.2.3 dispatchLayoutStep3阶段
- Item2、Item3~Item6做移动动画
- addItem1、addItem2做淡入动画
- 动画结束后Item5、Item6被回收到mCachedViews缓存池中
5.3 场景总结
5.3.1 notifyItemRemoved场景
5.3.2 notifyItemInserted场景