自定义RecyclerView分割线

现在自己所写的项目中用到列表的地方基本全部使用的是RecyclerView,而使用列表就会有非常大的几率用到分割线功能。以前在用到RecyclerView的分割线功能的时候,都是利用RecyclerView自身设置的背景色和每个Item的背景色的差异,然后继承RecyclerView.ItemDecoration,并且简单的重写
getItemOffsets (Rect outRect, View view, RecyclerView parent, RecyclerView.State state)
方法就能达到效果了。简单实现一个分割线效果代码如下

  public void getItemOffsets (Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        switch (mOrientation) {
            case LinearLayoutManager.HORIZONTAL:
                outRect.right += mSpace;
                if (parent.getChildAdapterPosition (view) == 0)
                    outRect.left += mSpace;
                break;
            case LinearLayoutManager.VERTICAL:
                outRect.bottom += mSpace;
                if (parent.getChildAdapterPosition (view) == 0)
                    outRect.top += mSpace;
                break;
        }
    }

这样就能实现最简单的一个分割线的效果了。可是今天在写项目的时候遇到了分割线颜色可能会变动的情况。因此上面这种简单的方式就不能满足要求了。

首先,Google了一番发现,原来ItemDecoration能做的事远远不止这么简单,它还能定制出更多的酷炫的效果,不仅能在“Item下面”绘制分割线效果,还能在“Item上面”盖章。要定制更多的效果就要重写ItemDecoration的另外两个方法
onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)

onDrawOver (Canvas c, RecyclerView parent, RecyclerView.State state)
onDraw方法要比Item 的onDraw方法先执行,所以它画出的东西总是在Item的下面,在 onDraw 为 divider 设置绘制范围,并绘制到 canvas 上,而这个绘制范围可以超出在 getItemOffsets 中设置的范围,但由于 decoration 是绘制在 childView 的底下,所以并不可见,但是会存在overDraw。而onDrawOver 方法是在Item的onDraw执行之后才执行,所以onDrawOver画出的东西是在Item的上面。仔细学习了一下onDrawOver方法,简直像发现了新大陆一样。此次项目中,有个列表左边需要展示一张图片,中间是4行文字描述,然后在右边还是一张图片,不过这张图片要有盖在文字上的效果类似于下面这样

自定义RecyclerView分割线_第1张图片
没有原型,自己画了个

我原本是用xml布局文件拼凑了这个效果。但是,仔细研究了onDrawOver方法后,发现用onDrawOver也可以很好的实现此效果。xml文件代码行数固然减少了很多而且加载效率肯定也有所提升,不过最重要的是感觉B格瞬间上升了很大一截
自定义RecyclerView分割线_第2张图片

下面先放上自定义ItemDecoration的完整代码

public class MyItemDecoration extends RecyclerView.ItemDecoration {
    private Drawable mDivider;
    private int mSize;
    private int mOrientation;
    private Paint mPaint;
    private Bitmap bitmap;

    public MyItemDecoration (Context context, int color, int drawableId, int size, int orientation) {
        mDivider = new ColorDrawable (color);
        mSize = size;
        mOrientation = orientation;
        bitmap = BitmapFactory.decodeResource (context.getResources (), drawableId);

        mPaint = new Paint ();
        mPaint.setColor (Color.RED);
        mPaint.setStyle (Paint.Style.FILL);
    }

    @Override
    public void getItemOffsets (Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
      switch (mOrientation){
            case  LinearLayoutManager.HORIZONTAL:
                outRect.right += mSize;
                break;
            case LinearLayoutManager.VERTICAL:
                outRect.bottom += mSize;
                break;
        }
    }


    @Override
    public void onDraw (Canvas c, RecyclerView parent, RecyclerView.State state) {
        int left, right, top, bottom;
        if (mOrientation == LinearLayoutManager.VERTICAL) {
            left = parent.getPaddingLeft ();
            right = parent.getWidth () + parent.getPaddingRight ();
            int count = parent.getChildCount ();
            for (int i = 0; i < count; i++) {
                View child = parent.getChildAt (i);
                RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams ();
                top = child.getBottom () + params.bottomMargin;
                bottom = top + mSize;
                mDivider.setBounds (left, top, right, bottom);
                mDivider.draw (c);
            }
        }
    }

    @Override
    public void onDrawOver (Canvas c, RecyclerView parent, RecyclerView.State state) {
        int left, top;
        if (mOrientation == LinearLayoutManager.VERTICAL) {
            int childCount = parent.getChildCount ();
            for (int i = 0; i < childCount; i++) {
                View child = parent.getChildAt (i);
                left = child.getWidth () - bitmap.getWidth () - 15;
                top = child.getBottom () / 2 + (child.getBottom () / 2 * i) / (i + 1) - bitmap.getHeight () / 2;
                c.drawBitmap (bitmap, left, top, mPaint);
            }
        }
    }
}

下面是自己测试的实现效果


自定义RecyclerView分割线_第3张图片

感觉很完美,右边“异常”标签的位置也可以通过计算调整。

在自己摸索着写这个MyItemDecoration 的过程中,遇到了个问题一并记录一下。以前都是简单的用,并没有深入的了解过ItemDecoration,也算给自己提个醒,以后不管学习还是工作,尽量多多的去延伸知识面
问题:只重写onDraw方法也可以实现分割线的效果,那么getItemOffsets 的作用是又是什么呢?
在遇到这个问题的时候,我觉得我占了一点好运气,因为我给每个Item都设置了一个边框,如果没有这个边框,可能我会以为只重写onDraw的效果就是正确的,因为它“看起来”的效果确实是正确的。只重写onDraw方法的时候出现的效果是这样的

自定义RecyclerView分割线_第4张图片
不重写getItemOffsets方法.png

有分割线的效果,但是边框却“越过”了分割线和下一个Item相连了,于是,我又重写getItemOffsets方法,此时的效果就正常了,如下
自定义RecyclerView分割线_第5张图片
设置分割线.png

可是,为什么会出现“越过”分割线的效果呢?经过我自己多次对照代码运行测试后发现,画第一个分割线的开始位置是这样计算的

public void onDraw (Canvas c, RecyclerView parent, RecyclerView.State state) {
           ......
     for (int i = 0; i < childCount; i++) {
          View child = parent.getChildAt (i);
          top = child.getBottom () + params.bottomMargin;
          bottom = top + mSize;
           ......
        }

取得Item1.getBottom()作为第一条分割线的top,然后让top加上设置的分割线的高度mSize作为第一条分割线的bottom,余下的分割线以此类推,此时不重写getItemOffsets的情况下,画个草图更加一目了然


自定义RecyclerView分割线_第6张图片
我是草图.png

虽然通过onDraw方法画出了分割线的效果,但是由于没有重写getItemOffsets()方法,所以RecyclerView的每个Item还是按照原始的布局位置设置,从3个红色箭头也可以看出
图片顶部到Item1 top的距离 > 图片到Item(x) top的距离,(x=2,3,4...)
这样从Item2开始每个Item的顶部都少了一截mSize大小的高度,这就也解释了为什么会出现边框“越过”分割线的原因

你可能感兴趣的:(自定义RecyclerView分割线)