转载请标明出处: http://blog.csdn.net/airsaid/article/details/53576087
本文出自:周游的博客
前言
MeasureSpec 是 View 的一个内部类,代表了一个32位的 int 值,高 2 位代表 SpecMode,低 30 位代表 SpecSize。SpecMode 是指测量模式,SpecSize 是指在某种测量模式下的规格大小。.
该类在很大程度上决定了 View 的尺寸规格,之所以说很大程度上是因为这个过程还受父容器的影响,是因为父容器影响 View 的 MeasureSpec 的创建过程。系统会将 View 的 LayoutParams 根据父容器所施加的规则转换成对应的 MeasureSpec,然后再根据这个 MeasureSpec 来测量出 View 的宽高。需要注意的是,这个的宽高是测量的宽高,并不一定是 View 最终的宽高。后面会详细了解 MeasureSpec 的创建过程,在了解之前,首先来了解下 MeasureSpec 的三种测量模式。
三种测量模式
SpecMode 和 SpecSize 组成了 MeasureSpec,MeasureSpec 通过将 SpecMode 和 SpecSize 打包成一个 int 值来避免过多的对象创建,并提供了对应的打包、解包方法:
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
SpecMode 有三类,分别是:
UNSPECIFIED:父容器不对子元素作任何约束,子 View 想要多大就给多大。这种情况一般用在系统内部,表示一种测量的状态。一般情况下,我们不用关注该测量模式。
EXACTLY:精准模式。父容器已经解决了子元素所需要的精准大小,这时候子 View 的最终大小就是 SpecSize 所指定的值。它对应于 LayoutParams 中的 match_parent 和具体的数值。
- AT_MOST:最大模式。父最大模式容器指定了一个可用大小即 SpecSize,子元素最大不可以超过指定的这个值。它对应于LayoutParams 中的 wrap_content。
在实际的开发中,我们一般只对AT_MOST进行处理,如:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width = 0;
int height = 0;
if(widthMode == MeasureSpec.AT_MOST){
width = ...
}
if(heightMode == MeasureSpec.AT_MOST){
height = ...
}
setMeasuredDimension(widthMode != MeasureSpec.AT_MOST ? widthSize : width,
heightMode != MeasureSpec.AT_MOST? heightSize : height);
}
MeasureSpec 和 LayoutParams 的对应关系
上面了解了 MeasureSpec 这个类,那么该类是怎么创建来的呢?根据什么创建来的呢?现在就来深入了解一下。
对于普通 View,MeasureSpec 是由父容器的 MeasureSpec 和自身的 LayoutParams 来共同决定的。
看看源码,MeasureSpec 是怎么被创建出来的,由于普通 View 的 measure 过程由 ViewGroup 传递而来,所以首先来看一下 ViewGroup 的:measureChildWithMargins()方法:
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);
}
上述方法,首先获取到了子元素的 LayoutParams 然后根据当前子元素的 LayoutParams 和父类的MeasureSpec,获取了子元素的 MeasureSpec,最后调用了子元素的 measure。
那么在 getChildMeasureSpec 又做了什么操作呢?继续来看一下 getChildMeasureSpec 方法:
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;
switch (specMode) {
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
上述方法主要是根据父容器的 MeasureSpec 同时结合了子元素的 LayoutParams 来确定了子元素的 MeasureSpec。
由于 UNSPECIFIED 这个模式主要用于系统内部 measure,一般来说,我们无须关注,所以排除掉 UNSPECIFIED 模式来总结一下就是:
当 View 采用固定宽高的时候,不管父容器的 MeasureSpec 是什么,View 的测量模式都是 EXACTLY 也就是精准模式。并且其大小遵循 LayoutParams 中的大小。
当 View 的宽高是 match_parent 的时候,如果父控件是精准模式,那么 View 也是精准模式,并且大小是父容器的剩余空间。如果父控件是最大模式,那么 View 也是最大模式,并且大小不会超过父容器的剩余空间。
当 View 的宽高是 wrap_content 的时候,不管父控件是精准模式还是最大模式,View 的模式最是最大模式,并且大小不能够超过父容器的剩余空间。
参考
- 《Android 开发艺术探索》