自定义一个带条纹的进度条(UI绘制)

290 阅读3分钟

前言

项目中有个等待页面需要有一个进度条的展示,是带条纹的样式,android自带进度条都是纯色的不太能满足要求。在网上找了一下也没有比较满意的轮子,于是干脆自己写一个进度条,顺便练习一下UI绘制相关技术。

期望效果如下所示:

xmyh2-segli.gif

分析要绘制的内容:

这个进度条主要分为三个部分,1,带圆角的灰色背景 2,表示当前进度的蓝色进度条 3,进度之上的灰色条纹。

开始绘制

(1)先定义绘制需要的参数


// 是否绘制条纹
private var isStripe = true

var progressBarHeight = 30f
var progressBarWidth = 500f

// 条纹宽度
var pathWidth = 15
    set(value) {
        field = value
        invalidate()
    }

// 条纹间隔宽度
var pathSpace = 45
    set(value) {
        field = value
        invalidate()
    }

var onProgressFinish: (() -> Unit)? = null

// 当前进度
private var currentProgress = 0

// 指示器图片
private var barDrawable = -1

// 动画时间
private var animDuration = 120 * 1000L

//灰色背景范围
private val bgRectF = RectF()

//蓝色前景范围
private val fontRectF: RectF by lazy {
    RectF()
}

//圆角值
private val radius: Float by lazy {
    bgRectF.height() / 2
}

(2)测量

val specModeW = MeasureSpec.getMode(widthMeasureSpec)
val specSizeW = MeasureSpec.getSize(widthMeasureSpec)

val specModeH = MeasureSpec.getMode(heightMeasureSpec)
val specSizeH = MeasureSpec.getSize(heightMeasureSpec)

var targetW = 0
var targetH = 0

if (specModeW == MeasureSpec.EXACTLY) {
    targetW = specSizeW
    progressBarWidth = targetW.toFloat()
} else if (specModeW == MeasureSpec.AT_MOST) {
    targetW = 1000
    progressBarWidth = targetW.toFloat()
}

if (specModeH == MeasureSpec.EXACTLY) {
    targetH = specSizeH
} else if (specModeH == MeasureSpec.AT_MOST) {
    targetH = if (isAnimFrame) {
        (progressBarHeight + drawableHeight).toInt()
    } else {
        (drawableHeight).toInt()
    }
}

if (isAnimFrame) {
    bgRectF.left = drawableWidth / 2f
    bgRectF.top = drawableHeight
    bgRectF.right = progressBarWidth - drawableWidth / 2f
    bgRectF.bottom = drawableHeight + progressBarHeight
} else if (isPointCenter) {
    bgRectF.left = drawableWidth / 2f
    bgRectF.top = drawableHeight / 2f
    bgRectF.right = progressBarWidth - drawableWidth / 2f
    bgRectF.bottom = progressBarHeight + progressBarHeight / 2f
} else {
    bgRectF.left = 0f
    bgRectF.top = drawableHeight / 2f
    bgRectF.right = progressBarWidth
    bgRectF.bottom = progressBarHeight + progressBarHeight / 2f
}
//测量主要是先确定进度条背景的范围
setMeasuredDimension(targetW, targetH)

(3)绘制背景

/**
 * 画背景
 */
private fun drawBackground(canvas: Canvas) {
    bgPath.reset()
    //传入之前测量好的值,圆角值就可以直接绘制出一个带圆角的矩形背景
    bgPath.addRoundRect(bgRectF, radius, radius, Path.Direction.CW)

    //这里根据当前的进度值先测量出进度条的绘制范围,后面就可以直接绘制了
    val progress = bgRectF.width() * currentProgress / 100
    fontRectF.left = bgRectF.left
    fontRectF.top = bgRectF.top
    fontRectF.right = bgRectF.left + progress
    fontRectF.bottom = bgRectF.bottom

    fontPath.reset()
    //先将前景范围测量好
    fontPath.addRoundRect(fontRectF, radius, radius, Path.Direction.CW)

    // 取差集 这里避免重复绘制,所以被前景遮住的部分就可以去掉了
    bgPath.op(fontPath, Path.Op.DIFFERENCE)
    // 画背景
    canvas.drawPath(bgPath, bgPaint)
}

(4)绘制前景和条纹


    /**
     * 画前景 进度
     */
    private fun drawFontBar(canvas: Canvas) {
        if (isStripe) {//需要绘制条纹走这里
            //这里代表条纹可绘制剩余的宽度
            var subW = fontRectF.width()
            //这里代表下一个条纹开始绘制的点
            var left = fontRectF.left

            path.reset()
            //开始循环绘制条纹
            while (subW > (pathSpaceTemp + pathSpace)) {
                //根据UI绘制一个带倾斜角度的矩形
                path.moveTo(left + pathSpaceTemp + pathSpace, fontRectF.top)
                path.lineTo(left + pathSpaceTemp + pathSpace - pathSpace * 3 / 4f, fontRectF.bottom)
                path.lineTo(
                    left + pathSpaceTemp + pathSpace - pathSpace * 3 / 4f + pathWidth,
                    fontRectF.bottom
                )
                path.lineTo(left + pathSpaceTemp + pathSpace + pathWidth, fontRectF.top)
                path.close()
                //每绘制一个剩余的宽度都会减少
                subW -= (pathSpace + pathWidth)
                //下一个条纹的起始位置都在右移
                left += (pathSpace + pathWidth)
            }

            if (!path.isEmpty) {
                // 根据之前测量好的前景范围 绘制前景部分
                canvas.drawPath(fontPath, fontPaint)

                // 根据前景范围和条纹路径范围 取两者共同的部分作为新的路径
                tempPath.op(fontPath, path, Path.Op.INTERSECT)
                // 开始绘制条纹
                canvas.drawPath(tempPath, pathPaint)
            }

            if (pathSpaceTemp < 0) {
                //这里是要每次绘制时都将条纹往右移动一点形成移动的效果
                pathSpaceTemp += 2f
            } else {
                pathSpaceTemp = -(pathSpace + pathWidth).toFloat()
            }
        } else {
            // 画前景
            canvas.drawPath(fontPath, fontPaint)
        }
    }

项目源码地址

https://github.com/VincentStory/ProgressBar

总结

通过自定义绘制,就可以通过修改源码得到任何你想要的进度条效果,包括更改颜色,调整速度。通过阅读源码也可以熟悉自定义view的一些知识点。