RecyclerView(2)ItemDecoration自定义 原理深度解析

上一篇的例子因为都是不同颜色的图片所以在设置之后能够看见图片之间的分割线,但是如果是纯白色,可能分割线看不出来,又或者需要各种样式(颜色和线条,比如虚线等),那就一定要自定义了,那怎么样去自定义呢?看过下面的分析后我相信对读者会有很大帮助,同时介绍一个个开源项目,大家可以去git上下载下来使用或者修改后使用:

https://github.com/yqritc/RecyclerView-FlexibleDivider

1、DividerItemDecoration

上一篇的简单使用中说到可以通过RecyclerView的addItemDecoration(ItemDecoration decor)方法来设置item之间的分割线。SDK目前提供了默认的分割线,在 android.support.v7.widget.DividerItemDecoration,下面是它的源码,看看开始的类注释说明,DividerItemDecoration是一种可用于items之间的分割线,如LinearLayoutManager,支持横向和竖向的。源代码不到200行,很简单,那么我们可以参照这个做一个分割线,并且在网格布局能同时使用横竖分割线,如上篇,可以看效果,或者下载git上的代码,自己修改看看效果,git仓库地址:https://github.com/cmyeyi/smm.git

2、DividerItemDecoration源码

加上注释和类库的引入全部就181行代码,可自行在AS中查看

 

3、自定义ItemDecoration

如何使用DividerItemDecoration,上面可以很简单的看明白,现在开始实践自定义。

自定义ItemDecoration需要继承它,下面是ItemDecoraction的源码,RecyclerView的一个内部类,它是一个抽象类,所有的方法需要我们自己实现,可以参考sdk提供的DividerItemDecoration的源码,甚至能够直接copy过来修改一下,

/**
 * 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);
    }
}

看了源码不知所措吧?

3.1、上面方法中其实只需要关注一下三个方法,(注意另外的三个重载方法都过时了)

public void onDraw(Canvas c, RecyclerView parent, State state)
public void onDrawOver(Canvas c, RecyclerView parent, State state) {
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)

3.1.1、onDraw

在提供给RecyclerView的Canvas中绘制适当的装饰。此方法绘制的任何内容都将在绘制项目视图之前绘制,因此将显示在视图下方。

3.1.2、onDrawOver

在提供给RecyclerView的Canvas中绘制适当的装饰。此方法绘制的内容将在绘制项目视图后绘制,并因此显示在视图上。

3.1.3、getItemOffsets
检索给定项目的偏移量。 outRect的每个字段指定项目视图应插入的像素数,类似于填充或边距。默认实现将outRect的边界设置为0并返回。 如果此ItemDecoration不影响项目视图的定位,则应在返回之前将outRect的所有四个字段(left,top,right,bottom)设置为零。如果需要访问Adapter以获取其他数据,可以调用RecyclerView getChildAdapterPosition(View)来获取View的适配器位置。

3.2、getItemOffsets修改该验证 

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
                           RecyclerView.State state) {
    if (mDivider == null) {
        Log.d("###","" + "mDivider == null");
        outRect.set(0, 0, 0, 0);
        return;
    }
    if (mOrientation == VERTICAL) {
        outRect.set(20, 0, 20, mDivider.getIntrinsicHeight());
        Log.d("###height","" + mDivider.getIntrinsicHeight());//08-29 14:59:28.995 14698-14698/com.kan.mz D/###height: 4
    } else {
        outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        Log.d("### width","" + mDivider.getIntrinsicWidth());
    }
}

修改前:

修改后

注意中间的两条间隔明显是左右window左右两边的宽度的2倍,所以这个效果是叠加的,

把 outRect.set(0, 0, 0, 0)改回到原来并且将最后一个参数修改为0,看看效果,会发现上下的细小分割线没有了,只有右边还有白色分割线。

sdk默认提供的分割线是right和bottom,宽度为4dp,我们从源码中是可以看到的,这里相当于调节view的padding。

总结一下,修改getItemOffsets就相当于修改view的padding一样。

3.3、onDraw修改该验证 

sdk提供的默认的DividerItemDecoration代码如下,参考分析,分水平和垂直方向绘制,选垂直方向绘制分析

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

现在修改红色部分代码:
Paint dividerPaint = new Paint();
dividerPaint.setColor(context.getResources().getColor(R.color.orange));
Log.d("#####","("+left+","+top+","+right+","+bottom+")");
canvas.drawRect(left, top, right, bottom, dividerPaint);
mDivider.draw(canvas);

然后看看效果:

分析

其实这里是使用画笔在canvas上作画,调用的方法是drawRect,上面前四的参数是通过计算所得,我们不能修改,这里要理解drawRect的含义,其实四个参数左右相对,上下相对,水平方向上从left画right,垂直方向上从top画到bottom,

log日志:

08-29 16:34:48.455 26211-26211/com.kan.mz D/#####: (0,640,1440,644)
08-29 16:34:48.455 26211-26211/com.kan.mz D/#####: (0,640,1440,644)
08-29 16:34:48.586 26211-26211/com.kan.mz D/#####: (0,640,1440,644)
08-29 16:34:48.587 26211-26211/com.kan.mz D/#####: (0,640,1440,644)

可以看到画的是垂直方向上的644 -640 = 4,与上面我们说的宽度4相符合。

稍作修改只是显示2个图片,并且修改,

canvas.drawRect(100, top, right, bottom, dividerPaint);

下面是修改后的显示,可以看到,left从100开始到1440结束,

总结

如果需要修改分割线的颜色偏移量等,可以通过onDraw方法修改,当然既然能够拿到canvas,能自定义Paint,那么我们队线条的形状样式也能按需修改;

3.4、onDrawOver修改该验证 

如果我們需要在指定图片的左上角添加东西,那么我们可以使用onDrawOver来实现,下面重写onDrawOver,看效果

@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
    super.onDrawOver(c, parent, state);
    int childCount = parent.getChildCount();
    for (int i = 0; i < childCount; i++) {
        View child = parent.getChildAt(i);
        int pos = parent.getChildAdapterPosition(child);
        if (pos % 3 == 0){
            Paint paint = new Paint();
            paint.setColor(context.getResources().getColor(R.color.gold));
            float left = child.getLeft() + 24;
            float right = left + 100;
            float top = child.getTop() + 24;
            float bottom = top + 100;
            c.drawRect(left, top, right, bottom, paint);
        }
    }

}

效果图:

分析和结论这里就不再说了,跟上面类似,只是onDraw相当于画背景,onDrawOver相当于画图层。

猜你喜欢

转载自blog.csdn.net/weixin_36709064/article/details/82078813