首先一个问题,什么是ItemDecoration呢?
有道翻译来看,Decoration翻译为“装饰”。那顾名思义,就是用来装饰我们的Item的类。
下面将针对ItemDecoration中的几个方法进行详细的讲解。
1、getItemOffsets
/**
* 执行ItemCount次
*
* @param outRect 操作item的边距
* @param view itemView
* @param parent recyclerVIew
* @param state recyclerView当前的状态
*/
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
}
这个方法主要用来对Item进行偏移,类似于在xml中设置的padding的效果,大多数的时候我们都会在这里偏移出我们需要画的图形的范围出来。对于第一个参数outRect,表示的是包含Item的一个矩形,具体可以参考下图。
图中黄色区域表示的是我们Item所占据并显示的区域,而当我们对outRect设置了属性之后,就可以得到蓝色的区域。
2、onDraw
/**
* 绘制的View会被Item覆盖
* 只执行一次
*
* @param c 画布
* @param parent recyclerView
* @param state recyclerView的状态
*/
@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
}
看到这个方法,了解过View的绘制流程的都会觉得很熟悉吧,这里和View中的onDraw类似,都提供了一个Cancas对象给我们去自由发挥。不过不同的是,这里的canvas起始坐标点(0,0)所在的位置不是ItemView的左上角,而是上图中outRect的左上角。结合上图不难发现,我们在这个方法中绘制的图像只能在蓝色区域才能被看到,ItemView的区域将会遮挡在outRect之上。
3、onDrawOver
/**
* 绘制的View会覆盖在Item之上
* 只执行一次
*
* @param c 画布
* @param parent recyclerView
* @param state recyclerView的状态
*/
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
}
与onDraw基本一样的方法,只不过此处绘制的图像将会覆盖在ItemView之上。
关于ItemDecoration的基本介绍就结束了,下面的话我将用一个时间轴的例子来展示一下在日常编码时ItemDecoration的作用。
看了效果图,我们先将每一项需要偏移的距离给计算出来。创建一个类继承自RecyclerView.ItemDecoration,并重写其中的getItemOffsets和onDraw方法。
在getItemOffsets中,我们进行偏移,预留出我们的画布区域。
public class TimeDecoration extends RecyclerView.ItemDecoration {
private int screenWidth;//屏幕宽度
private int radius = 20;//圆半径
private Paint paint;
public TimeDecoration() {
paint = new Paint();
}
...
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
screenWidth = CommonUtil.getScreenWH(parent.getContext())[0];
int marginLeft = 50;//itemView和时间轴之间的外边距
int left = screenWidth / 2 + radius + marginLeft;
int bottom = 50;
outRect.set(left,0,0,bottom);
}
}
放张图,比较容易理解这些距离是怎么计算出来的,其中获取屏幕宽度用的是一个工具类,大家可以去百度怎么获取。

下一步我们将在onDraw中绘制出时间轴。
首先我们先将每个时间轴对应的实心圆画出来,坐标的计算的话可以根据代码以及之前给的分解图自行理解。
@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDraw(c, parent, state);
c.drawColor(Color.RED);
//循环当前所有显示出来的ItemView
int len = parent.getChildCount();
View childView, nextChildView;
float centerX = screenWidth / 2f - radius;//圆心X坐标
float centerY;
for (int i = 0; i < len; i++) {
childView = parent.getChildAt(i);
centerY = childView.getY() + radius;//圆心Y坐标
//画圆
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.FILL);
c.drawCircle(centerX,centerY,radius,paint);
}
}

然后我们再将每个实心圆用一条线(实际上是一个高>宽的矩形)连接起来。
@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDraw(c, parent, state);
//c.drawColor(Color.RED);
//循环当前所有显示出来的ItemView
int len = parent.getChildCount();
View childView, nextChildView = null;
float centerX = screenWidth / 2f - radius;//圆心X坐标
float centerY;
int lineWidth = 10;//线宽
for (int i = 0; i < len; i++) {
childView = parent.getChildAt(i);
centerY = childView.getY() + radius;//圆心Y坐标
//画线
float rect_left = centerX - (lineWidth / 2f);
float rect_top = centerY + radius;
float rect_right = rect_left + lineWidth;
float rect_bottom;
if (i < len - 1) {
//不是最后一项,则线的高度默认为当前项到下一项之间的高度
nextChildView = parent.getChildAt(i + 1);
rect_bottom = nextChildView.getY();
}else {
//最后一项由于没有下一项,所以线的高度需要自己定义一个
rect_bottom = centerY + radius + 50;
}
paint.setColor(Color.BLUE);
c.drawRect(rect_left, rect_top, rect_right, rect_bottom, paint);
//画圆
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL);
c.drawCircle(centerX, centerY, radius, paint);
}
}
到此,一个时间轴就做好啦。放下最终的效果图。
