• 时间轴、流程类时间轴绘制


    效果图

    时间轴效果图
    • 可控制是否绘制在中间
    • 控制绘制的线条是否为虚线
    • 控制第一条数据圆顶部线条和最后一条数据圆底部线条是否绘制

    除了gif图片展示的属性,还可以控制圆的大小颜色、圆是否有上和左偏移、线条颜色等属性

    除了通用的时间轴绘制,我们还可以通过改变绘制圆的样式,改为绘制相应的bitmap图像,来实现展示相关的流程

    流程类时间轴效果图

    思路

    关于ItemDecoration相关的内容已经写了不少,这个其实就是小菜一碟。我们需要做的工作有两点

    • ItemDecoration在getItemOffsets()方法内做相应的偏移
    • onDraw()方法内分别绘制圆、圆顶部线条、圆底部线条
      • 绘制线条,我们需要知道start和end的点坐标;
      • 绘制圆,我们需要知道圆心和半径;

    通过下图,你将能清楚地获取到这些绘制需要的一些信息

    image

    具体实现

    有了以上内容,我们开始绘制

    步骤一:ItemView顶部偏移

     override fun getItemOffsets(
            outRect: Rect,
            view: View,
            parent: RecyclerView,
            state: RecyclerView.State,
        ) {
            if (parent.getChildAdapterPosition(view) != 0) {
                //第一个不做顶部偏移
                outRect.top = topItemSpace
            }
            outRect.left = leftItemSpace
    
        }
    

    步骤二:绘制圆和线条

    override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
            //获取到的是当前屏幕可见的个数
            val childCount = parent.childCount
    
            for (i in 0 until childCount) {
                val view = parent.getChildAt(i)
                //获取真实的在整体数据中的位置
                val index=parent.getChildAdapterPosition(view)
    
                //ItemView左侧+偏移的矩形框(绿色框部分)
                val spaceRectTop = if (index == 0) view.top else view.top - topItemSpace
                val spaceRectBottom = view.bottom
                val spaceRectLeft = view.left - leftItemSpace
                val spaceRectRight = view.left
    
                //ItemView左侧,不包含偏移的矩形框(红色框部分)
                val dataRectLeft = view.left - leftItemSpace
                val dataRectTop = view.top
                val dataRectRight = view.left
                val dataRectBottom = view.bottom
    
                //圆心坐标
                var centerX = if(isDrawAtMiddle) (dataRectLeft + dataRectRight)/ 2  else (dataRectLeft + dataRectRight)/ 2 + circleLeftPadding
                val centerY =
                    if (isDrawAtMiddle) (dataRectTop + dataRectBottom) / 2 else dataRectTop + circleRadius + circleTopPadding
    
                //绘制第一条线
                if (index==0){
                    if (isDrawFirstItemTopLine){
                        c.drawLine(
                            centerX.toFloat(),
                            spaceRectTop.toFloat(),
                            centerX.toFloat(),
                            (centerY - circleRadius).toFloat(),
                            mLinePaint
                        )
                    }
                }else{
                    c.drawLine(
                        centerX.toFloat(),
                        spaceRectTop.toFloat(),
                        centerX.toFloat(),
                        (centerY - circleRadius).toFloat(),
                        mLinePaint
                    )
                }
                //绘制圆(居中显示)
                c.drawCircle(centerX.toFloat(), centerY.toFloat(), circleRadius.toFloat(), mCirclePaint)
    
    
                //绘制第二条线,注意这里要用itemCount,因为上面的childCount是当前页面可见的个数
                parent.adapter?.let {
                    if (index!=it.itemCount-1){
                        c.drawLine(
                            centerX.toFloat(),
                            (centerY + circleRadius).toFloat(),
                            centerX.toFloat(),
                            spaceRectBottom.toFloat(),
                            mLinePaint
                        )
                    }else{
                        if (isDrawLastItemBottomLine){
                            c.drawLine(
                                centerX.toFloat(),
                                (centerY + circleRadius).toFloat(),
                                centerX.toFloat(),
                                spaceRectBottom.toFloat(),
                                mLinePaint
                            )
                        }
                    }
                }
            }
        }
    

    注意:下标的获取

    因为我们需要每个ItemView都绘制,所以需要使用循环。但因为val childCount = parent.childCount获取到的是当前页面可见的个数,并不是实际的个数,所以我们在判断是否是首条或者最后一条数据时,那个index要通过val index=parent.getChildAdapterPosition(view)的方式来获取到真实的下标位置。

    流程类的绘制

    和绘制通用的圆类似,不过是将Canvas.drawCircle()改为Canvas.drawBitmap()。至于不同的bitmap的加载,我们可以通过传入集合的数据类型来判断绘制哪种图片即可。

    override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
    	...
    	val srcRect = Rect(0, 0, progressBitmap.width, progressBitmap.height)
    	val dstRect = Rect(
            centerX - circleRadius,
            centerY - circleRadius,
            centerX + circleRadius,
            centerY + circleRadius
        )
        c.drawBitmap(errorBitmap, srcRect, dstRect, mCirclePaint)
        ...
    }
    

    总结

    其实主要还是ItemDecoration相关的内容,在onDraw()方法内绘制圆、绘制bitmap和绘制线条,根据上面的图,知道具体的坐标位置,绘制就很轻松了,也可以在此基础上继续扩展,使得我们的时间轴ItemDecoration更加的通用,方便运用到项目中。

    如果本文对你有帮助,请别忘记三连,如果有不恰当的地方也请提出来,下篇文章见。

    时间轴效果图
  • 相关阅读:
    HC32L110(四) HC32L110的startup启动文件和ld连接脚本
    IntelliJ IDEA 记学习笔 Maven自动导包 Auto Import
    VMware16安装CentOS 7.9操作系统(Minimal版)
    Leetcode—2678.老人的数目【简单】
    数据结构—查找(顺序查找和折半查找)
    【浏览器】端数据库存储方案----indexDB、localForage
    【C++进阶学习】第一弹——继承(上)——探索代码复用的乐趣
    2022 PAT 甲级(秋季)
    MySQL - 数据库的监控方式
    启动spark-shell时报错java.lang.NumberFormatException: For input string: “0x100“
  • 原文地址:https://www.cnblogs.com/myfittinglife/p/18244244