RecyclerView机制解析: ItemDecoration

  1. RecyclerView的ItemDecoration机制相对比较简单,不过扩展性很强,在ChildView的测量和展示上为使用者提供了极大的发挥空间,像divider/项目高亮/项目边框等效果都可以轻松实现

  2. ItemDecoration可以同时存在复数个,维护在一个列表中,影响是可以叠加的,ItemDecoration的作用顺序从列表头到列表尾

    1. addItemDecoration可以添加一个ItemDecoration。
    2. removeItemDecoration可以删除一个ItemDecoration。
    3. invalidateItemDecorations表示当前的ItemDecoration发生了变化,需要更新。
    4. 因为RecyclerView**继承自ViewGroup,默认其willNoDraw是true(意味着其onDraw函数不会被调用),但是ItemDecoration的onDraw需要在RecyclerView的onDraw回调点进行,因此添加/删除ItemDecoraton时会根据情况改变willNotDraw属性**。
    5. 应为ItemDecoration会影响ChildView的测量,进而影响布局,还影响绘制,因此ItemDecoration的添加/删除/Invalidate会触发requestLayout来重新测量布局绘制.
    6. 在Scroll/Layout的过程中禁止添加/删除ItemDecoration
  3. 先看ItemDecoration是如何影响ChildView的测量:

    1. 在http://blog.csdn.net/fyfcauc/article/details/54291174中我们已经比较完整的介绍了RecyclerView的measure流程。
    2. ItemDecoration的影响在measureChildWithMargins(还有measureChild)这个关键的ChildView测量函数中体现
    3. measureChildWithMargins会在生成ChildView的测量约束MeasureSpec前去获取ChildView的DecorInsets: 通过getItemDecorInsetsForChild返回一个Rect,包含了Item在四个方向上的DecorInset
      1. getItemDecorInsetsForChild**以ChildView为粒度**
      2. 出于性能的考虑,如果之前为ChildView生成过DecorInsets,那么会缓存在ChildView的LayoutParam中(mDecorInsets), 同时为了保证mDecorInsets的时效性,还同步维护了一个mInsetsDirty标记在LayoutParam中:
      3. 在获取ChidlView的DecorInsets时,如果其mInsetsDirty为false,那么代表缓存没有过期,直接返回缓存的mDecorInsets
      4. 如果mInsetsDirty为true,那么代表缓存过期,需要根据ItemDecoration数组重新生成
        1. 因此,添加/删除ItemDecoration时,还会将所有ChildView(包括Recycler中的)的mInsetsDirty设置为true来使DecorInsets缓存失效
        2. 相关函数: markItemDecorInsetsDirty函数可以将当前的ChildView以及Recycler中的ChildView的mInsetsDirty设置为true
      5. 重新计算DecorInsets很简单,遍历ItemDecoration数组,调用其getItemOffsets获得该ItemDecoration在四个方向上的偏移,不断叠加,最终得到一个累加了所有ItemDecortaion的DecorInsets(一个Rect)
    4. measureChildWithMargins**处理DecorInsets的逻辑和角度类似于处于处理ChildView的Margin,最终体现在为ChildView生成的MeasureSpec中,从而影响了View的测量**
  4. 再看ItemDecoration是如何影响ChildView的布局:

    1. ItemDecoration影响ChildView布局的手法也和ChildView的Margin基本相同.
    2. LayoutManager会使用layoutDecoratedWithMargins(View child, int left, int top, int right, int bottom)来布局ChildView
    3. 传入layoutDecoratedWithMargins的left/top/right/bottom是在ChidlView尺寸的基础上叠加了DecorInsets和Margin
    4. 因此layoutDecoratedWithMargins**真正布局ChildView时会将这些不属于ChildView本身的偏移从left/top/right/bottom中”减掉”**。
    5. 这样ChildView在被真正布局时并不会被DecorInset影响面积大小(面积大小在上一步测量时应经被影响了),只影响位置
  5. 最后看ItemDecoration是如何影响绘制:

    1. RecyclerView的onDraw函数在有ItemDecoration存在的情况下会被调用
    2. 遍历调用ItemDecoration的onDraw回调, Canvas会作为参数传入, 同时还有RecyclerView和当前的LayoutState
    3. 由上面可见,onDraw回调的粒度就不是ChildView了,而是整个Canvas,可以在Canvas的任何位置绘制,不过注意,这里绘制的内容可能会被ChildView覆盖
    4. 如果不想被ChildView覆盖,就要使用ItemDecoration的onDrawOver回调了, onDrawOver在super.draw调用之后(ChildView已经绘制完毕)。同样的粒度也是整个Canvas。
    5. ItemDecoration还会影响动画或者滑动时Invalidate的细节,这里先不讨论。

你可能感兴趣的:(Android,Android,Layout,Android,Draw)