View的绘制流程是Android UI系统的核心,理解绘制机制对于自定义View和性能优化至关重要。
View绘制的三个阶段
View的绘制流程遵循Measure → Layout → Draw三个阶段:
shellView绘制流程: measure() → onMeasure() → 测量View宽高 ↓ layout() → onLayout() → 确定View位置 ↓ draw() → onDraw() → 绘制View内容
第一阶段:Measure(测量)
测量目的
确定View的宽度和高度(measuredWidth/measuredHeight)。
MeasureSpec
MeasureSpec是父View对子View的尺寸要求,由模式和尺寸组成:
| 模式 | 值 | 含义 |
|---|---|---|
| EXACTLY | 0x40000000 | 精确值,如match_parent或具体数值 |
| AT_MOST | 0x80000000 | 最大值,如wrap_content |
| UNSPECIFIED | 0x00000000 | 无限制,如ScrollView中的子View |
测量流程
java@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); // 根据模式计算实际尺寸 int width = calculateWidth(widthMode, widthSize); int height = calculateHeight(heightMeasureSpec); setMeasuredDimension(width, height); }
第二阶段:Layout(布局)
布局目的
确定View在父容器中的位置(left, top, right, bottom)。
布局流程
java@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { // 遍历子View,确定每个子View的位置 for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); child.layout(childLeft, childTop, childRight, childBottom); } }
第三阶段:Draw(绘制)
绘制步骤
draw()方法包含6个步骤:
javapublic void draw(Canvas canvas) { // 1. 绘制背景 drawBackground(canvas); // 2. 保存画布状态 saveCount = canvas.getSaveCount(); // 3. 绘制内容(子类实现) onDraw(canvas); // 4. 绘制子View dispatchDraw(canvas); // 5. 绘制装饰(如滚动条) onDrawForeground(canvas); // 6. 恢复画布状态 canvas.restoreToCount(saveCount); }
ViewGroup的绘制特点
ViewGroup继承自View,但增加了子View管理:
- 测量阶段:遍历测量所有子View
- 布局阶段:确定子View的位置
- 绘制阶段:dispatchDraw()绘制所有子View
绘制优化技巧
1. 减少重绘
java// 使用invalidate(Rect)局部重绘 invalidate(0, 0, 100, 100); // 使用ViewStub延迟加载 <ViewStub android:id="@+id/stub" android:layout="@layout/view" />
2. 避免过度绘制
- 移除不必要的背景
- 使用clipRect减少绘制区域
- 使用GPU Overdraw调试工具检测
3. 使用硬件加速
xml<application android:hardwareAccelerated="true">
自定义View要点
javapublic class CustomView extends View { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 处理wrap_content if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) { setMeasuredDimension(defaultWidth, defaultHeight); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 绘制自定义内容 canvas.drawCircle(cx, cy, radius, paint); } }
面试要点
- 理解MeasureSpec的三种模式
- 掌握requestLayout()和invalidate()的区别
- 了解绘制流程的触发时机
- 掌握自定义View的基本步骤
- 理解硬件加速和软件绘制的区别