本文大纲:
话接上文,ViewRootImpl 中的测量、布局、绘制继续分析...
1. 测量流程源码分析
1.1. performMeasure与measure方法
- mView即顶层容器DecorView,直接调用measure方法。
- View.measure为final类型,不可重写。
- 首先尝试读取缓存,若命中则调用onMeasure;若未命中,则重新设置并优化。
1.2. onMeasure与尺寸保存
- onMeasure内部调用setMeasuredDimension和setMeasuredDimensionRaw。
- 核心操作是对成员变量赋值并设置标记位,保存measuredWidth和measuredHeight。
- 测量的本质是确定控件的宽和高。
2. MeasureSpec原理解析
2.1. 定义与结构
- MeasureSpec封装了View的测量模式(Spec Mode)和尺寸(Spec Size)。
- 它是一个32位int值:高2位表示模式,低30位表示尺寸。
c
View = 模式 + 尺寸->MeasureSpec 32位int值
00000000000000000000000000000000
MODE_MASK : 11000000000000000000000000000000
~MODE_MASK: 00111111111111111111111111111111
SpecMode(前2位) + SpecSize(后30)
2.2. 三种模式常量
- UNSPECIFIED:父容器不对View做限制,大小随意,通常系统内部使用。二进制前两位为00。
- EXACTLY:精确模式,父容器已检测出View所需精确大小。对应match_parent或固定大小。二进制前两位为01。
- AT_MOST:最大模式,父容器指定可用大小,View不能超过该值。对应wrap_content。二进制前两位为11。
2.3. 打包与解包方法
makeMeasureSpec(size, mode):将尺寸和模式打包成MeasureSpec。逻辑:(size & ~MODE_MASK) | (mode & MODE_MASK)。~MODE_MASK保留size低30位,MODE_MASK保留mode高2位。getMode(spec):提取高2位模式。getSize(spec):提取低30位尺寸。
3. DecorView的MeasureSpec确定规则
3.1. 计算入口
- 通过getRootMeasureSpec方法,传入窗口宽度(window size)和顶层View的布局参数(lp.width)。

3.2. 具体规则
- 若layout_width为MATCH_PARENT:模式为EXACTLY,尺寸为窗口大小。
- 若layout_width为WRAP_CONTENT:模式为AT_MOST,尺寸为窗口大小(最大不超过窗口)。
- 若layout_width为固定值:模式为EXACTLY,尺寸为固定值。
3.3. 结论
- DecorView的MeasureSpec由窗口大小和自身LayoutParams决定。
4. 普通View/ViewGroup的MeasureSpec确定规则

4.1. 递归测量过程
- FrameLayout.onMeasure遍历子View,调用measureChildWithMargins。
- 该方法先调用getChildMeasureSpec获取子View的MeasureSpec,再调用子View的measure方法。
4.2. getChildMeasureSpec逻辑
参数:父容器spec、父容器padding、子ViewchildDimension。
- 当父容器模式为EXACTLY时 :
- 子View固定大小:模式EXACTLY,尺寸为固定值。
- 子ViewMATCH_PARENT:模式EXACTLY,尺寸为父容器剩余空间(specSize - padding)。
- 子ViewWRAP_CONTENT:模式AT_MOST,尺寸为父容器剩余空间。
- 当父容器模式为AT_MOST时 :
- 子View固定大小:模式EXACTLY,尺寸为固定值。
- 子ViewMATCH_PARENT或WRAP_CONTENT:模式均为AT_MOST,尺寸为父容器剩余空间。
- 当父容器模式为UNSPECIFIED时 :
- 子View固定大小:模式EXACTLY,尺寸为固定值。
- 子ViewMATCH_PARENT或WRAP_CONTENT:模式均为UNSPECIFIED,尺寸为0。
4.3. 结论
- 普通View的MeasureSpec由父容器的MeasureSpec和自身LayoutParams共同决定。
5. ViewGroup与View的测量实现差异
5.1. ViewGroup测量
- 先递归测量所有子View。
- 根据子View的最大宽高、Padding、最小宽高、背景等因素,计算自身宽高。
- 调用setMeasuredDimension保存自身尺寸。
5.2. View测量
- 直接调用measure -> onMeasure -> getDefaultSize。
- getDefaultSize中,无论EXACTLY还是AT_MOST,默认都返回specSize(父容器剩余空间)。

5.3. 重要结论
- 若自定义View不重写onMeasure,match_parent和wrap_content效果一致,均填满父容器剩余空间。
- 自定义View必须重写onMeasure以正确处理wrap_content。
- 自定义ViewGroup需重写onMeasure以测量子View并确定自身大小。
6. 布局流程(Layout)
6.1. 入口与定位
- performLayout调用host.layout。
- View.layout中调用setFrame(l, t, r, b)确定左上右下四个坐标位置。
6.2. onLayout回调
- layout方法最后调用onLayout(空实现)。
- ViewGroup:需在onLayout中遍历子View并调用其layout方法,完成子View摆放(递归)。
- View:只需确定自身位置,无需处理子View。
7. 绘制流程(Draw)
7.1. 入口
- performDraw -> draw -> drawSoftware -> mView.draw。
7.2. View.draw六个步骤
- 绘制背景(drawBackground)。
- 保存图层缓存(如有必要)。
- 绘制内容(onDraw),自定义View需重写此方法。
- 绘制子View(dispatchDraw),ViewGroup需实现此方法以递归绘制子View。
- 绘制装饰(如滚动条)。
- 绘制前景。
7.3. 递归绘制
- ViewGroup.dispatchDraw遍历子View,调用drawChild,进而调用子View的draw方法。
8. 总结与自定义建议
8.1. 布局
- ViewGroup:重写onLayout摆放子View。
- View:仅需确定自身位置,onLayout可选。
8.2. 绘制
- 顺序:背景 -> 自身(onDraw) -> 子View(dispatchDraw) -> 装饰。
8.3. 自定义View
- 必须重写onMeasure处理尺寸,重写onDraw绘制内容。
8.4. 自定义ViewGroup
- 必须重写onMeasure测量子View,重写onLayout摆放子View;若仅包含系统控件,onDraw可不重写。