Administrator
发布于 2026-08-25 / 21 阅读
0
0

系列文章《Android 工程师的 100 篇进阶笔记》第 17 篇:Android 自定义 View 与 Canvas 绘制实战

Compose 再火,传统 View 体系也不会消失。图表、仪表盘、手写签名、游戏场景这些需求,通常还是靠自定义 View 实现。

今天这篇文章,把自定义 View 的绘制流程拆开讲清楚。


一、自定义 View 的三条路径

路径

适用场景

工作量

继承现有 View

改行为、加装饰

小

继承 ViewGroup

自定义布局规则

中

直接继承 View / SurfaceView

完全自定义绘制

大

如果只需要画一些图形,最轻量的做法是直接继承 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:用图片做填充


五、性能注意事项

  1. 不要在 onDraw 里创建对象:Paint、Path 尽量作为成员变量

  2. 避免过度重绘:能用硬件加速就别用软件层

  3. 复杂动画考虑 SurfaceView:独立线程绘制,不阻塞主线程

  4. 局部刷新用 invalidate(Rect):不要每次 invalidate 整个 View


小结

自定义 View 的核心三件事:

  1. 用 Canvas + Paint 完成绘制

  2. 用 onMeasure/onLayout 解决尺寸和位置

  3. 注意性能,避免在 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


评论