childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
…
}
复制代码
我们在继续看下 getRootMeasureSpec 的源码实现,代码如下:
//ViewRootImpl.java
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
/**
其实就是 XML 布局中定义的 MATCH_PARENT
/
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
/
其实就是 XML 布局中定义的 WRAP_CONTENT
/
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
/
*其实就是 XML 布局中定义的 绝对 px
*/
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
复制代码
通过上面代码,DecorView 的 MeasureSpec 的产生过程就很明确了,具体其准守如下规则,根据它的 LayoutParams 中的宽高的参数来划分。
- LayoutParams.MATCH_PARENT: 精确模式,大小就是窗口的大小;
- LayoutParams.WRAP_CONTENT: 最大模式,大小不定,此模式一般是子 View 决定,但是不能超过父容器的大小;
- XML 中宽高 px/dp 固定: 精确模式,大小为 LayoutParams 中指定的大小。
那么对于普通 View 也就是 XML 布局中的 View 是怎么测量的呢?我们先来看一下 ViewGroup 的 measureChildWithMargins 方法,因为 View 的 measure 过程就是由它给传递过来的,代码如下:
//ViewGroup.java
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//得到子元素的 MeasureSpec
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);
//对子View 开始进行 measure
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
复制代码
上述方法首先会拿到子 View 的 LayoutParams 布局中定义的参数,然后根据父容器的 MeasureSpec 、 子元素的 LayoutParams 、子元素的 padding 等然后拿到对子元素的测量规格,可以看 getChildMeasureSpec 代码具体实现:
//ViewGroup.java
//padding:指父容器中已占用的大小
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//拿到在父容器中可用的最大的尺寸值
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
//根据父容器的测量规格和 View 本身的 LayoutParams 来确定子元素的 MeasureSpec
switch (specMode) {
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
复制代码
这一段代码虽然比较多,但是还是比较容易理解,它主要的工作就是先拿到在父容器中可用的尺寸,然后根据父元素的测量规格和子元素中 LayoutParams 参数来决定当前子 View 的 MeasureSpec。
上面的代码,如果用一张表格来表示的话,应该更好理解,请看下表:
parentSpecMode / childLaoutParams | EXACTLY(精准模式) | AT_MOST(最大模式) | UNSPECIFIED(精准模式) |
---|---|---|---|
dp/px | EXACTLY childSize | EXACTLY childSize | EXACTLY childSize |
match_parent | EXACTLY parentSize | AT_MOST parentSize | UNSPECIFIED 0 |
Warap_content | AT_MOST parentSize | AT_MOST parentSize | UNSPECIFIED 0 |
通过此表可以更加清晰的看出,只要提供父容器的 MeasureSpec 和子元素的 LayoutParams, 就可以快速的确定出子元素的 MeasureSpec 了,有了 MeasureSpec 就可以进一步确定出子元素测量后的大小了。
View 工作流程
View 的工作流程主要是指 measure、layout 、draw 这三个流程,即 测量 -> 布局 -> 绘制,其中 measure 确定 View 测量宽高,layout 确定 View 的最终宽高和四个顶点的位置,而 draw 则将 View 绘制到屏幕上。
在讲解 View 的绘制流程之前,我们有必要知道 View 的 measure 何时触发,其实如果对 Activity 生命周期源码有所了解的应该知道,在 onCreate 生命周期中,我们做了 setContentView 把 XML 中的节点转为 View 树的过程,然后在 onResume 可以交互的状态,开始触发绘制工作,可以说 Activity 的 onResume 是开始绘制 View 的入口也不为过,下面看入口代码:
//Act