1. 初识ViewRoot和DecorView
- ViewRoot扮演的是View的管理者角色,负责完成View的新建、更新和删除等操作。在代码层面,由ViewRootImpl类具体实现。
View的绘制流程,具体实现在ViewRootImpl.performTraversals()中,代码十分复杂,主要分为measure、layout和draw三大步骤,需要花时间研读。
- DecorView是根视图,包含上下两部分:标题栏和内容栏,其中内容栏的id是固定的android.R.id.content,我们在Activity里调用setContentView的时候,其实就是在设置这个内容栏的布局。在代码层面,DecorView是PhoneWindow的一个内部类,继承自FrameLayout。
2. 理解MeasureSpec
-
一个MeasureSpec封装了父容器传递给子View的布局要求。在测量过程中,系统会根据MeasureSpec来测量View,这个MeasureSpec由View本身想要的布局参数,结合父容器的MeasureSpec来生成。
-
在代码层面,MeasureSpec由32位的int值来表示,其中高2位代表测量模式(SpecMode),低30位代表测量规格(SpecSize)。
- 测量模式有三种:
- UNSPECIFIED。父容器不做任何限制,要多大就给多大,这种情况一般用于系统内部。
- EXACTLY。父容器指定精确大小为SpecSize,这个SpecSize就是View的最终大小。该模式对应于布局中的match-parent或者具体数值。
-
AT_MOST。父容器指定大小上限为SpecSize,View的大小不能超过SpecSize,具体是什么值取决于View的具体实现(默认为SpecSize)。该模式对应于布局中的wrap-content。
- 对于子View,MeasureSpec由自身的布局参数和父容器的MeasureSpec共同决定,生成规则参见ViewGroup中的getChildMeasureSpec(),简单总结如下:
- View的布局参数为match-parent时,生成View的MeasureSpec = 父容器SpecMode + parentSize
- View的布局参数为wrap-content时,生成View的MeasureSpec = AT_MOST + parentSize
- View的布局参数为固定宽高时,不论父容器的MeasureSpec是什么,生成View的MeasureSpec = EXACTLY + childSize
3. View的工作流程
-
如果是单个View,自身调用measure方法就可以完成测量;如果是ViewGroup,除了完成自身的测量过程外,还需要遍历调用所有子View的measure过程
- 代码层面,View测量时的方法调用链路:measure()->onMeasure()->getDefaultSize()。在getDefaultSize()中可以看到:
- EXACTLY和AT_MOST模式下,View的测量宽高其实就等于SpecSize。
- UNSPECIFIED模式下,View的测量宽高计算规则(以宽度为例):如果View没有设置背景,测量宽度=minWidth;如果设置了背景,测量宽度=max(minWidth, 背景宽度)。