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
  1. 先看ItemDecoration是如何影响ChildView的测量:
  1. 在中我们已经比较完整的介绍了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
  1. 重新计算DecorInsets很简单,遍历ItemDecoration数组,调用其getItemOffsets获得该ItemDecoration在四个方向上的偏移,不断叠加,最终得到一个累加了所有ItemDecortaion的DecorInsets(一个Rect)
  1. measureChildWithMargins**处理DecorInsets的逻辑和角度类似于处于处理ChildView的Margin,最终体现在为ChildView生成的MeasureSpec中,从而影响了View的测量**
  1. 再看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影响面积大小(面积大小在上一步测量时应经被影响了),只影响位置
  1. 最后看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的细节,这里先不讨论。