一句话总结 :
View 的绘制流程就像盖房子:先量尺寸(Measure)→ 再摆位置(Layout)→ 最后刷墙装修(Draw)。整个过程由"包工头" ViewRootImpl 指挥,保证按时(16ms/帧)完工!
一、绘制流程三步走
1. Measure(量尺寸)
-
核心任务:确定每个 View 的宽高。
-
规则传递 :父 View 根据自身规则(如 LinearLayout 的权重)向子 View 传递
MeasureSpec
(测量规格),包含:- EXACTLY :精确尺寸(如
100dp
)。 - AT_MOST :最大尺寸(如
match_parent
)。 - UNSPECIFIED :随便你(如
wrap_content
,但实际可能受父容器限制)。
- EXACTLY :精确尺寸(如
-
代码入口 :
View.measure()
→onMeasure()
。
示例:
arduino
// 自定义 View 重写 onMeasure
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
int width = calculateWidth(widthSpec); // 根据父容器的限制计算自己的宽
int height = calculateHeight(heightSpec);
setMeasuredDimension(width, height); // 必须调用!
}
2. Layout(摆位置)
- 核心任务:确定 View 在屏幕上的位置(四个坐标:left, top, right, bottom)。
- 父控件的责任 :遍历子 View,调用
child.layout()
设置其位置。 - 代码入口 :
View.layout()
→onLayout()
。
示例:
java
// 自定义 ViewGroup 重写 onLayout
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.layout(childLeft, childTop, childRight, childBottom);
}
}
3. Draw(刷墙装修)
-
核心任务:将 View 的内容绘制到屏幕上。
-
绘制顺序:
- 绘制背景(
drawBackground()
)。 - 绘制自身内容(
onDraw()
)。 - 绘制子 View(
dispatchDraw()
)。 - 绘制装饰(如滚动条、前景)。
- 绘制背景(
-
代码入口 :
View.draw()
→onDraw()
。
示例:
typescript
// 自定义 View 重写 onDraw
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(x, y, radius, paint); // 画个圆
}
二、绘制原理:幕后的大佬们
1. ViewRootImpl:总包工头
-
职责:
- 管理整个 View 树的绘制流程(
performTraversals()
)。 - 通过
Choreographer
监听 VSync 信号(屏幕刷新脉冲),确保每帧在 16ms 内完成。
- 管理整个 View 树的绘制流程(
2. Choreographer:节奏大师
- 作用:协调输入、动画、绘制事件,保证按 VSync 信号同步执行。
- 卡顿检测:如果一帧超时(>16ms),触发掉帧(Jank)。
3. SurfaceFlinger:刷墙队
- 职责:将各窗口(Surface)的绘制结果合成,最终显示到屏幕。
- 双缓冲机制:避免绘制过程中出现闪烁(Back Buffer 绘制,Front Buffer 显示)。
三、触发绘制的两种方式
方法 | 作用 | 触发流程 |
---|---|---|
invalidate() |
标记脏区域,触发重绘(Draw 阶段) | 只走 draw() 流程 |
requestLayout() |
强制重新测量和布局(可能重绘) | 触发 measure() → layout() → draw() |
示例:
scss
// 点击按钮后更新 View
button.setOnClickListener {
myView.updateContent(); // 修改数据
myView.invalidate(); // 触发重绘
}
四、优化绘制的实战技巧
1. 减少层级嵌套
- 问题:嵌套过深导致多次测量(如 RelativeLayout 嵌套 LinearLayout)。
- 解决 :使用
ConstraintLayout
替代,扁平化布局。
2. 避免过度绘制
-
检测工具:开发者选项 → "显示过度绘制区域"。
-
优化方案:
- 移除不必要的背景(如父容器已设背景,子 View 无需重复)。
- 使用
canvas.clipRect()
限制绘制区域。
3. 离线绘制
- 场景:复杂静态内容(如图表)。
- 方案 :使用
Bitmap
或Canvas
预渲染,直接绘制缓存。
五、总结口诀
- 绘制流程三步走,测量布局再刷墙
- ViewRoot 总指挥,Choreographer 卡节奏
- invalidate 重绘,requestLayout 全量走
- 优化层级少嵌套,流畅体验不用愁!