一.初识ViewRoot 和 DecorView
- DecorView
DecorView是整个Window界面的最顶层View。DecorView只有一个子元素为LinearLayout。代表整个Window界面,包含通知栏,标题栏,内容显示栏三块区域。 - ViewRoot
ViewRoot是连接WIndowManager和DecorVIew的纽带。View的三大流程(measure layout draw)都是通过ViewRoot完成的。在ActivityThread中,Activity创建后,DecorView会被添加到Window中,同时创建ViewRootImpl,然后将DecorView与ViewRootImpl建立关联(通过ViewRoot的setView方法)。
一张图看明白ViewRoot 和 DecorView
二.View的工作流程概述
View 的绘制流程是从 ViewRoot 的 performTraversals 方法开始的,它经过 measure、layout 和 draw 三个过程才能最终将一个 View 绘制出来
- measure方法用于测量View的宽高
- layout方法用于确定View在父容器中的位置
- draw方法负责把View绘制在屏幕上
大致流程图如下:
理解:
performTraversals 会依次调用 performMeasure、performLayout 和 performDraw 三个方法,这三个方法分别完成顶级 View 的 measure、layout 和 draw 这三大流程,其中在 performMeasure 中会调用 measure 方法,在 measure 方法中又会调用 onMeasure 方法,在 onMeasure 方法中则会对所有的子元素进行 measure 过程,这个时候 measure 流程就从父容器传递到子元素中了,这样就完成了一次 measure 过程。接着子元素会重复父容器的 measure 过程,如此反复就完成了整个 View 树的遍历。
同理,performLayout 和 performDraw 的传递流程和 performMeasure 是类似的,唯一不同的是,performDraw 的传递过程是在 draw 方法中通过 dispatchDraw 来实现的,不过这并没有本质区别。
View绘制过程中的一些方法介绍
方法 | 作用 | 被赋值的时间 |
---|---|---|
getMeasuredWidth/getMeasureHeight | 获取View测量后的宽/高 | Measure完成以后 |
getTop/getBottom/getLeft/getRight | VIew四个顶点的坐标 | layout完成以后 |
getWidth/getHeight | 拿到View的最终宽/高 | layout完成之后 |
三.理解MeasureSpec
1.MeasureSpec是干什么的?
MeasureSpec在很大程度上决定了一个View的尺寸规格,之所以说很大程度上是因为这个过程还受父容器的影响。在测量过程中,系统会将View的LayoutParams根据父容器所施加的转换规则转换为对应的MeasureSpec,然后再根据这个MeasureSprc来测量出View的宽/高。
2.MeasureSpec的定义
MeasureSpec代表一个32位的int值,高2位代表SpecMode(测量模式),低30位代表SpecSize(在某种测量模式下的规格大小)。
//MeasureSpec源码——部分重点代码
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
//将 size和mode打包成一个MeasureSpec
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
//MeasureSpec解包出mode
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
//MeasureSpec解包出size
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的内存分配,为了方便操作,其提供了打包和解包方法。
SpecMode有三类:
类别 | 说明 | 使用时机 |
---|---|---|
UNSPECIFIED | 父容器不对View有任何限制,要多大给到大 | 这种情况一般用于系统内部,表示一直测量出状态 |
EXACTLY | 父容器已经检测出View的精确的大小,这时候View的最终大小就是SpecSize确定的值 | 对应LayoutParams中的match_parent和具体数值这两种模式 |
AT_MOST | 父容器指定一个可用大小即SpecSize,View的大小不能大于这个值 | 对应LayoutParent中的wrap_content |
3.MeasureSpec和LayoutParams的对应关系
在View测量的时候,系统会将LayoutParams在父容器的约束下转换为对应的MeasureSpec,然后在根据这个MeasureSpec来确定View测量后的宽\高。
另外,对于顶级View(DecorView)和普通View来说,MeaSureSpec的转换过程略有不同。
3.1 顶级View(DecorView)
对于DecorView,其MeasureSpec由窗口的尺寸和自身的LayoutParams来共同决定。
源码分析:
/*
ViewRootImpl.measureHierarchy其中的一段代码
这段代码展示了DecorView的MeasureSpec创建过程
desiredWindowWidth,desiredWindowHeight是屏幕尺寸
*/
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
/*
ViewRootImpl.getRootMeasureSpec源码
*/
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT: //精确模式,大小就是窗口的大小
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT: //最大模式,大小不定,但不能超酷窗口大小
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default: //精确模式,大小为LayoutParams中指定的大小
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
3.2 普通View
View的measure过程由ViewGroup传递而来
源码分析:
/*
ViewGroup.measureChildWithMargins方法
改方法会对子元素进行measure,在调用子元素的measure方法之前会先通过getChildMeasureSpec
方法得到子元素的MeasureSpec
*/
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
/*
获得子元素的MeasureSpec,可以看出子元素MeasureSpec的确定不止和父元素的MeasureSpec,
自身的LayoutParams有关,还和View的margin和padding有关
*/
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
/*
ViewGroup.getChildMeasureSpec方法
*/
public stat