首先,测量有三种mode(模式):
1.UNSPECIFIED: 美其名曰不具体的测量,父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量状态,我们应用程序貌似都没涉及到。对应的具体数值为:00000000 00000000 00000000 00000000
2.EXACTLY:顾名思义是具体的、精确的测量,在布局中对应match_parent和具体数值两种模式,如下:
android:width=”100dp”
android:height=”match_parent”
上面两种都是EXACTLY模式,因为match_parent就是父容器大小,测量子类的时候父容器大小已经是测量好的。对应的数值为01000000 00000000 00000000 00000000
3.AT_MOST:意如其名,最多不能超过指定大小,这个指定大小指的是父元素测量好自己之后给子元素指定的大小,一般是父元素剩余可用空间。在布局中对应:wrap_content,如下:
android:width=”wrap_content”
对应的数值为10000000 00000000 00000000 00000000
MeasureSpec用一个32位的int值代替,高两位代表测量模式(UNSPECIFIED,EXACTLY,AT_MOST),低两位代表测量的大小,例如一个通过makeMeasureSpec()得到一个int值为 01000000 00000000 00000000 00001111,则他的测量mode是高两位01也就是EXACTLY精确的大小,具体大小为低30位,000000 00000000 00000000 00001111 大小为15
首先测量的是mDecorView(FrameLayout),ViewRootImpl中有一个代码片段
if (!goodMeasure) {
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}
desiredWindowWidth/desiredWindowHeight 是屏幕的宽/高
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:
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
通过以上代码可以知道mDecorView的测量过程,如果是match_parent、wrap_content,大小就是屏幕的大小,如果是其他情况大小就是指定的尺寸大小。
测量普通的View,他的measure过程都有mDecorView一层一层传递进来的。
测量某个具体的子类源码(在measure的时候遍历每个子类,遍历到每一个子类的时候都会调用到这个方法):
/**
* 根据父类的MeasureSpec值、padding值和child的width、height、Margin值计算出子类的
* childWidthMeasureSpec和childHeightMeasureSpec ,然后调用子类的measure()测量出最终大小
*/
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
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);
}
看一下是如何计算出子类的测量值:
/**
* 父类分三种情况UNSPECIFIED、EXACTLY、AT_MOST,子类width和height也分这三种情况
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//得出父类的测量模式和大小
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//减去父类已经占有的空间,这个变量用padding命名貌似不太合适,可以用withUsed或heightUsed,得出可用大小
int size = Math.max(0, specSize - padding);
//定义测量出的mode和size变量
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
//父布局的Mode是EXACTLY
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
//父控件是固定大小child也是固定大小,那没的说android:with定义多大就给它多大,这种情况可能child的宽度高度大于parent的高度宽度,但是超出部分显示不出来。
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//父控件是固定大小,child是填充parent,所以把剩余空间都给它,模式也是EXACTLY
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//父控件是固定大小,child是wrap_content,把剩余空间都给它,模式是AT_MOST,代表大小不能超过这个值。这种情况下跟resultSize跟match_parent效果一样。如果写自定义View要自己适配这种情况。
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
//父布局的Mode是AT_MOST
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;
//父布局的Mode是UNSPECIFIED
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//通过resultSize、resultMode合并成MeasureSpec
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
只有第一个case添加了注解,后面两个参考第一个理解,就明白了child是如何根据parent和自身的mode来测量的。子类利用getChildMeasureSpec()得到的值去测量自己。
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
measure的实现如下:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
......
onMeasure(widthMeasureSpec, heightMeasureSpec);
......
}
measure会调用onMeasure,onMeasure的实现如下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
onMeasure通过调用setMeasuredDimension(int measuredWidth, int measuredHeight)去设置,而measuredWidth/measuredHeight是通过getDefaultSize()方法计算出来的,看一下getDefaultSize()的实现:
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
//这两个是上面通过getChildMeasureSpec()方法得到的mode和size
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
大多数情况都会是MeasureSpec.AT_MOST和MeasureSpec.EXACTLY,所以我们先认为getDefaultSize返回的就是getChildMeasureSpec()计算出来的大小,也就是specSize。
getDefaultSize(int size, int measureSpec) 中size参数是通过getSuggestedMinimumWidth()函数得到的,看一下他的实现:
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
如果这个背景(mBackground )为null,则返回mMinWidth,如果该View设置了背景,则返回mMinWidth和mBackground.getMinimumWidth()的较大值。
mMinWidth是View构造函数解析XML的时候给的值,给出代码片段:
case R.styleable.View_minWidth:
mMinWidth = a.getDimensionPixelSize(attr, 0);
从上面可以看出它的值在布局里面是 android:minWidth=”50dp”给的值,如果没有定义这个属性,默认值是0,接下来看一下mBackground.getMinimumWidth(),调用到了Drawable的值:
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
返回的是mBackground背景宽度,默认返回0。
所以这样分析下来getDefaultSize(int size, int measureSpec)执行的返回结果有三种情况:
1.0
2.背景图片大小
3.通过getChildMeasureSpec()计算过后的大小
然后调用 setMeasuredDimension()方法最终设置大小:
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
setMeasuredDimension做的事情也就是给View的两个成员变量measuredWidth和measuredHeight赋值了,供绘制的时候参考。