View 绘制流程深度解析

View 绘制流程深度解析

从 measure、layout 到 draw,完全掌握 View 渲染原理

目录

  1. 绘制流程概述
  2. Measure 测量阶段
  3. Layout 布局阶段
  4. Draw 绘制阶段
  5. 整体流程图
  6. 面试高频问题

1. 绘制流程概述

三大核心方法


flowchart TB
Measure["measure(int, int)<br/>确定 View 的宽高"] --> Layout["layout(int, int, int, int)<br/>确定 View 的位置"]
Layout --> Draw["draw(Canvas)<br/>绘制 View 内容"]
Draw --> Order["调用顺序:measure → layout → draw"]

何时触发绘制

1
2
3
4
// 触发重新绘制的场景
view.requestLayout() // 触发 measure + layout + draw
view.invalidate() // 只触发 draw
view.postInvalidate() // 在主线程调用,触发 draw

2. Measure 测量阶段

MeasureSpec 详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// MeasureSpec = Mode + Size
// Mode 有三种模式:

/**
* UNSPECIFIED (0): 父容器不限制,子 View 可以任意大小
* 用于 ScrollView 等容器
*
* EXACTLY (1073741824): 精确模式,父容器指定了确切大小
* 对应 layout_width="200dp" 或 match_parent
*
* AT_MOST (戵): 最大模式,子 View 不能超过指定大小
* 对应 layout_width="wrap_content"
*/

val specMode = MeasureSpec.getMode(measureSpec)
val specSize = MeasureSpec.getSize(measureSpec)

when (specMode) {
MeasureSpec.EXACTLY -> println("精确大小: $specSize")
MeasureSpec.AT_MOST -> println("最大不能超过: $specSize")
MeasureSpec.UNSPECIFIED -> println("无限制")
}

onMeasure 核心逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// 获取建议的宽高
val width = getDefaultSize(suggestedMinimumWidth, widthMeasureSpec)
val height = getDefaultSize(suggestedMinimumHeight, heightMeasureSpec)

setMeasuredDimension(width, height)
}

// getDefaultSize 实现
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size; // 使用原始大小
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize; // 使用 MeasureSpec 指定的大小
break;
}
return result;
}

自定义 View 的 onMeasure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class CustomView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

// 推荐尺寸
private val defaultWidth = 200 // dp
private val defaultHeight = 200 // dp

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)

// 计算最终尺寸
val finalWidth = when (widthMode) {
MeasureSpec.EXACTLY -> widthSize
MeasureSpec.AT_MOST -> minOf(defaultWidth.dp.toPx(), widthSize)
else -> defaultWidth.dp.toPx()
}

val finalHeight = when (heightMode) {
MeasureSpec.EXACTLY -> heightSize
MeasureSpec.AT_MOST -> minOf(defaultHeight.dp.toPx(), heightSize)
else -> defaultHeight.dp.toPx()
}

setMeasuredDimension(finalWidth.toInt(), finalHeight.toInt())
}
}

ViewGroup 的 onMeasure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// 1. 测量所有子 View
for (i in 0 until childCount) {
val child = getChildAt(i)
measureChild(child, widthMeasureSpec, heightMeasureSpec)
}

// 2. 计算自己的尺寸
// ...
setMeasuredDimension(measuredWidth, measuredHeight)
}

// measureChild 实现
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
// 获取子 View 的 LayoutParams
final LayoutParams lp = child.getLayoutParams();

// 计算子 View 的 MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(
parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight,
lp.width
);

final int childHeightMeasureSpec = getChildMeasureSpec(
parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom,
lp.height
);

// 测量子 View
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

3. Layout 布局阶段

onLayout 核心逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
// 对于 View: 无需实现

// 对于 ViewGroup: 需要布局所有子 View
for (i in 0 until childCount) {
val child = getChildAt(i)

// 计算子 View 的位置
val width = child.measuredWidth
val height = child.measuredHeight

// 简单的垂直布局示例
val childLeft = left + paddingLeft
val childTop = top + currentHeight + marginTop
val childRight = childLeft + width
val childBottom = childTop + height

// 调用子 View 的 layout
child.layout(childLeft, childTop, childRight, childBottom)

// 记录已使用的高度
currentHeight += height + marginTop + marginBottom
}
}

