【Android】学习ItemDecoration并制作时间轴效果

首先一个问题,什么是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的一个矩形,具体可以参考下图。

outRect

图中黄色区域表示的是我们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);
        }
    }

到此,一个时间轴就做好啦。放下最终的效果图。

最终效果图
最终效果图

所有代码已打包上传的GitHub。

发布了24 篇原创文章 · 获赞 7 · 访问量 8692

猜你喜欢

转载自blog.csdn.net/d745282469/article/details/98945502