在分析view的measure过程之前,有必要看看什么是measureSpec,因为它在view的measure流程中经常出现,比较重要,不知道的童鞋可能会感觉这是啥玩意儿,好高大上阿。。。其实看过之后会发现measureSpec并不难,下面开始----
一、MeasureSpec简介
MeasureSpec代表一个32位的int值,这个从类的常量中就可以看出来,它是View的内部类,如下:
public static class 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;
/**
* 精确:子元素会被给予一个确定的值,对应LayoutParams中的Match_parent和具体的数值
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* 不大于:父元素提供一个可用的值,子元素不能超过这个值,对应LayoutParams中的wrap_content
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
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);
}
. . .
}
MeasureSpec提供了三种模式(SpecMode),分别为上面的UNSPECIFIED、EXACTLY、AT_MOST
<<是位运算符,一般用于二进制,代表向左移位,MODE_SHIFT是个常量,30,所以UNSPECIFIED就是0向左移位30位,EXACTLY是1向左移位30位,AT_MOST是2向左移位30位,如下表示:
UNSPECIFIED:
移位前:00000000 00000000 00000000 00000000
移位后:00000000 00000000 00000000 00000000
EXACTLY:
移位前:00000000 00000000 00000000 00000001
移位后:01000000 00000000 00000000 00000000
AT_MOST:
移位前:00000000 00000000 00000000 00000010
移位后:10000000 00000000 00000000 00000000
总的来说,其实就是MeasureSpec中,高两位代表SpecMode,低30位代表SpecSize。MeasureSpec类将SpecMode(模式)和SpecSize(大小)整合成一个int值,方法就是上面代码中的makeMesaureSpec方法,同时为了方便,也提供了方法可以从一个MeasureSpec值当中取出来原始的SpecMode和SpecSize,就是上面代码中的getMode和getSize,方法都很简单,这里可以简单解释下:
MODE_MASK是3向左移位30位,即11000000 00000000 0000000000000000,取反就是00111111 11111111 11111111 11111111。
把已整合的MeasureSpec值和MODE_MASK做&运算,即可得到SpecMode,将已整合的MeasureSpec值和取反后的MODE_MASK即可得到SpecSize。
另外,MeasureSpec和LayoutParams是有对应关系的,系统中是通过measureSpec来测量View的,但是在外部一般都是通过设置LayoutParams来改变View的,比如wrap_content和match_parent等。那么中间是怎么转换的呢,首先需要知道一点,就是View的MeasureSpec并不是唯一由LayoutParams来决定的,LayoutParams是需要和父容器一起才能决定view的MeasureSpec,在view测量的时候,系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec。
PS:顶级view的measureSpec不在此列,它是由窗口尺寸和自身的LayoutParams决定的,这个可以自行查看源码(在ViewRootImpl中的measureHierarchy方法中)
View的measure过程要分情况来看,如果是一个单纯的view,那么通过measure过程就完成了测量过程,如果还是一个viewgroup,那么不仅要完成自身的测量过程外,还要遍历子元素并调用子元素的measure过程,各个子元素继续递归调用,直到所有的子元素测量完毕。
二、View的measure过程
view的测量过程是从measure方法开始的,此方法是final修饰的,所以子类是不能重写此方法的,view方法内会调用onmeasure方法,伪代码如下所示:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
}
所以看看onmeasure方法
/**
* <p>
* Measure the view and its content to determine the measured width and the
* measured height. This method is invoked by {@link #measure(int, int)} and
* should be overriden by subclasses to provide accurate and efficient
* measurement of their contents.
* </p>
*
* <p>
* <strong>CONTRACT:</strong> When overriding this method, you
* <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
* measured width and height of this view. Failure to do so will trigger an
* <code>IllegalStateException</code>, thrown by
* {@link #measure(int, int)}. Calling the superclass'
* {@link #onMeasure(int, int)} is a valid use.
* </p>
*
* <p>
* The base class implementation of measure defaults to the background size,
* unless a larger size is allowed by the MeasureSpec. Subclasses should
* override {@link #onMeasure(int, int)} to provide better measurements of
* their content.
* </p>
*
* <p>
* If this method is overridden, it is the subclass's responsibility to make
* sure the measured height and width are at least the view's minimum height
* and width ({@link #getSuggestedMinimumHeight()} and
* {@link #getSuggestedMinimumWidth()}).
* </p>
*
* @param widthMeasureSpec horizontal space requirements as imposed by the parent.
* The requirements are encoded with
* {@link android.view.View.MeasureSpec}.
* @param heightMeasureSpec vertical space requirements as imposed by the parent.
* The requirements are encoded with
* {@link android.view.View.MeasureSpec}.
*
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
注释一大堆,代码没几行。。。看起来很简单,但从方法的注释上可以看到重要性,子类一般都需要重写此方法,并且在重写的时候必须要setMeasuredDimension方法,否则会报异常。此方法有一个默认的实现,setMeasuredDimension是设置view的measurewidth和measureheight的方法,这里不多说,看getDefaultSize方法,如下:
public static int getDefaultSize(int size, int measureSpec) {
int result = 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;
}
代码很少,逻辑也很简单,当view的MeasureSpec模式为AT_MOST或者EXACTLY的时候,这个方法返回的默认测量值就是参数MeasureSpec中的specSize,specSize就是view测量后的值。从上面的代码可以看出来,当view模式处于AT_MOST的时候和EXACTLY的时候返回的结果都是一样的,都是specSize,那么这个specSize是多少呢,它从measurcSpec中获取,是从父元素中传来的,所以带着这个问题来看看viewgroup的 measure过程吧。
三、ViewGroup的measure过程
对于viewgroup来说,除了要测量自身以外,还要遍历子元素并调用子元素的measure方法,各个子元素继续递归调用这个流程,而viewgroup是个抽象类,所以并没有重写View的onmeasure方法,而是提供了其他的方法,相关的方法有三个:measureChildren、measureChild和measureChildWithMargins三个方法/**
* Ask all of the children of this view to measure themselves, taking into
* account both the MeasureSpec requirements for this view and its padding.
* We skip children that are in the GONE state The heavy lifting is done in
* getChildMeasureSpec.
*(这个view的所有子元素在这个view的MeasureSpec和padding的约束下绘制自身,跳过那些处于gone状态下的子元素,重要部分在getChildMeasureSpec方法中,下同)
* @param widthMeasureSpec The width requirements for this view
* @param heightMeasureSpec The height requirements for this view
*/
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);
}
}
}
/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding.
* The heavy lifting is done in getChildMeasureSpec.
*
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param parentHeightMeasureSpec The height requirements for this view
*/
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding
* and margins. The child must have MarginLayoutParams The heavy lifting is
* done in getChildMeasureSpec.
*
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param widthUsed Extra space that has been used up by the parent
* horizontally (possibly by other children of the parent)
* @param parentHeightMeasureSpec The height requirements for this view
* @param heightUsed Extra space that has been used up by the parent
* vertically (possibly by other children of the parent)
*/
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);
}
看方法别忘了看注释,注释是个很好的帮助,这三个方法都是有关于measure的,measureChildren方法很好理解,就是遍历子元素,调用measureChild方法,measureChild方法内是先取出子元素的LayoutParams,通过一个getChildMeasureSpec方法来获取相应的MeasureSpec,然后递归调用子元素的measure方法。而measureChildWithMargins方法则还需要计算子元素的margins,下图表示父容器和子元素之间的padding和margin。
蓝色的代表父元素的padding(padding是和自身内容之间的距离,view内部),黄色的代表子元素的margin(margin是和外部边界的距离,view外部),中间红色的是子元素。举个不太恰当的例子,如果父容器是房子,子元素是里面的人,那么padding就是父元素--房子的墙壁厚度,margin是子元素--人距离墙壁的距离。
代码注释中可以看到,重点是在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) {
// Parent has imposed an exact size on us(当父元素是精准模式的时候)
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {//如果子元素的LayoutParams也是确定的值,那么子元素的模式就是EXACTLY,大小为设置的值
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {//如果子元素是Match_parent,那么子元素模式就是EXACTLY,大小就是父元素剩余可用的空间
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {//如果子元素是wrap_content,那么子元素模式就是AT_MOST,大小就是父元素剩余可用的空间
// 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: //如果父元素的模式是AT_MOST
if (childDimension >= 0) {//如果子元素的LayoutParams是精确的,那么子元素模式就是EXACTLY,大小就是设置的值
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {//如果子元素是Match_parent,那么子元素模式就是AT_MOST,大小就是父元素剩余可用的空间
// 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) {//如果子元素是wrap_content,那么子元素模式就是EXACTLY,大小就是父元素剩余可用的空间
// 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: //如果父元素的模式是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 = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
这个方法的作用就是根据父容器的MeasureSpec并结合View本身的LayoutParams来确定子元素的MeasureSpec,参数中的padding是父元素中已经被占用的空间,所以子元素的可用空间就是父容器的大小specSize减去已经占用的padding。注释已经比较清楚了,总结如下:
1、 当view的Layoutparams采用的是固定宽高的时候,不管父元素的MeasureSpec是什么,view的MeasureSpec的模式都会是精准的EXACTLY,大小都是设置的固定值
2、 当view的LayoutParams采用的是match_parent的时候,如果父元素是精准模式,那么view的模式就会是精准模式(EXACTLY)并且大小是父元素中剩余的所有大小,如果父元素是最大模式,那么view的模式就是最大模式(AT_MOST),并且大小不能超过父元素中剩余的大小。
3、 当view的LayoutParams是wrap_content的时候,不管父元素的MeasureSpec是什么,view的模式都会是最大模式(AT_MOST),并且大小不能超过父元素中剩余的大小。
返回先前的问题,当view的模式是AT_MOST的时候, view的宽高会是specSize,通过上面的代码可以得到,specSize就是父容器剩余的所有空间,如果不作处理,当view的LayoutParams是wrap_content的时候,那么效果和match_parent是一样的,都是父容器剩余的所有空间。所以说直接继承view的自定义控件要记得在onmeasure方法中设置当处于wrap_content的时候的大小,否则wrap_content的时候就跟match_parent是一样的效果。
有兴趣的童鞋可以查看一下常用的View和ViewGroup的measure代码,一般都经过特殊处理的,不过源码虽好,可不要贪看哦(本屌丝连着看了一周的源码,已经快吐了。。。)
measure过程就先写到这里,本人也是菜鸟,欢迎留言,一起学习,如有错误,还望指正。