前言
本文中,我将从RecyclerView插入Item 和 删除Item的两个场景,通过dispatchLayoutStep1、dispatchLayoutStep2、dispatchLayoutStep3各个阶段下来说明RecyclerView的布局情况。
场景1:插入Item
1.1 场景说明
假设在Item1下面插入两条数据AddItem1,AddItem2 图片1.2 核心方法
notifyItemInserted()
1.3 各个阶段布局情况
阶段1:dispatchLayoutStep1()
- 寻找锚点,找到Item1 图片
- 移除屏幕上的Views,放入到mAttachedScrap中 图片
- 锚点处从上往下填充 图片
-
锚点处从下往上填充,由上图可知,上面没有空间了,不填充
-
判断是否还有剩余的空间,如果有在末尾填充,下面没空间了,不填充
-
因为当前是预布局阶段,不填充
阶段2:dispatchLayoutStep2()
- 寻找锚点,找到Item1 图片
- 移除屏幕上的Views,放入到mAttachedScrap中 图片
- 锚点处从上往下填充,此时将变化后的数据填充到屏幕上,addItem1和addItem2被填充到item1下面 图片
-
锚点处从下往上填充,由图可知,没有空间不填充
-
判断是否还有剩余的空间,由图可知,没有空间不填充
-
当前是layoutStep2阶段,会将mAttachScrap的内容,填充到屏幕末尾,ViewHolder5和ViewHolder6对应的ItemView被填充
阶段3:dispatchLayoutStep3阶段
- Item2、Item3~Item6做移动动画
- addItem1、addItem2做淡入动画
- 动画结束后Item5、Item6被回收到mCachedViews缓存池中
1.4 总结
在Item1下面增加两个Item场景,各个layout阶段的布局情况
图片场景2:删除Item
2.1 场景说明
屏幕上有Item1-Item6,共6个View,目前需删除Item1和Item2。 图片2.2 核心方法
notifyItemRemoved()
2.3 各个阶段布局情况
- 将Item1 Item2对应的ViewHolder设置为REMOVE状态
- 将所有的Item对应的ViewHolder的mPreLayoutPosition字段赋值为当前的position
我们回顾以下onLayoutChildren的几个步骤
- 寻找填充的锚点(最终调用findReferenceChild方法)
- 移除屏幕上的Views(最终调用detachAndScrapAttachedViews方法)
- 从锚点处从上往下填充(调用fill和layoutChunk方法)
- 从锚点处从下往上填充(调用fill和layoutChunk方法)
- 如果还有多余的空间,继续填充(调用fill和layoutChunk方法)
- 非预布局,将scrapList中多余的ViewHolder填充(调用layoutForPredictiveAnimations)
阶段1:dispatchLayoutStep1()
- 寻找填充的锚点,寻找锚点的逻辑是,从上往下,找到第一个非remove状态的Item。在本Case中,找到Item3 图片
- 移除屏幕上的Views,将它们的ViewHolder放入到Recycler的mAttachedScrap缓存中,这个缓存的好处是如果position对应上了,无需重新绑定,直接拿来用。 图片
- 从锚点Item3处往下填充,mAttachedScrap只剩下ViewHolder2和ViewHolder1 图片
- 从锚点Item3处往上填充Item2 Item1,因为Item2,Imte1已经被remove掉了,它消耗的空间不会被记录,那么到步骤5的时候还可以填充 图片
- 还有多余的空间,继续填充,把Item7、Item8填充到屏幕中 图片
-
因为当前是预布局,直接返回
至此step1的layout结束
阶段2:dispatchLayoutStep2阶段
- 寻找填充的锚点,寻找锚点的逻辑是,从上往下,找到第一个非remove状态的Item。在本Case中,找到Item3 图片
- 移除屏幕上的Views,将它们的ViewHolder放入到Recycler的mAttachedScrap缓存中 图片
- 从锚点Item3处往下填充,填充到Item6为止,就没有足够的距离了,mAttachedScrap只剩下ViewHolder8,ViewHolder7,ViewHolder2,ViewHolder1 图片
- 往上填充,虽然此时还有两个View的高度,但是此时,上边没有数据了,此处不填充 图片
- 此时还有两个View的高度,继续往下填充 图片
「注意此时已经布局完成但是屏幕上部与第一个有GAP,会修复」
if (getChildCount() > 0) { // because layout from end may be changed by scroll to position // we re-calculate it. // find which side we should check for gaps. if (mShouldReverseLayout ^ mStackFromEnd) { int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true); startOffset += fixOffset; endOffset += fixOffset; fixOffset = fixLayoutStartGap(startOffset, recycler, state, false); startOffset += fixOffset; endOffset += fixOffset; } else { int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true); startOffset += fixOffset; endOffset += fixOffset; fixOffset = fixLayoutEndGap(endOffset, recycler, state, false); startOffset += fixOffset; endOffset += fixOffset; } }
修复后效果如下
图片
- 当前不是预布局,但是因为ViewHolder1和ViewHolder2都是被Remove掉的,所以跳过 图片
阶段3:dispatchLayoutStep3阶段
- Item1、Item2做消失动画
- Item3、Item4~Item8做移动动画
- 动画结束后,Item1、Item2会被回收到mCachedViews缓存池中 图片
2.4 总结
图片至此,关于RecyclerView布局过程讲解完毕。
网友评论