RecyclerView分割线详解

具体的原理可以参考底部留下的第一个地址
用法可以参考第二个地址


1. 添加RecyclerView分割线,继承自RecyclerView.ItemDecoration

  1. 可实现3个方法
  • onDraw()
  • getItemOffsets()
  • onDrawOver()

方法间相互关系

1. 方法执行的顺序为:

getItemOffsets() 执行4次 –> onDraw() 执行1次 –> child view onDraw() 执行1次 –> onDrawOver() 执行1次

通过Log日志可以看到
getItemOffsets()  position = 0
getItemOffsets()  position = 1
getItemOffsets()  position = 2
getItemOffsets()  position = 3
onDraw()
onDrawOver()
2. 方法的作用:
2.1 onDraw()
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {...}

顾名思义,跟View的onDraw()方法一样,分割线的绘制,这个方法中有熟悉的Canvas对象,拿它来直接画就行。但是画的位置需要通过获取RecyclerView的item来确定。

比如在第一个item的上面我要个20dp的分割线:
c.drawRect(childView.getLeft(), childView.getTop() - 20dp, childView.getRight(), childView.getTop(), mPaint);
childView就是这个第一项item,根据这个item的上下左右的位置来确定分割线的位置,从而绘制分割线。而item的位置首先是按照原始的排列,比如本例中的从上到下,依次排列,item中间没有空隙。但是在getItemOffsets()方法中通过Rect的设置来增加item的左上右下的Padding值从而与其他item产生距离。

在上面方法的执行流程中可以看到,getItemOffsets()方法会首先执行多次,而在方法中设置的outRect左上右下的值每个item被onMeasure()测量方法间接调用 加在item的Padding中,从而item之间会产生间隔。
然后调用分割线的onDraw()方法绘制
然后每个item的onDraw()方法调用
然后onDrawOver()方法调用
由此可知,在分割线的onDraw()方法中绘制的内容可能会被item的onDraw()中绘制的内容挡住,而onDrawOver()方法中绘制的内容会在最上层。
2.2 getItemOffsets()
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {...}

如上所讲,在getItemOffsets()方法中通过Rect的设置来增加item的左上右下的Padding值。

outRect.set(0, 20dp, 0, 10dp);
就是在item顶部与底部分别在padding中加相应的值。
2.3 onDrawOver()
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {...}

与onDraw()方法相同,只是执行的顺序不同。

talk is cheap show me the code
功能描述:
1. 分为两部分,第一部分是上面的一行行选项,第二部分是下面的提示,其中选项可以有一个或多个,而提示只有一个
2. 分割线主要体现在选项中,第一个选项距顶部30dp,最后一个选项距底部20dp,选项之间相距10dp。
mRecyclerView.addItemDecoration(mDividerItemDecoration);
mDividerItemDecoration.setItemCount(3);
class DividerItemDecoration extends RecyclerView.ItemDecoration {
        private final float TOP_DIVIDER_HEIGHT = getResources().getDimension(R.dimen.promote_limit_list_top_divider_height);
        private final float MIDDLE_DIVIDER_HEIGHT = getResources().getDimension(R.dimen.promote_limit_list_middle_divider_height);
        private final float BOTTOM_DIVIDER_HEIGHT = getResources().getDimension(R.dimen.promote_limit_list_bottom_divider_height);

        private int mItemCount;

        Paint mPaint = new Paint();

        public void setItemCount(int itemCount) {
            this.mItemCount = itemCount + 1;
        }

        @Override
        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
            if (mItemCount < 2) {
                return;
            }
            mPaint.setColor(getResources().getColor(R.color.promote_limit_divider_color));

            //获取RecyclerView中所有子项
            int count = parent.getChildCount();
            for (int i = 0; i < count; i++) {
                //获取RecyclerView中指定位置的子项
                View childView = parent.getChildAt(i);

                if (mItemCount == 2) {
                    //所有子项只有两项,既只有一个选项的时候
                    if (i == 0) {
                        //第一个选项要求距顶部30dp,距底部20dp
                        c.drawRect(childView.getLeft(), childView.getTop() - (int) TOP_DIVIDER_HEIGHT, childView.getRight(), childView.getTop(), mPaint);
                        c.drawRect(childView.getLeft(), childView.getBottom(), childView.getRight(), childView.getBottom() + BOTTOM_DIVIDER_HEIGHT, mPaint);
                    }
                } else {
                    //所有子项大于两项,既多于一个选项的时候
                    if (i == 0) {
                        //第一个选项要求距顶部30dp,距底部10dp
                        c.drawRect(childView.getLeft(), childView.getTop() - (int) TOP_DIVIDER_HEIGHT, childView.getRight(), childView.getTop(), mPaint);
                        c.drawRect(childView.getLeft(), childView.getBottom(), childView.getRight(), childView.getBottom() + MIDDLE_DIVIDER_HEIGHT, mPaint);
                    } else if (i == count - 2) {
                        //最后一个选项要求距底部20dp,最后一个选项是所有子项的倒数第二个
                        c.drawRect(childView.getLeft(), childView.getBottom(), childView.getRight(), childView.getBottom() + BOTTOM_DIVIDER_HEIGHT, mPaint);
                    } else if (i == count - 1) {
                        //最后一项是提示,不做处理
                    } else {
                        //其他选项,距底部10dp
                        c.drawRect(childView.getLeft(), childView.getBottom(), childView.getRight(), childView.getBottom() + MIDDLE_DIVIDER_HEIGHT, mPaint);
                    }
                }
            }
        }

        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
            int position = params.getViewAdapterPosition();

            if (mItemCount < 2) {
                return;
            }
            //与上面的逻辑一样
            if (mItemCount == 2) {
                if (position == 0) {
                    outRect.set(0, (int) TOP_DIVIDER_HEIGHT, 0, (int) BOTTOM_DIVIDER_HEIGHT);
                }
            } else {
                if (position == 0) {
                    outRect.set(0, (int) TOP_DIVIDER_HEIGHT, 0, (int) MIDDLE_DIVIDER_HEIGHT);
                } else if (position == mItemCount - 2) {
                    outRect.set(0, 0, 0, (int) BOTTOM_DIVIDER_HEIGHT);
                } else if (position == mItemCount - 1) {
                } else {
                    outRect.set(0, 0, 0, (int) MIDDLE_DIVIDER_HEIGHT);
                }
            }
        }

        @Override
        public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
            super.onDrawOver(c, parent, state);
            Log.d("myinfo", "onDrawOver");
        }
    }
参考:
https://blog.piasy.com/2016/03/26/Insight-Android-RecyclerView-ItemDecoration/
87
http://ju.outofmemory.cn/entry/249642
https://gold.xitu.io/entry/58482343128fe1006c6291c1