RecyclerView.ItemDecoration源码浅析

/ Android基础控件 / 没有评论 / 402浏览

前言

RecyclerView目前来说,是日常开发中使用最多的控件,功能强大而且复杂。而Item Decoration作为RecyclerView开发过程中不可或缺的部分,需要深入的了解一下。

源码分析

    /**
     * An ItemDecoration allows the application to add a special drawing and layout offset
     * to specific item views from the adapter's data set. This can be useful for drawing dividers
     * between items, highlights, visual grouping boundaries and more.
     *
     * <p>All ItemDecorations are drawn in the order they were added, before the item
     * views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView, RecyclerView.State) onDraw()}
     * and after the items (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView,
     * RecyclerView.State)}.</p>
     */
    public abstract static class ItemDecoration {
        /**
         * Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
         * Any content drawn by this method will be drawn before the item views are drawn,
         * and will thus appear underneath the views.
         *
         * @param c Canvas to draw into
         * @param parent RecyclerView this ItemDecoration is drawing into
         * @param state The current state of RecyclerView
         */
        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) {
        }

        /**
         * Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
         * Any content drawn by this method will be drawn after the item views are drawn
         * and will thus appear over the views.
         *
         * @param c Canvas to draw into
         * @param parent RecyclerView this ItemDecoration is drawing into
         * @param state The current state of RecyclerView.
         */
        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);
        }

        /**
         * Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies
         * the number of pixels that the item view should be inset by, similar to padding or margin.
         * The default implementation sets the bounds of outRect to 0 and returns.
         *
         * <p>
         * If this ItemDecoration does not affect the positioning of item views, it should set
         * all four fields of <code>outRect</code> (left, top, right, bottom) to zero
         * before returning.
         *
         * <p>
         * If you need to access Adapter for additional data, you can call
         * {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the
         * View.
         *
         * @param outRect Rect to receive the output.
         * @param view    The child view to decorate
         * @param parent  RecyclerView this ItemDecoration is decorating
         * @param state   The current state of RecyclerView.
         */
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
            getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
                    parent);
        }
    }

注意看英文注释,ItemDecoration是用于装饰RecyclerView的Item的,主要关注如下三个方法

getItemOffsets

|参数|意义| |--|--| |outRect|ItemView的外围矩形| |view|ItemView| |parent|RecyclerView,ItemView的Parent| |state|RecyclerView的状态|

直接这样子说看不出效果,上几张图区分一下

        outRect.top =20;
        outRect.bottom =20;

        outRect.left=60;
        outRect.right =60;

这里写图片描述

        outRect.top =20;
        outRect.bottom =20;

        outRect.left=60;
        outRect.right =0;

这里写图片描述

        outRect.top =60;
        outRect.bottom =60;

        outRect.left=20;
        outRect.right =20;

这里写图片描述 看上去设置outRect设置top,left,right,bottom有一种padding的效果,但是实际上只是它是ItemView外围的空间,你可以将其理解成RecyclerView针对每个ItemView设置的Padding,而不是ItemView自身的padding。

onDraw

|参数|意义| |--|--| |c|RecyclerView的Canvas| |parent|RecyclerView,ItemView的Parent| |state|RecyclerView的状态|

onDraw操作是在RecyclerView所在的画布上绘制内容,也就是在ItemView的背后绘制内容,重叠部分会被ItemView遮挡。 比如我们在Item View的背后画一个圆圈圈。

    public YdDividerItemDecoration(){
        mPaint = new Paint();
        mPaint.setColor(Color.GREEN);
        mPaint.setAntiAlias(true);
    }


    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        Logger.d("yidong -- onDraw");

        int width = parent.getWidth();
        int height = parent.getHeight();

        int radius = Math.min(width, height)/2;

        c.drawCircle(width/2, height/2, radius, mPaint);
    }

这里写图片描述 这个是为了说明,我们操作的画布是RecyclerView的画布,而不是针对ItemView,所以一般情况下,我们需要针对每个ItemView做操作,比如画分割线,官方给出的DividerItemDecoration的onDraw方法


    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        if (parent.getLayoutManager() == null || mDivider == null) {
            return;
        }
        if (mOrientation == VERTICAL) {
            drawVertical(c, parent);
        } else {
            drawHorizontal(c, parent);
        }
    }
   private void drawVertical(Canvas canvas, RecyclerView parent) {
        canvas.save();
        final int left;
        final int right;
        //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
        if (parent.getClipToPadding()) {
            left = parent.getPaddingLeft();
            right = parent.getWidth() - parent.getPaddingRight();
            canvas.clipRect(left, parent.getPaddingTop(), right,
                    parent.getHeight() - parent.getPaddingBottom());
        } else {
            left = 0;
            right = parent.getWidth();
        }

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            parent.getDecoratedBoundsWithMargins(child, mBounds);
            final int bottom = mBounds.bottom + Math.round(child.getTranslationY());
            final int top = bottom - mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(canvas);
        }
        canvas.restore();
    }

可以看出都是便利RecyclerView的Item来做操作的。

onDrawOver

|参数|意义| |--|--| |c|RecyclerView的Canvas| |parent|RecyclerView,ItemView的Parent| |state|RecyclerView的状态|

参数类型和意义和onDraw一模一样,但是效果确实在RecyclerView的ItemView之上。 还是换个圆圈圈,代码和上面一样,只是移动到onDrawOver方法下执行。 这里写图片描述 可以很明显的看到,我们的圆圈圈覆盖了ItemView。至于谁覆盖谁,显然是绘制顺序的问题。 这里写图片描述 可以看到执行顺序,onDrawOver是在onDraw之后的,但是ItemView的绘制是否在这个两个操作之前,从最后的效果来看,是的,但是需要代码证实。直接看RecyclerView绘制相关的方法即可。

    @Override
    public void draw(Canvas c) {
        //这里是RecyclerView的绘制过程,和View的绘制过程类似,先是背景,然后是调用
        //recyclerview的onDraw方法绘制自身,然后通过dispatchDraw来绘制子View
        // 而onDraw(下面)执行的就是ItemDecoration的onDraw方法,所以顺序很明显了。
        //ItemDecoration-> ItemView -> ItemDecoration.onDrawOver
        super.draw(c);
		// 执行ItemDecoration onDrawOver方法,最顶层
        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);

        // 执行ItemDecoration的onDraw方法,最底层
        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDraw(c, this, mState);
        }
    }

总结

看清楚了ItemDecoration的实现原理,具体通过什么方式绘制出什么样的图形,应该只是设计和时间的问题,Github上有不错的一些实现,大家有空可以去学习借鉴一下。