在 Android 的视图系统(View System)中,measurelayoutdraw是视图渲染的三个核心流程,负责从 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()等。
  • 过程
    1. 父 View 根据自己的布局参数(如 layout_width 和 layout_height)和父容器提供的约束(MeasureSpec)测量自身。
    2. 递归调用子 View 的measure()方法,传递合适的 MeasureSpec。
    3. 每个 View 调用setMeasuredDimension()保存自己的测量尺寸。

Info

父 View ➜ measure 子 View ➜ 子 View 返回尺寸 ➜ 父 View 决定自身大小

  • MeasureSpec:测量规范,包含尺寸(size)和模式(mode):
    1. EXACTLY:精确尺寸(如 100dp 或 match_parent)。
    2. AT_MOST:最大尺寸(如 wrap_content 受父容器限制)。
    3. 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()发起,在测量阶段完成后执行。
  • 过程
    1. 父 View 根据自己的测量尺寸和布局规则(如 LinearLayout 的线性排列或 ConstraintLayout 的约束)计算子 View 的位置。
    2. 调用每个 View 的layout(left, top, right, bottom)方法,设置其在父容器中的边界。
    3. 递归调用子 View 的layout()方法,完成整个 View 树的布局。
  • 输出:每个 View 的最终位置getLeft()getTop()等和尺寸getWidth()getHeight()
  • 关键点:
    1. layout()的坐标是相对于父容器的。
    2. 位置计算通常依赖measure阶段的结果(如子 View 的测量宽高)。

3. Draw(绘制)

Draw 是渲染流程的最后一步,负责将 View 及其子 View 的内容绘制到屏幕上(通过 Canvas)。这一阶段将 View 的视觉内容(如背景、文本、图片等)渲染到屏幕。

  • 触发:由ViewRootImpl.performDraw()发起,在布局阶段完成后执行。
  • 过程
    1. View 树的根节点调用draw()方法,触发整个 View 树的绘制。
    2. 每个 View 执行以下步骤:
      • 绘制背景drawBackground()
      • 绘制自身内容(onDraw(),由开发者自定义)。
      • 绘制子 View(递归调用子 View 的draw())。
      • 绘制装饰(如前景、滚动条)。
    3. 最终内容通过 Canvas 渲染到屏幕的 Surface 上。
  • 输出:屏幕上显示的最终 UI。

4. Measure、Layout、Draw 的整体流程

  1. Measure
    • 确定 View 树的尺寸。
    • 自顶向下递归,父 View 约束子 View 的尺寸。
    • 结果存储在 measuredWidth 和 measuredHeight 中。
  2. Layout
    • 根据测量结果,确定 View 在父容器中的位置。
    • 自顶向下递归,设置每个 View 的边界。
    • 结果存储在 left、top、right、bottom 中。
  3. Draw
    • 将 View 内容渲染到屏幕。
    • 自底向上递归,绘制背景、内容、子 View 和装饰。
    • 最终通过 Canvas 输出到屏幕。

顺序:measure → layout → draw

触发条件:

  • requestLayout():重新触发measurelayout(可能引发draw)。
  • invalidate():只触发draw(不重新测量和布局)。
流程图 ### 举个例子理解三大流程 假设你有一个 `Button`: ```xml