不止是“装修”:从“匠人手绘”到“智能拼装”,Android 自定义视图的演进

219 阅读4分钟

一句话总结:

优化自定义 View 的最高境界,是首先思考“我能否不写自定义 View?”。对于必须手写的场景,要精通传统 View 的优化“手艺”;而面向未来,则应拥抱 Jetpack Compose 这一“智能拼装”的新范式,它在架构层面就为我们解决了大部分性能问题。


第一章:“匠人”的技艺——精通传统自定义 View 的优化

当我们需要维护存量代码或必须使用 View 体系时,掌握一套精湛的“装修”手艺至关重要。

核心原则一:为 onDraw() 极致“减负”

onDraw 是渲染的“热路径”,每秒可能被调用 60 次。它的任何性能抖动都会直接体现为卡顿。

  • 零对象分配: 严禁onDraw 方法内 new 任何对象。Paint, Rect, Path 等所有绘图相关的对象,都必须在 View 初始化时创建并复用。
  • 零耗时操作: 严禁onDraw 方法内执行任何耗时操作,如解码 Bitmap、文件 I/O 等。所有数据和资源的准备,都应提前到 onMeasureonLayout 甚至构造函数中。

核心原则二:避免不必要的“粉刷”——对抗过度绘制

  • 裁剪绘制区域 (canvas.clipRect): 如果你只需要重绘 View 的一小部分,请使用 clipRect 告诉 Canvas:“只在脏区内作画!”
  • 移除冗余背景: 检查 View 和其父容器,确保没有被完全覆盖的背景仍在被绘制。

核心原则三:理解硬件加速的“交易”

setLayerType(View.LAYER_TYPE_HARDWARE, null) 并非免费的性能“开关”。它是在用**“更多的内存”去交换“更快的动画/绘制”**。

  • 何时使用? 当你的 View 包含大量复杂的绘制操作(如多条 Path、复杂的 Shader),或者需要频繁地进行 alpha/rotation/scale 动画时,开启硬件层可以带来巨大收益。
  • 何时避免? 对于简单的 View,开启它反而会增加不必要的内存开销。

第二章:工程师的思考——我真的需要一个“自定义 View”吗?

在继承 View 开始码字之前,请先自问:

  1. 我想要的是一个自定义的“布局容器”吗?

    • 如果是,99% 的场景下 ConstraintLayout 都是更优的选择。它能用扁平的层级实现极其复杂的布局,避免了自定义 ViewGroup 中繁琐的 onMeasure/onLayout 逻辑。
  2. 我想要的只是一个自定义的“外观”吗?

    • 如果是,优先考虑创建一个自定义 Drawable。你可以实现复杂的绘图逻辑,然后将其应用到任何 ImageViewView 的背景上。这比创建一个完整的 View 子类更轻量、更解耦。

第三章:“未来”的工艺——用 Jetpack Compose 进行“智能拼装”

Jetpack Compose 彻底改变了游戏规则。它用**“组合”替代了“继承” ,用“声明式”替代了“命令式”**。

架构性地解决旧问题

  • onDraw 内存抖动?不存在了。

    在 Compose 中,状态对象通过 remember { ... } 创建,其生命周期由 Compose 运行时管理,天然避免了在绘制阶段重复创建。

    val paint = remember { Paint().apply { color = Color.RED } }
    Canvas(modifier = Modifier.fillMaxSize()) {
        drawCircle(color = Color.Red, radius = 50f) // 直接使用状态
    }
    
  • invalidate() 和 requestLayout()?被淘汰了。

    UI 由“状态”驱动。当状态改变时,Compose 的**“智能重组”**机制会自动找出需要更新的最小 UI 单元并重绘。开发者不再需要手动管理刷新。

从“手绘”到“拼装”

过去我们需要写一个复杂的自定义 View 来画一个带图标、标题和子标题的卡片。在 Compose 中,我们只是简单地“拼装”:

@Composable
fun MyCustomCard(icon: ImageVector, title: String, subtitle: String) {
    Row(verticalAlignment = Alignment.CenterVertically) {
        Icon(imageVector = icon, contentDescription = null)
        Column {
            Text(text = title, fontWeight = FontWeight.Bold)
            Text(text = subtitle, style = MaterialTheme.typography.body2)
        }
    }
}

这不仅代码更少、更直观,其底层的布局和绘制也经过了现代化的高度优化。


四、总结:你的自定义 UI 决策路径

需求首选方案 (现代)备用方案 (传统)
构建全新的 UI 界面Jetpack Compose(不推荐)
实现一个复杂的布局Jetpack ComposeConstraintLayout
实现一个复杂的静态外观在 Compose 中使用 Canvas自定义 Drawable
维护旧的自定义 View 代码(重构为 Compose)遵循第一章的“匠人”优化法则

最终建议:

精通传统 View 的优化技巧,是你作为一名资深 Android 开发者的“基本功”,它能让你在任何项目中游刃有余。而拥抱 Jetpack Compose,则是让你跳出“手工作坊”的思维局限,用更先进、更高效的“工业化”思想去构建未来的用户界面。