Android 自定义 View 的核心流程主要围绕 测量(Measure) 、布局(Layout) 、绘制(Draw) 三大阶段,对应的关键方法是 onMeasure()、onLayout() 和 onDraw()。如果是自定义 ViewGroup,还需要额外处理子 View 的测量与布局。以下是详细说明:
一、测量阶段 ------ 确定 View 的宽高
核心方法: onMeasure(int widthMeasureSpec, int heightMeasureSpec)
- MeasureSpec 由父 View 传入,包含 模式(Mode) 和 尺寸(Size) :
EXACTLY:确定大小(如match_parent或具体 dp 值)。AT_MOST:最大不超过某值(如wrap_content)。UNSPECIFIED:无限制(较少使用,如 ScrollView 内部)。
- 必须调用
setMeasuredDimension(width, height)保存测量结果。 - 注意 :若为
wrap_content需要自行计算内容所需尺寸(否则可能占据全父容器)。
辅助方法:
resolveSize(int desiredSize, int measureSpec):根据 MeasureSpec 计算合适的尺寸。
二、布局阶段 ------ 确定 View 的位置(仅 ViewGroup 必须重写)
核心方法: onLayout(boolean changed, int left, int top, int right, int bottom)
- 对于普通 View(非容器),通常不需要重写(位置由父 View 决定)。
- 对于 ViewGroup ,必须重写该方法,遍历所有子 View 并调用子 View 的
layout(l, t, r, b)来设定它们的位置。 - 参数
changed表示 View 的尺寸或位置是否改变。
相关方法:
requestLayout():触发重新测量和布局。
三、绘制阶段 ------ 显示内容
核心方法: onDraw(Canvas canvas)
- 使用
Canvas和Paint绘制图形、文字、图片等。 - 避免在
onDraw中创建对象(防止频繁 GC)。 - 通过
invalidate()触发重绘(可在任意线程调用,内部会 post 到 UI 线程)。
绘制层级:
ViewGroup 默认会先绘制自身背景(调用 onDraw),再调用 dispatchDraw(Canvas) 绘制子 View。若需要自定义绘制顺序,可重写 dispatchDraw()。
四、其他关键方法
| 方法 | 作用 | 调用时机 |
|---|---|---|
| 构造方法 | 解析自定义属性(TypedArray),初始化 Paint 等对象 |
代码或 XML 创建 View 时 |
onSizeChanged(int w, int h, int oldw, int oldh) |
监测量尺寸变化,常用于初始化与宽高相关的数据(如 Bitmap、路径) | 第一次测量或尺寸改变后 |
onAttachedToWindow() / onDetachedFromWindow() |
处理窗口附着与分离时的资源操作(如注册/注销监听) | View 添加到/移出窗口时 |
onMeasure 中的 getSuggestedMinimumWidth() |
获取 View 建议的最小宽度(由背景或 android:minWidth 决定) |
测量阶段内部调用 |
invalidate() |
标记 View 为"脏",触发重绘(仅调用 onDraw,不重新测量布局) |
UI 数据改变需要刷新显示时 |
requestLayout() |
标记 View 树需要重新测量和布局(会依次调用 onMeasure、onLayout) |
宽高或位置数据改变时 |
五、自定义属性(可选但常用)
- 在
res/values/attrs.xml中声明<declare-styleable>。 - 构造方法中通过
context.obtainStyledAttributes()获取TypedArray,解析自定义属性值。 - 记得
recycle()。
六、典型流程时序(以 ViewGroup 为例)
sql
构造方法 → onMeasure(测量自身及子View)→ onLayout(摆放子View)→ onDraw(绘制内容)
若触发 invalidate():仅重新执行 onDraw。
若触发 requestLayout():重新执行 onMeasure → onLayout → onDraw。
七、注意事项
- 性能 :避免在
onDraw中分配对象;减少invalidate区域(invalidate(Rect))。 - wrap_content :自定义 View 必须自己处理,否则效果等同
match_parent。 - Padding :如果自定义 View 支持
padding,需要在onDraw中主动偏移绘制区域,或在onMeasure中减去padding。 - 线程 :UI 操作(除
invalidate外)必须在主线程。 - 触摸事件 (扩展):若需要交互,重写
onTouchEvent或dispatchTouchEvent。
总结
| 类型 | 必须重写的方法 | 通常重写的方法 |
|---|---|---|
| 自定义 View | onMeasure、onDraw |
构造方法、onSizeChanged |
| 自定义 ViewGroup | onMeasure、onLayout |
onDraw(若需要绘制背景)、dispatchDraw |
理解这三步流程和关键方法的调用链,即可灵活实现各种自定义控件。