Android ItemDecoration

01.ItemDecoration的用途

1.1 作用

  • 通过设置recyclerView.addItemDecoration(new DividerDecoration(this));来改变Item之间的偏移量或者对Item进行装饰。
  • 当然,你也可以对RecyclerView设置多个ItemDecoration,列表展示的时候会遍历所有的ItemDecoration并调用里面的绘制方法,对Item进行装饰。

1.2 RecyclerView.ItemDecoration是一个抽象类

  • 该抽象类常见的方法如下所示:

    1
    2
    3
    4
    5
    6
    public void onDraw(Canvas c, RecyclerView parent)
    装饰的绘制在Item条目绘制之前调用,所以这有可能被Item的内容所遮挡
    public void onDrawOver(Canvas c, RecyclerView parent)
    装饰的绘制在Item条目绘制之后调用,因此装饰将浮于Item之上
    public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent)
    与padding或margin类似,LayoutManager在测量阶段会调用该方法,计算出每一个Item的正确尺寸并设置偏移量。

02.addItemDecoration()源码分析

  • a.通过下面代码可知,mItemDecorations是一个ArrayList,我们将ItemDecoration也就是分割线对象,添加到其中。

    • 可以看到,当通过这个方法添加分割线后,会指定添加分割线在集合中的索引,然后再重新请求 View 的测量、布局、(绘制)。注意: requestLayout会调用onMeasure和onLayout,不一定调用onDraw!
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public void addItemDecoration(ItemDecoration decor) {
    addItemDecoration(decor, -1);
    }

    // 主要看这个方法
    public void addItemDecoration(ItemDecoration decor, int index) {
    if (mLayout != null) {
    mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or"
    + " layout");
    }
    if (mItemDecorations.isEmpty()) {
    setWillNotDraw(false);
    }
    if (index < 0) {
    mItemDecorations.add(decor);
    } else {
    // 指定添加分割线在集合中的索引
    mItemDecorations.add(index, decor);
    }
    markItemDecorInsetsDirty();
    // 重新请求 View 的测量、布局、绘制
    requestLayout();
    }
  • b.接着看下markItemDecorInsetsDirty这个方法做了些什么

    • 这个方法先获取所有子View的数量,然后遍历了 RecyclerView 和 LayoutManager 的所有子 View,再将其子 View 的 LayoutParams 中的 mInsetsDirty 属性置为 true,最后调用了 mRecycler.markItemDecorInsetsDirty()方法处理复用逻辑。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    void markItemDecorInsetsDirty() {
    final int childCount = mChildHelper.getUnfilteredChildCount();
    //先遍历了 RecyclerView 和 LayoutManager 的所有子 View
    for (int i = 0; i < childCount; i++) {
    final View child = mChildHelper.getUnfilteredChildAt(i);
    //将其子 View 的 LayoutParams 中的 mInsetsDirty 属性置为 true
    ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true;
    }
    //调用了 mRecycler.markItemDecorInsetsDirty(),
    //Recycler 是 RecyclerView 的一个内部类,就是它管理着 RecyclerView 的复用逻辑
    mRecycler.markItemDecorInsetsDirty();
    }
  • c.接着看下markItemDecorInsetsDirty()这个方法

    • 该方法就是获取RecyclerView 缓存的集合,然后遍历集合得到RecyclerView 的缓存单位是 ViewHolder,获取缓存对象,在获取到layoutParams,并且将其 mInsetsDirty 字段一样置为 true
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    void markItemDecorInsetsDirty() {
    //就是 RecyclerView 缓存的集合
    final int cachedCount = mCachedViews.size();
    for (int i = 0; i < cachedCount; i++) {
    //RecyclerView 的缓存单位是 ViewHolder,获取缓存对象
    final ViewHolder holder = mCachedViews.get(i);
    //获得 LayoutParams
    LayoutParams layoutParams = (LayoutParams) holder.itemView.getLayoutParams();
    if (layoutParams != null) {
    //将其 mInsetsDirty 字段一样置为 true
    layoutParams.mInsetsDirty = true;
    }
    }
    }
  • d.回过头在看看addItemDecoration中requestLayout方法

    • requestLayout 方法用一种责任链的方式,层层向上传递,最后传递到 ViewRootImpl,然后重新调用 view 的 measure、layout、draw 方法来展示布局
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    @CallSuper
    public void requestLayout() {
    if (mMeasureCache != null) mMeasureCache.clear();

    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
    // Only trigger request-during-layout logic if this is the view requesting it,
    // not the views in its parent hierarchy
    ViewRootImpl viewRoot = getViewRootImpl();
    if (viewRoot != null && viewRoot.isInLayout()) {
    if (!viewRoot.requestLayoutDuringLayout(this)) {
    return;
    }
    }
    mAttachInfo.mViewRequestingLayout = this;
    }
    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    mPrivateFlags |= PFLAG_INVALIDATED;
    if (mParent != null && !mParent.isLayoutRequested()) {
    mParent.requestLayout();
    }
    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
    mAttachInfo.mViewRequestingLayout = null;
    }
    }
  • e.在 RecyclerView 中搜索 mItemDecorations 集合

    • 在onDraw中
    1
    2
    3
    4
    5
    6
    7
    8
    @Override
    public void onDraw(Canvas c) {
    super.onDraw(c);
    final int count = mItemDecorations.size();
    for (int i = 0; i < count; i++) {
    mItemDecorations.get(i).onDraw(c, this, mState);
    }
    }
    • 在draw方法中
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Override
    public void draw(Canvas c) {
    super.draw(c);

    final int count = mItemDecorations.size();
    for (int i = 0; i < count; i++) {
    mItemDecorations.get(i).onDrawOver(c, this, mState);
    }
    //省略部分代码
    }
    • 总结概括
      • 可以看到在 View 的以上两个方法中,分别调用了 ItemDecoration 对象的 onDraw onDrawOver 方法。
      • 这两个抽象方法,由我们继承 ItemDecoration 来自己实现,他们区别就是 onDraw 在 item view 绘制之前调用,onDrawOver 在 item view 绘制之后调用。
      • 所以绘制顺序就是 Decoration 的 onDraw,ItemView的 onDraw,Decoration 的 onDrawOver。