在 Android 的视图系统(View System)中,measure、layout、draw是视图渲染的三个核心流程,负责从 View 的尺寸计算到最终绘制到屏幕上。
Info
measure()➜layout()➜draw()
(测量 → 布局 → 绘制)
这三个过程统称为 View 的绘制流程,由系统自动调用,用于将 View 渲染在屏幕上。每个View(如 Button、TextView)从创建到显示在屏幕上,都要经过这三个核心流程。
1. Measure(测量阶段)
Measure 是 Android View 系统渲染流程的第一步,负责确定 View 或 ViewGroup 的尺寸(宽高)。在这个阶段,View 树会递归计算每个 View 的测量尺寸(measured width 和 measured height),以便为后续的布局和绘制做准备。
工作原理
- 触发:由
ViewRootImpl.performMeasure()发起,通常在 View 树需要更新时,如界面首次加载、布局变化、旋转屏幕、调用requestLayout()等。 - 过程:
- 父 View 根据自己的布局参数(如 layout_width 和 layout_height)和父容器提供的约束(MeasureSpec)测量自身。
- 递归调用子 View 的
measure()方法,传递合适的 MeasureSpec。 - 每个 View 调用
setMeasuredDimension()保存自己的测量尺寸。
Info
父 View ➜ measure 子 View ➜ 子 View 返回尺寸 ➜ 父 View 决定自身大小
- MeasureSpec:测量规范,包含尺寸(size)和模式(mode):
- EXACTLY:精确尺寸(如 100dp 或 match_parent)。
- AT_MOST:最大尺寸(如 wrap_content 受父容器限制)。
- UNSPECIFIED:无限制(通常用于滚动视图,少见,如
ScrollView的子 View)
- 输出:每个 View 的测量宽高(getMeasuredWidth() 和 getMeasuredHeight())。
**示例:**手动测量 View 尺寸
View view = findViewById(R.id.my_view);
int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
view.measure(widthSpec, heightSpec);
Log.d("Measure", "Measured Width: " + view.getMeasuredWidth());2. Layout(布局)
Layout 是渲染流程的第二步,负责确定 View 及其子 View 在屏幕上的具体位置(左上角坐标和宽高)。基于测量阶段的结果,View 树会递归设置每个 View 的最终位置和尺寸。
- 触发:由
ViewRootImpl.performLayout()发起,在测量阶段完成后执行。 - 过程:
- 父 View 根据自己的测量尺寸和布局规则(如 LinearLayout 的线性排列或 ConstraintLayout 的约束)计算子 View 的位置。
- 调用每个 View 的
layout(left, top, right, bottom)方法,设置其在父容器中的边界。 - 递归调用子 View 的
layout()方法,完成整个 View 树的布局。
- 输出:每个 View 的最终位置
getLeft()、getTop()等和尺寸getWidth()、getHeight()。 - 关键点:
layout()的坐标是相对于父容器的。- 位置计算通常依赖
measure阶段的结果(如子 View 的测量宽高)。
3. Draw(绘制)
Draw 是渲染流程的最后一步,负责将 View 及其子 View 的内容绘制到屏幕上(通过 Canvas)。这一阶段将 View 的视觉内容(如背景、文本、图片等)渲染到屏幕。
- 触发:由
ViewRootImpl.performDraw()发起,在布局阶段完成后执行。 - 过程:
- View 树的根节点调用
draw()方法,触发整个 View 树的绘制。 - 每个 View 执行以下步骤:
- 绘制背景
drawBackground()。 - 绘制自身内容(
onDraw(),由开发者自定义)。 - 绘制子 View(递归调用子 View 的
draw())。 - 绘制装饰(如前景、滚动条)。
- 绘制背景
- 最终内容通过 Canvas 渲染到屏幕的 Surface 上。
- View 树的根节点调用
- 输出:屏幕上显示的最终 UI。
4. Measure、Layout、Draw 的整体流程
- Measure:
- 确定 View 树的尺寸。
- 自顶向下递归,父 View 约束子 View 的尺寸。
- 结果存储在 measuredWidth 和 measuredHeight 中。
- Layout:
- 根据测量结果,确定 View 在父容器中的位置。
- 自顶向下递归,设置每个 View 的边界。
- 结果存储在 left、top、right、bottom 中。
- Draw:
- 将 View 内容渲染到屏幕。
- 自底向上递归,绘制背景、内容、子 View 和装饰。
- 最终通过 Canvas 输出到屏幕。
顺序:measure → layout → draw。
触发条件:
requestLayout():重新触发measure和layout(可能引发draw)。invalidate():只触发draw(不重新测量和布局)。