前言
项目中有个等待页面需要有一个进度条的展示,是带条纹的样式,android自带进度条都是纯色的不太能满足要求。在网上找了一下也没有比较满意的轮子,于是干脆自己写一个进度条,顺便练习一下UI绘制相关技术。
期望效果如下所示:
分析要绘制的内容:
这个进度条主要分为三个部分,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的一些知识点。