外观
自定义 View 要实现哪几个关键点?
标准回答
自定义 View 的关键点是确定尺寸、绘制内容、处理交互和正确通知更新。onMeasure 根据父容器给出的 MeasureSpec、内容需求、padding 和最小尺寸计算结果,并调用 setMeasuredDimension。onSizeChanged 适合在尺寸变化时计算圆心、路径等几何信息,避免每次绘制重复算。
onDraw 负责当前 View 自身的图形,例如刻度、文字、进度。Paint、Path 等对象尽量复用,绘制中不做磁盘或网络操作。外观变化需要 invalidate;布局需求变化需要 requestLayout,并按实际内容变化正确通知重绘。它们安排后续更新,不是调用返回就立即显示。
需要触摸交互时,在 onTouchEvent 处理按下、移动、抬起和取消;识别出点击后,通过 performClick 保留点击语义与可访问性支持。业务值变化通过明确的回调交给外部,不把网络请求等业务逻辑塞进绘制方法。需要在重建后恢复的自定义状态,也要设计保存与恢复。
自定义 ViewGroup 还要负责孩子:在 onMeasure 测量自己及必要的子 View,在 onLayout 调用孩子的 layout 安排位置。孩子的绘制通常由已有 dispatchDraw 实现组织;如果想在孩子前后叠加内容,再考虑重写 dispatchDraw 并保留合适的 super 调用。onDraw 画自身,onLayout 摆孩子,dispatchDraw 组织画孩子,三者不要混淆。
关键机制
- MeasureSpec 包含模式与尺寸:EXACTLY 给出确定尺寸,AT_MOST 给出上限,UNSPECIFIED 表示本次未施加上限;还需考虑 padding 和最小尺寸。
- 大小变化影响测量布局时使用 requestLayout;仅外观内容变化使用 invalidate。二者触发后续调度,不代表立即绘制。
- Paint、路径等可复用对象避免每帧重复分配;坐标计算考虑密度、字体、padding 和可访问性。
通俗例子
一个音量旋钮:onMeasure 决定圆盘尺寸,onSizeChanged 计算圆心,onDraw 画刻度,触摸角度转音量,最终通过回调交给业务层。
追问与回答
做一个音量旋钮,具体分工是什么?
onMeasure 给出控件尺寸,onSizeChanged 算圆心和半径,onDraw 画圆盘、刻度和指针,onTouchEvent 将触摸位置换算成音量并处理取消。音量变了通知外部并 invalidate;只有外部尺寸需求改变时才需要重新布局。这样的分工能避免把所有事情都挤进 onDraw。
自定义 ViewGroup 只实现 onLayout 可以吗?
通常不够。你还需要正确测量容器及孩子,才能知道它们应该占多大。onLayout 使用测量和布局规则摆位置,本身不替你建立完整的测量策略。若继承已有容器且其测量规则正好满足需求,可以复用父类实现;直接继承 ViewGroup 时则必须明确处理这些职责。
易混淆点
在 onDraw 里创建大量对象或不断 requestLayout,可能导致频繁 GC 或反复遍历。
参考资料
内容来源
- Android 技术札记 · 原条目
custom-view