Android在绘制View之前,必须对View进行测量,测量之后,Android才知道要绘制多大的View。View的测量过程,用到MeasureSpec,我们要理解了这个类,才能更好理解View的测量过程。
MeasureSpec
应用别人的MeasureSpec源码:
public class MeasureSpec {
// 进位大小为2的30次方(int的大小为32位,所以进位30位就是要使用int的最高位和倒数第二位也就是32和31位做标志位)
private static final int MODE_SHIFT = 30;
// 运算遮罩,0x3为16进制,10进制为3,二进制为11。3向左进位30,就是11 00000000000...0(11后跟30个0)
// (遮罩的作用是用1标注需要的值,0标注不要的值。因为1与任何数做与运算都得任何数,0与任何数做与运算都得0)
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
// 0向左进位30,就是00 00000000000...0(00后跟30个0)
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
// 1向左进位30,就是01 00000000000...0(01后跟30个0)
public static final int EXACTLY = 1 << MODE_SHIFT;
// 2向左进位30,就是10 00000000000...0(10后跟30个0)
public static final int AT_MOST = 2 << MODE_SHIFT;
/**
* 根据提供的size和mode得到一个详细的测量结果
*/
// measureSpec = size + mode; (注意:二进制的加法,不是十进制的加法!)
// 这里设计的目的就是使用一个32位的二进制数,32和31位代表了mode的值,后30位代表size的值
// 例如size=100(4),mode=AT_MOST,则measureSpec=100+10000...00=10000..00100
public static int makeMeasureSpec(int size, int mode) {
return size + mode;
}
/**
* 通过详细测量结果获得mode
*/
// mode = measureSpec & MODE_MASK;
// MODE_MASK = 11 00000000000(11后跟30个0),原理是用MODE_MASK后30位的0替换掉measureSpec后30位中的1,再保留32和31位的mode值。
// 例如10 00..00100 & 11 00..00(11后跟30个0) = 10 00..00(AT_MOST),这样就得到了mode的值
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
/**
* 通过详细测量结果获得size
*/
// size = measureSpec & ~MODE_MASK;
// 原理同上,不过这次是将MODE_MASK取反,也就是变成了00 111111(00后跟30个1),将32,31替换成0也就是去掉mode,保留后30位的size
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
/**
* 重写的toString方法,打印mode和size的信息,这里省略
*/
public static String toString(int measureSpec) {
return null;
}
}
代码注释很清晰,也容易理解。其实MeasureSpec是一个工具类,通过这个工具类,可以将本来2个int(mode和size)信息的值通过2进制形式合成一个,减少内存分配。因为测量方法调用相当的频繁,能省则省,GoodiDea。
mode有3中模式:
EXACTLY
精确值模式。如在定义View时,设置layout_width=”100dp”或者 layout_width=”match_parent”,此时View的宽度为精确值。
AT_MOST
最大值模式。如在定义View时,设置layout_width=”wrap_content”,此时View的宽度会根据内容而定,但是不能超过父控件可用的大小。
UNSPECIFIED
不限制模式。View想要多大有多大,不做限制。一般用于系统内部,表示测量状态。
View的onMeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
我们自定义View时,一般要重写这个方法根据模式设置View大小。getSuggestedMinimumWidth和getSuggestedMinimumHeight方法获取建议(默认)值:
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
从代码中看出,如果view设置了背景,则返回背景Drawable的大小(前提这个Drawable设置了大小,否则返回0)。如果没有设置背景,则返回mMinWidth(mMinHeight),mMinWidth对应xml属性android:minWidth,mMinHeight对应xml属性android:minHeight。
getDefaultSize方法获取默认的大小:
/**
* size是默认大小
*/
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
// Mode = UNSPECIFIED时使用提供的默认大小
case MeasureSpec.UNSPECIFIED:
result = size;
break;
// Mode = AT_MOST,EXACTLY时使用测量的大小
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
然后通过setMeasuredDimension方法储存View的宽高:
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
代码中显示,setMeasuredDimension方法是储存View测量之后的宽高值,所以我们重写onMeasure方法时,一定要调用setMeasuredDimension方法。
我们从getDefaultSize方法知道,我们自定义View时(继承View),如果没有重写onMeasure方法,那么wrap_content将不起作用。从代码上看,当specMode为MeasureSpec.AT_MOST时,result的值为specSize。结合下面的表,specSize值是父View的可用大小,那么wrap_content就相当于match_parent了,所以我们得重写onMeasure方法满足wrap_content属性。一般处理方法是,当specMode为MeasureSpec.AT_MOST时,就设定result为某个精确值。
ViewGroup的onMeasure方法
因为View是一种树状结构,父控件要测量,那么便会通知所有子View先测量,然后再测量自己。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//调用ViewGroup类中测量子View的方法
measureChildren(widthMeasureSpec, heightMeasureSpec);
//调用View类中默认的测量方法
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
}
我们看看测量所有子View的方法measureChildren:
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
遍历所有子View,如果子View的状态不为GONE,则调用measureChild方法:
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
// 取得子视图的布局参数
final LayoutParams lp = child.getLayoutParams();
// 通过getChildMeasureSpec获取最终的宽高详细测量值
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
// 将计算好的宽高详细测量值传入measure方法,完成最后的测量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
getChildMeasureSpec方法的作用是根据父控件MeasureSpec,父控件的padding和子View的width获取测量大小:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// 父View的模式和大小
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
// 计算出父View可用大小,specSize为父view本来的大小,padding是父view已经用掉的大小,剩下是父view可用大小
int size = Math.max(0, specSize - padding);
// 子View想要的实际大小和模式(需要计算)
int resultSize = 0;
int resultMode = 0;
// 通过1.父View的MeasureSpec 2.子view的LayoutParams属性这两点来确定子view的大小
switch (specMode) {
// 当父View的模式为EXACITY时,父View强加给子view确切的值(父View为设置match_parent或者固定值的ViewGroup)
case MeasureSpec.EXACTLY:
// 当子View的LayoutParams>0也就是有确切的值
if (childDimension >= 0) {
//子View大小为子自身所赋的值,模式大小为EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
// 当子View的LayoutParams为MATCH_PARENT时(-1)
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 子View大小为父View大小,模式为EXACTLY
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
// 当子View的LayoutParams为WRAP_CONTENT时(-2)
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 子View决定自己的大小,但最大不能超过父View,模式为AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 当父View的模式为AT_MOST时,父View强加给子view一个最大的值。(父View设置为wrap_content)
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;
// 当父View的模式为UNSPECIFIED时,父容器不对View有任何限制,要多大给多大(多见于ListView、GridView)
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// 子View大小为子自身所赋的值
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 因为父View为UNSPECIFIED,所以MATCH_PARENT的话子类大小为0
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 因为父View为UNSPECIFIED,所以WRAP_CONTENT的话子View大小为0
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
再配合一张表看:
估计这样看就不会懵了。获取到子View的测量大小后,调用child.measure方法完成子View的测量。到此,完成View的测量。