View的measure是View的三大过程最复杂的一个。View的measure过程要分情况来看,如果只是一个原始的View,那么通过measure方法就完成了测量过程,如果是一个ViewGroup,除了完成自己的测量过程外,还会遍历去调用所有子元素的measure方法,各个子元素再递归执行这个过程,下面分两种情况分别讨论。
0、MeasureSpec
在说measure过程之前,需要先说明一个类,MeasureSpec,从名字来看,MeasureSpec看起来像测量规格或者测量说明书,不管怎么翻译,它看起来都反应了一个View的测量过程,通过源码发现,MeasureSpec确实参与了View的measure过程。
MeasureSpec在很大程度上决定了一个View的尺寸规格,这句是核心,那么问题又来了,为什么是很大程度呢,是因为MeasureSpec的创建还和父容器有关,系统会根据View的LayoutParams根据父容器的MeasureSpec规格转换成对应的MeasureSpec。
MeasureSpec代表一个32位int值,高2位为SpecMode,低30位代表SpecSize。
SpecMode指测量模式,而SpecSize是指在某种测量模式下的规格大小。
SpecMode有三类:
EXACTLY
父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize的值,对应于LayoutParams.MATCH_PARENT和具体数值两个模式。
AT_MOST
父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么要看不同View的具体实现,对应于LayoutParams.WRAP_CONTENT
UNSPECIFIED
父容器不对View有任何限制,要多大给多大,一般用于系统内部。
MeasureSpec的创建还和父容器有关,系统会根据View的LayoutParams根据父容器的MeasureSpec规格转换成对应的MeasureSpec。在源码中,通过一个方法getChildMeasureSpec()就可以看出来。而这个方法在ViewGroup的measureChild中,代码如下。
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);
}
getChildMeasureSpec方法截图
* @param spec The requirements for this view
* @param padding The padding of this view for the current dimension and
* margins, if applicable
* @param childDimension How big the child wants to be in the current
* dimension
* @return a MeasureSpec integer for the child
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension)
通过上面两段源码可以看出,getChildMeasureSpec方法中各个参数的含义为:spec为父容器的
MeasureSpec,padding就是布局中padding,childDimension为子View的LayoutParams。
LayoutParams的参数有:
LayoutParams.MATCH_PARENT;LayoutParams.WRAP_CONTENT;还有就是固定大小了,LayoutParams在布局文件里就已经申明了。
getChildMeasureSpec清楚展示了普通View的MeasureSpec的创建规则,如下图所示
1、View的measure过程
view的measure过程由其measure方法来完成,measure方法是一个final类型的方法,这意味着子类不能重写此方法,在View的measure中回去调用onMeasure方法,因此只要看onMeasure方法即可,代码如下。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
上述代码很简洁,但是间接并不代表简单,setMeasuredDimension方法会设置View的宽高值,因此继续看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;
}
由于UNSPECIFIED用于系统内部,所以暂时不管,针对AT_MOST,EXACTLY两个,此时直接返回specSize,就是View测量后的大小。从 getDefaultSize这个方法来看,系统并没有处理wrap_content这个属性。而由MeasureSpec的创建可知,当出现wrap_content就是match_content。所以我们在自定义View时,如果直接继承自View,需要在onMeasure方法中对wrap_content这个属性做一下处理,比如在TextView中,它是直继承自View的,所有在他的onMeasure方法中找到了一句
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(widthSize, width);
}
其中withSize就是SpecSize,with是其实际所占大小。源码这一块写的特别长,不赘述。
2、ViewGroup的measure过程
对于ViewGroup来说,处理完成自己的measure过程以外,还会遍历去调用所有子元素的measure方法,各个子元素在递归去执行这个过程,和View不同的是,ViewGoup是一个抽象类,因此它没有重写View的onMeasure方法,但是提供了一个measureChild的方法。
那么ViewGoup是如何完成自己的measure的过程呢。由于ViewGoup是一个抽象类,需要被继承,所以它的measure需要由其子类去实现,也就是LinearLayout,或者RelativeLayout来实现,完成自己的measure过程又需要父容器的MeasureSpec,ViewGroup的父容器有可能还是ViewGroup或者DecorView,那么再往上追究,DecorView的测量又是怎么完成的呢?
对于DecorView来说,在ViewRootImpl中的measureHierarchy方法中有如下一段代码,它展示了DecorView的MeasureSpec的创建过程,desiredWindowHeight,childWidthMeasureSpec为屏幕的尺寸。为什么DecorView的MeasureSpec会在这里被创建呢,因为DecorView是放在PhoneWindow上的,这个过程PhoneWindow通过ViewRootImpl来实现。
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
接着看getRootMeasureSpec方法,
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
通过上述代码,DecorView的MeasureSpec的产生就明确了,那么ViewGroup的MeasureSpec的产生也就明确了。
ViewGroup产生了自身的MeasureSpec之后,开始测量每一个子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);
}
}
}
上述代码表明,ViewGroup会对每一个子元素进行measure,接下来看measureChild方法,源码如下。
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);
}
}
}
这段代码也比较好理解,measureChild就是取出子元素的LayoutParams,然后再通过getChilMeasure方法来创建子元素的MeasureSpec,接着讲MeasureSpec直接传递给子元素的measure方法进行测量。 getChilMeasure在之前也说明过了。
ViewGroup是一个抽象类,所以它并没有完成测量的全过程,它的onMeasure方法需要它的子类去完成,但是ViewGroup里面的方法是其子类所公共的。相当于基类。那么为什么ViewGroup不一起实现onMeasure呢,因为它的子类比如LinearLayout或者RelativeLayout他们的onMeasure方法差别很大,所以不好统一实现,还不如让子类自己去实现。
下面以LinearLayout总结ViewGroup子类的onMeasure方法是如何实现的。
首先看LinearLayout的onMeasure方法,源码如下所示:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
上述比较易懂,意思是当布局采用垂直时,使用measureVertical方法,当布局采用水平时,采用measureHorizontal方法。再以 measureVertical方法为例。measureVertical的源码特别长,这里只取一段代码,分析其大概逻辑。
// See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i) { //之间省略N行
<span style="white-space:pre"> </span>// Determine how big this child would like to be. If this or
// previous children have given a weight, then we allow it to
// use all available space (and we will shrink things later
// if needed).
measureChildBeforeLayout(
child, i, widthMeasureSpec, 0, heightMeasureSpec,
totalWeight == 0 ? mTotalLength : 0);
if (oldHeight != Integer.MIN_VALUE) {
lp.height = oldHeight;
}
final int childHeight = child.getMeasuredHeight();
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));//下面省略N行
}
从上面这段代码可以看出,系统会遍历子元素并对每个子元素执行measureChildBeforeLayout方法,这个方法内部会调用子元素的measure方法,这样子元素就开始进入measure过程,并且系统会通过mTotalLength这个变量来存储LinearLayout在竖直方法的初步高度。每遍历一个子元素,mTotalLength就会增加,增加的部分主要包括子元素的高度以及子元素在竖直方向上的margin等。当子元素测量完毕后,LinearLayout会测量自己的大小。源码如下,该段依然在 measureVertical这个方法中。最后调用setMeasuredDimension这个方法设置LinearLayout的宽高。onMeasure方法也就执行完了,垂直方向的LinearLayout测量完毕。
<span style="white-space:pre"> </span>// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
// Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
// Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
//。。。。。之间省略N行
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);