在 Android 中,View 的三大绘制流程是 measure(测量)、layout(布局)和 draw(绘制)。它们共同决定了视图在屏幕上的大小、位置和最终显示效果。这三个过程遵循 “父视图驱动子视图” 的机制,从 ViewRootImpl 开始自上而下执行。
1. Measure(测量)—— 确定宽高
目的:计算出 View 的宽度和高度(即 getMeasuredWidth() 和 getMeasuredHeight())。
关键方法:measure() → onMeasure()
核心逻辑
- MeasureSpec:父容器对子视图的“测量约束”,由 模式 和 尺寸 组成。
UNSPECIFIED:无限制(如 ScrollView 内部)EXACTLY:精确值(如match_parent或固定 dp)AT_MOST:最大值(如wrap_content)
执行流程
- 父容器根据自身的 MeasureSpec 和子布局参数,为每个子视图生成一个 MeasureSpec。
- 调用子视图的
measure(childWidthMeasureSpec, childHeightMeasureSpec)。 - 子视图在
onMeasure()中根据传入的 MeasureSpec,计算出自己的宽高,并通过setMeasuredDimension(width, height)保存。
注意事项
- measure 可能被多次调用(如父容器为了协调布局会反复测量)。
- 在
onMeasure()中必须调用setMeasuredDimension(),否则会抛出异常。 - 自定义 View 时,通常需要重写
onMeasure()来处理wrap_content(默认行为与match_parent相同)。
2. Layout(布局)—— 确定位置
目的:确定 View 在父容器中的四个顶点坐标(left, top, right, bottom),即最终显示位置。
关键方法:layout() → onLayout()
执行流程
- 父容器在
onLayout()中遍历所有子 View。 - 根据测量出的宽高及自身布局规则(如 LinearLayout 的排列方向),为每个子视图计算位置。
- 调用子视图的
layout(left, top, right, bottom)。 - 子视图保存这些坐标,并在
onLayout()中(如果是 ViewGroup)继续触发其子视图的 layout。
获取最终位置
getLeft()/getTop()/getRight()/getBottom()getWidth()=getRight()-getLeft()(通常等于测量宽度,但不绝对)
与 measure 的区别
| 流程 | 输入 | 输出 |
|---|---|---|
| measure | MeasureSpec(约束) | 测量宽/高 |
| layout | 测量宽/高 + 父容器坐标 | 实际位置 + 最终宽/高 |
3. Draw(绘制)—— 显示内容
目的:将 View 的内容绘制到屏幕上(背景、文本、图像等)。
关键方法:draw() → onDraw()
绘制步骤(draw() 内部顺序)
- 绘制背景(
drawBackground()) - 自身内容(调用
onDraw()) - 子视图(
dispatchDraw()) - 前景 / 滚动条(
onDrawForeground())
注意事项
- 避免在
onDraw()中创建新对象(防止 GC 频繁触发)。 - 不要主动调用
draw(),需要重绘时使用invalidate()(UI 线程)或postInvalidate()(非 UI 线程)。 - 如果 View 没有背景且内容为空,系统会跳过绘制(但 View 仍会触发子视图绘制)。
整体调用链(从 ViewRootImpl 开始)
ViewRootImpl.performTraversals()
├── performMeasure() → child.measure()
├── performLayout() → child.layout()
└── performDraw() → child.draw()
- 首次进入时三个流程都会执行。
- 若仅改变位置(如动画平移)且大小不变 → 只执行
layout - 若仅改变外观(如文字变色)且大小/位置不变 → 只执行
draw(调用invalidate())
总结:三个流程的职责
| 流程 | 做的事 | 关键方法 | 输出 |
|---|---|---|---|
| measure | 量多大 | onMeasure() | 测量宽/高 |
| layout | 放哪 | onLayout() | 四顶点坐标 |
| draw | 画什么 | onDraw() | 像素显示 |
了解这三个流程可以帮助你:
- 实现自定义 View(必须重写
onMeasure/onDraw,ViewGroup 还需重写onLayout) - 排查布局异常(如
wrap_content失效、位置偏移) - 优化性能(避免过度测量、重叠绘制)