Eason最近遇到一个需求,需要去展示分段式的进度条,为了给这个进度条想要的外观和感觉,在构建用户界面 (UI) 时,大家通常会依赖 SDK 提供的可用工具并尝试通过调整SDK来适配当前这个UI需求;但悲伤的是,大多数情况下它基本不符合我们的预期。所以Eason决定自己绘制它。
创建自定义视图
在 Android 中要绘制自定义动图,大家需要使用Paint并根据Path对象引导绘制到画布上。
我们可以直接在画布Canvas中操作上面的所有对象View。更具体地说,所有图形的绘制都发生在onDraw()回调中。
class SegmentedProgressBar @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
override fun onDraw(canvas: Canvas) {
// Draw something onto the canvas
}
}
回到进度条,让我们从开始对整个进度条的实现进行分解。
整体思路是: 先绘制有一组显示不同角度的四边形,它们彼此间隔开并且具有没有空间的填充状态。最后,我们有一个波浪动画与其填充进度同步。
在尝试满足上述所有这些要求之前,我们可以从一个更简单的版本开始。不过不用担心。我们会从基础的开始并逐步深入浅出的!
绘制单段进度条
第一步是绘制其最基本的版本:单段进度条。
暂时抛开角度、间距和动画等复杂元素。这个自定义动画整体来说只需要绘制一个矩形。我们从分配 aPath和一个Paint对象开始。
private val segmentPath: Path = Path()
private val segmentPaint: Paint = Paint( Paint.ANTI_ALIAS_FLAG )
尽量不在onDraw()方法内部分配对象。这两个Path和Paint对象必须在其范围之内创建。在View很多时候调用这个onDraw回调时将导致你内存逐渐减少。编译器中的 lint 消息也会警告大家不要这样做。
要实现绘图部分,我们可能要选择Path的drawRect()方法。因为我们将在接下来的步骤中绘制更复杂的形状,所以更倾向于逐点绘制。
moveTo():将画笔放置到特定坐标。
lineTo(): 在两个坐标之间画一条线。
这两种方法都接受Float值作为参数。
从左上角开始,然后将光标移动到其他坐标。
下图表示将绘制的矩形,给定一定的宽度 ( w ) 和高度 ( h )。
在Android中,绘制时,Y轴是倒置的。在这里,我们从上到下计算。
绘制这样的形状意味着将光标定位在左上角,然后在右上角画一条线。
path.moveTo(0f, 0f)
path.lineTo(w, 0f)
在右下角和左下角重复这个过程。
path.lineTo(w, h)
path.lineTo(0f, h)
最后,关闭路径完成形状的绘制。
path.close()
计算阶段已经完成。是时候用paint给它涂上颜色了!
针对Paint对象的处理,大家可以使用颜色、Alpha 通道和其他选项。Paint.Style枚举决定形状是否将被填充(默认)、空心有边框或两者兼而有之。 在示例中,将绘制一个带有半透明灰色的填充矩形:
paint.color = color
paint.alpha = alpha.toAlphaPaint()
对于 alpha 属性,Paint需要Integer从 0 到 255。由于更习惯于Float从 0 到 1操作 a ,我创建了这个简单的转换器
fun Float.toAlphaPaint(): Int = (this * 255).toInt()
上面已准备好呈现我们的第一个分段进度条。我们只需要将我们的Paint按照计算出的x和y方向绘制在canvas上。
canvas.drawPath(path,paint)
下面是部分代码:
class SegmentedProgressBar @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
@get:ColorInt
var segmentColor: Int = Color.WHITE
var segmentAlpha: Float = 1f
private val segmentPath: Path = Path()
private val segmentPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)
override fun onDraw(canvas: Canvas) {
val w = width.toFloat()
val h = height.toFloat()
segmentPath.run {
moveTo(0f, 0f)
lineTo(w, 0f)
lineTo(w, h)
lineTo(0f, h)
close()
}
segmentPaint.color = segmentColor
segmentPaint.alpha = alpha.toAlphaPaint()
canvas.drawPath(segmentPath, segmentPaint)
}
}
使用多段进度条前进
是不是感觉已经差不多快完成了呢?对的!已经完成了大部分自定义动画的工作。我们将为每个段创建一个实例,而不是操作唯一的Path和Paint对象。
var segmentCount: Int = 1 // Set wanted value here
private val segmentPaths: MutableList<Path> = mutabl