RecyclerView在处理消失动画时采用了和Animation/LayoutTransition效果相似但是本质有区别的策略:某个ChildView需要渐变消失动画效果,那么该ChildView在动画结束前不会从ViewGroup中被remove掉,在动画运行完以后才会被remove掉。
上述策略显然会造成不一致:Data中,ChildView对应的Item已经被remove了(数据remove不会因为有动画就延迟),但是对应的ChildView因为动画的原因还留在RecyvlerView中,RecyclerView的ChildView和Data在这个时间段是不一致的。这种情况下的ChildView可以被理解为是暂态的(在RecyclerView中,被称作为AnimatingView)。
上面的不一致很显然会对RecyclerView的正常运作产生影响,比如RecyclerView的LayoutManager在进行布局时是不会也不能考虑上面的AnimatingView的(概括的来讲)。 但是某些场景下,RecyclerView又需要能够感知到这些AnimatingView(比如View复用)。这就造成了两种观察ChildView的视角:
为了区分两种视角以及支持两种视角下的ChildView操作,需要进行额外的逻辑处理,RecyclerView将这部分逻辑独立为一个单独的模块:ChildHelper。
RecyclerView尽管本身是一个ViewGroup,但是将ChildView管理职责全权委托给了ChildHelper,所有关于ChildView的操作都要通过ChildHelper来间接进行,ChildHelper成为了一个ChildView操作的中间层,getChildCount/getChildAt等函数经由ChildHelper的拦截处理再下发给RecyclerView的对应函数,其参数或者返回结果会根据实际的ChildView信息进行改写。
考虑一下ChildHelper的模型:
这样,ChildHelper储存了足够的信息来区分ChildView以及支持不同视角的操作,不过,因为ChildHelper本身并不是一个ViewGroup(只是一个中间处理器),真正涉及到具体平台的ChildView操作(这里就是Android的ViewGroup的相关ChildView操作)还需要落地到RecyclerView, ChildHelper通过提供一个Callback给RecyclerView这种方式实现了真正操作实现的落地。 因此在RecyclerView初始化ChildHelper时可以看到需要提供一个Callback的具体实现来给ChildHelper。
ChildHelper维护了两套ChildView序列,一套序列只包含了可见的ChildView,另一套则包含了所有ChildView,序列在这里很重要,因为很多对ChildView的操作是以其在序列中的位置作为参数进行的。举个例子:
RecyclerView中有 A,B,C,D,E,F 6个ChildView,其中B,C,E是不可见的, 存在两个ChildView序列:
[1] 所有ChildView: A,B,C,D,E,F [2] 可见ChildView: A, D, F.
对[1]getChildCount是6, 对[2]getChildCount是3.
对[1]getChildAt(1)得到的是B, 对[2]getChildAt(1)得到的是D
AddView函数只针对可见ChildView, 如果指定了要add的index(在序列中的位置), 那么会根据真实ChildView的情况,对index进行偏移(getOffset函数非常关键,会基于Bucket算出合适的偏移结果),算出一个在真实ChildView序列中的index,将这个新的index作为View在ViewGroup中的index进行添加。在将View按照新的index进行添加后,Bucket中的映射关系也会进行相应的insert。比如: 在[2]的index 2位置插入G(addView(G, 2)), 那么两套序列会变为:
[1] A,B,C,D,G,E,F
[2] A,D,G,F
addView传输的index是2, 经过ChildHelper转化,最后偏移为4插入到了RecyclerView中。
综上可见,ChildHelper作为一个ChildView中间层对外提供两个ViewGroup环境,一个是真实的包含所有ChildView,另一个只包含了在Data层面可见的ChildView。外界不需要关心内部如何保存View信息以及计算index偏移,只需调用ViewGroup类方法即可。