RecyclerView机制解析: ChildHelper

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的视角:

  1. 从ViewGroup角度看,所有ChildView都是可见的,AnimatingView
  2. 从Data角度看,只有能够映射到Data的ChildView才是可见的(笼统的来说)

为了区分两种视角以及支持两种视角下的ChildView操作,需要进行额外的逻辑处理,RecyclerView将这部分逻辑独立为一个单独的模块:ChildHelper

RecyclerView尽管本身是一个ViewGroup,但是将ChildView管理职责全权委托给了ChildHelper所有关于ChildView的操作都要通过ChildHelper来间接进行,ChildHelper成为了一个ChildView操作的中间层,getChildCount/getChildAt等函数经由ChildHelper的拦截处理再下发给RecyclerView的对应函数,其参数或者返回结果会根据实际的ChildView信息进行改写。

考虑一下ChildHelper的模型:

  1. 首先,我们需要能够支持两种视角, 那么普通ChildView和AnimatingView这类特殊的View要能够被区分,为了区分就需要维护一个映射表,ChildView的位置是key,value则是否是特殊的View。在ChildHelper的实现中,将普通ChildView看作是可见的,特殊的View看作是不可见的,只需要维护一个true/false即可。进一步的,为了节省空间和提升效率,ChildHelper采用了BitSet来实现这个映射,因为true/false信息1bit就足以储存了, 这个BitSet在ChildHelper中对应的类是Bucket类(Bucket可以根据操作动态的扩展自己的空间,其本身实现也值得一看)。BitSet的很多操作都是位运算,因此效率高。
  2. 其次,要支持两种视角下的ChildView操作, 比如getChildCount这个操作, 即应该有得到所有ChildView的版本,也应该有只得到所有可见ChildView的版本。ChildHelper就分别实现了两类办法,比如getChildCount返回可见**ChildView的数量,get**Unfiltered**ChildCount则返回了所有ChildView的数量。 同时,为了查询某个View是可见还是不可见的,还要维护一个不可见View的列表 mHiddenViews (**Bucket不方便提供这个信息,因为Bucket是以View的位置为key)

这样,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类方法即可。

发布了426 篇原创文章 · 获赞 48 · 访问量 64万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览