setFrame / setOpticalFrame

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// setFrame 核心实现
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;

// 检查位置是否变化
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;

// 保存旧值
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;

// 如果大小变化,标记
if (mRight - mLeft != getWidth() || mBottom - mTop != getHeight()) {
mPrivateFlags |= PFLAG_SIZE_CHANGED;
}
}
return changed;
}

4. Draw 绘制阶段

draw 核心流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// View.draw() 完整流程
public void draw(Canvas canvas) {
// 1. 绘制背景
drawBackground(canvas);

// 2. 绘制内容 (如果需要)
onDraw(canvas);

// 3. 绘制子 View (ViewGroup)
dispatchDraw(canvas);

// 4. 绘制装饰 (滚动条、前景等)
onDrawForeground(canvas);
}

dispatchDraw 分发绘制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ViewGroup.dispatchDraw
protected void dispatchDraw(Canvas canvas) {
for (int i = 0; i < childrenCount; i++) {
View child = getChildAt(i);

// 检查子 View 是否可见
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
// 绘制子 View
drawChild(canvas, child, drawingTime);
}
}
}

// drawChild 核心
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
// ... 动画处理

// 调用子 View 的 draw 方法
return child.draw(canvas, this, drawingTime);
}

自定义 View 绘制示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class CircleView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.RED
style = Paint.Style.FILL
}

private var centerX = 0f
private var centerY = 0f
private var radius = 0f

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
centerX = w / 2f
centerY = h / 2f
radius = minOf(w, h) / 2f - 20f.dp.toPx()
}

override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawCircle(centerX, centerY, radius, paint)
}
}

5. 整体流程图


flowchart TB
Request["requestLayout()"] --> Traversal["performTraversals()"]
Traversal --> Measure["measure()"]
Measure --> MDetail["onMeasure() / setMeasuredDimension() / ViewGroup 测量子 View"]
MDetail --> LayoutStage["layout()"]
LayoutStage --> LDetail["setFrame() / onLayout() / ViewGroup 布局子 View"]
LDetail --> DrawStage["draw()"]
DrawStage --> DDetail["drawBackground() / onDraw() / dispatchDraw() / onDrawForeground()"]
DDetail --> Finish["绘制完成"]


6. 面试高频问题

Q1: requestLayout vs invalidate 区别

方法 作用 触发
requestLayout() 重新 measure + layout + draw 位置或大小变化
invalidate() 重新 draw 内容变化
postInvalidate() 线程中调用 invalidate 同上

Q2: View 测量模式如何决定?


flowchart TB
ParentSpec["父 MeasureSpec + 子 View LayoutParams"] --> ExactParent["EXACTLY"]
ExactParent --> E1["match_parent → EXACTLY"]
ExactParent --> E2["wrap_content → AT_MOST"]
ExactParent --> E3["200dp → EXACTLY"]
ParentSpec --> AtMostParent["AT_MOST"]
AtMostParent --> A1["match_parent → AT_MOST"]
AtMostParent --> A2["wrap_content → AT_MOST"]
AtMostParent --> A3["200dp → EXACTLY"]
ParentSpec --> UnspecifiedParent["UNSPECIFIED"]
UnspecifiedParent --> U1["任意 → UNSPEC"]

Q3: 为什么 wrap_content 不生效?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 问题:wrap_content 生效但尺寸为 0
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// ❌ 错误:使用默认实现
super.onMeasure(widthMeasureSpec, heightMeasureSpec)

// ✅ 正确:手动计算尺寸
val desiredWidth = 200.dp.toPx()
val desiredHeight = 200.dp.toPx()

val width = resolveSize(desiredWidth, widthMeasureSpec)
val height = resolveSize(desiredHeight, heightMeasureSpec)

setMeasuredDimension(width, height)
}

总结


flowchart TB
DrawCore["View 绘制核心"] --> D1["Measure: measure() → onMeasure() → setMeasuredDimension()"]
DrawCore --> D2["Layout: layout() → onLayout() → setFrame()"]
DrawCore --> D3["Draw: draw() → onDraw() → dispatchDraw()"]


相关文章