RecyclerView(四):ItemDecoration的方法说明和实现原理

ItemDecoration是对item进行装饰的一个抽象类,只要实现它对应的方法就可以实现对应的功能,先来看下这个类:

public abstract static class ItemDecoration {
    // 在onDraw()方法中被调用到
    public void onDraw(Canvas c, RecyclerView parent, State state) {
        onDraw(c, parent);
    }

    /**
     * @deprecated
     * Override {@link #onDraw(Canvas, RecyclerView, RecyclerView.State)}
     */
    @Deprecated
    public void onDraw(Canvas c, RecyclerView parent) {
    }

    // RecyclerView重写了view的draw()方法,在draw()方法中被调用到,也就是当item被绘制完后会调用到这个方法(即最后被绘制)
    public void onDrawOver(Canvas c, RecyclerView parent, State state) {
        onDrawOver(c, parent);
    }

    /**
     * @deprecated
     * Override {@link #onDrawOver(Canvas, RecyclerView, RecyclerView.State)}
     */
    @Deprecated
    public void onDrawOver(Canvas c, RecyclerView parent) {
    }


    /**
     * @deprecated
     * Use {@link #getItemOffsets(Rect, View, RecyclerView, State)}
     */
    @Deprecated
    public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
        outRect.set(0, 0, 0, 0);
    }

   // 会在测量中被调用到,outRect中的left,top,right,bottom四个距离最终会被加到margin中,可以理解为就是设置item的margin值
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
        getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
                parent);
    }
}

getItemOffsets()的调用时机和设置说明:

// 测量view所需要的空间大小
public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

			// getItemOffsets()中设置的值会在下面这个方法中被调用到
            final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
            // 计算长宽方面的总值
            widthUsed += insets.left + insets.right;
            heightUsed += insets.top + insets.bottom;
			// 计算childView长所需要的总空间大小
            final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
                    getPaddingLeft() + getPaddingRight()
                            + lp.leftMargin + lp.rightMargin + widthUsed, lp.width,
                    canScrollHorizontally());
            // 计算childView高所需要的总空间大小
            final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
                    getPaddingTop() + getPaddingBottom()
                            + lp.topMargin + lp.bottomMargin + heightUsed, lp.height,
                    canScrollVertically());
            if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
                child.measure(widthSpec, heightSpec);
            }
        }

	// 这里就是去收集getItemOffsets()返回的值
    Rect getItemDecorInsetsForChild(View child) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        if (!lp.mInsetsDirty) {
            return lp.mDecorInsets;
        }

        if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
            // changed/invalid items should not be updated until they are rebound.
            return lp.mDecorInsets;
        }
        // 注意这里,insets变量使用的是LayoutManager中的mDecorInsets变量,所以下次使用的时候直接使用LayoutManager中的mDecorInsets就可以了
        final Rect insets = lp.mDecorInsets;
        insets.set(0, 0, 0, 0);
        final int decorCount = mItemDecorations.size();
        // 这里就是遍历所有添加进来的ItemDecoration
        for (int i = 0; i < decorCount; i++) {
            mTempRect.set(0, 0, 0, 0);
            mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
            insets.left += mTempRect.left;
            insets.top += mTempRect.top;
            insets.right += mTempRect.right;
            insets.bottom += mTempRect.bottom;
        }
        lp.mInsetsDirty = false;
        return insets;
    }

从这里还看不出getItemOffsets()设置的就是margin,这里只是计算出了item所需要的空间大小,再布局的时候还有一个layoutDecoratedWithMargins()方法:

        public void layoutDecoratedWithMargins(View child, int left, int top, int right,
                int bottom) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final Rect insets = lp.mDecorInsets;
            child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,
                    right - insets.right - lp.rightMargin,
                    bottom - insets.bottom - lp.bottomMargin);
        }

从这里就可以看出,getItemOffsets()设置的值最终都是会加到margin中去,这也就说明了getItemOffsets()中设置的值其实可以理解为设置margin值。
onDraw()和onDrawOver()的区别
在我看来,这两个方法的最主要区别就是调用的先后顺序,onDraw()先于onDrawOver()调用,下面来看下RecyclerView中代码的实现:

    @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);
        }
		... ...
    }

    @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);
        }
    }

在view的方法中调用了onDraw()方法,上面的draw()方法先调用了super.draw(),这也就是说会先调用到onDraw(),接着调用的就是onDrawOver()方法,这样看,是不是整个逻辑就比较清晰了。如果想来个例子,可以看看
android v7包里面提供的DividerItemDecoration这个类,它实现的是在item之间添加分割线,到此ItemDecoration就结束了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值