Compose 再火,传统 View 体系也不会消失。图表、仪表盘、手写签名、游戏场景这些需求,通常还是靠自定义 View 实现。
今天这篇文章,把自定义 View 的绘制流程拆开讲清楚。
一、自定义 View 的三条路径
如果只需要画一些图形,最轻量的做法是直接继承 View,重写 onDraw(Canvas)。
二、onDraw 里的核心对象
class CircleProgressView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : View(context, attrs) {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.BLUE
strokeWidth = 12f
style = Paint.Style.STROKE
}
override fun onDraw(canvas: Canvas) {
val cx = width / 2f
val cy = height / 2f
val radius = min(cx, cy) - paint.strokeWidth
canvas.drawCircle(cx, cy, radius, paint)
}
}
Canvas 提供绘制方法,Paint 决定颜色、粗细、填充方式、抗锯齿等视觉效果。
三、measure 和 layout 不能忽略
自定义 View 不只是画。系统要知道它占多大地方,需要重写 onMeasure:
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val size = resolveSize(200.dpToPx(), widthMeasureSpec)
setMeasuredDimension(size, size)
}
如果是自定义 ViewGroup,还得重写 onLayout 来摆放子 View:
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
for (i in 0 until childCount) {
val child = getChildAt(i)
child.layout(left, top, left + child.measuredWidth, top + child.measuredHeight)
}
}
很多绘制异常,其实是 measure 阶段就错了。
四、用 Path 和 Shader 做复杂效果
val path = Path().apply {
moveTo(0f, 0f)
lineTo(100f, 200f)
quadTo(200f, 100f, 300f, 200f)
close()
}
canvas.drawPath(path, paint)
常见 Shader:
LinearGradient:线性渐变RadialGradient:径向渐变SweepGradient:扫描渐变BitmapShader:用图片做填充
五、性能注意事项
不要在 onDraw 里创建对象:Paint、Path 尽量作为成员变量
避免过度重绘:能用硬件加速就别用软件层
复杂动画考虑 SurfaceView:独立线程绘制,不阻塞主线程
局部刷新用 invalidate(Rect):不要每次 invalidate 整个 View
小结
自定义 View 的核心三件事:
用 Canvas + Paint 完成绘制
用 onMeasure/onLayout 解决尺寸和位置
注意性能,避免在 onDraw 里创建对象
下一篇,我们进入 Kotlin 协程的响应式扩展:Flow 的冷流、热流与背压。
参考阅读
引用链接
[1]Android 自定义 View 官方指南: https://developer.android.com/training/custom-views
[2]Advanced Android in Kotlin: Custom Views: https://developer.android.com/courses/android-developers-kotlin-course