measure过程要分情况来看,如果只是一个原始的View,那么通过measure方法就完成了其测量过程,如果是一个ViewGroup,除了需要完成自己的测量外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个流程.
在分析源码前,先简单介绍下View的测量模式:
MeasureSpec
measureSpec代表一个32位int值,高2位代表SpecMode,低30位代表SpecSize,其中SpecMode是指测量模式,而SpecSize是指在某种测量模式下的规格大小。
MeasureSpec将SpecMode和SpecSize打包成一个int值,通过其静态方法getMode(int measureSpec)方法可以获取specMode
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
getSize(int measureSpec)获取specSize
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
通过makeMeasureSpec(int size, int mode)可以打包生成MeasureSpec
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
测量模式分为3种:
UNSPECIFIED:父控件不对View有任何限制,要多大给多大,一般用于系统内部
EXACTLY:父控件已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。它对应与LayoutParams中的match_parent或者具体的数值这2种情况.
AT_MOST:父控件指定了一个可用大小即SpecSize,View的大小不能大于这个值,它对应于LayoutParams中的wrap_content。
我们先从ViewGroup源码下手,它是一个抽象类,因此它没有重写View的onMeasure方法,其具体的测量过程需要各个子类去实现,比如LinearLayout、ReleativeLayout等等。但它提供了一个叫做measureChildren的方法,用于测量它所有的子控件.
measureChildren
/**
* 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.
*
* @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) {
//如果子控件是可见的,则调用下面的方法测量该子控件,传入目标子控件,父控件的宽高测量模式3个参数
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
measureChild
/**
* 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);
//最后调用子控件的measure方法进行测量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
此外,ViewGroup还提供了一个与上面方法类似的方法叫做measureChildWithMargins,它在计算剩余空间的时候不仅排除了内边距外,而且还排除了子View的外边距,以及父控件自身已使用的空间.
measureChildWithMargins
/**
* 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();
//计数子控件的宽度测量模式(比measureChild方法多传入了子控件的外边距和父控件已使用的宽度)
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
//计数子控件的高度测量模式(比measureChild方法多传入了子控件的外边距和父控件已使用的高度)
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
//测量该子控件
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
getChildMeasureSpec
/**
* Does the hard part of measureChildren: figuring out the MeasureSpec to
* pass to a particular child. This method figures out the right MeasureSpec
* for one dimension (height or width) of one child view.
*
* The goal is to combine information from our MeasureSpec with the
* LayoutParams of the child to get the best possible results. For example,
* if the this view knows its size (because its MeasureSpec has a mode of
* EXACTLY), and the child has indicated in its LayoutParams that it wants
* to be the same size as the parent, the parent should ask the child to
* layout given an exact size.
*
* @param spec The requirements for this view 父控件的宽高测量模式
* @param padding The padding of this view for the current dimension and
* margins, if applicable 父控件的内边距或者子View的外边距
* @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) {
//获取父控件的测量模式
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) {
//子控件布局中的宽或高不等于0,则采用子控件的布局中定义的宽或高,即lp.width或lp.height
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
// 子控件布局参数是MATCH_PARENT,则获取父控件剩余空间,模式为精确
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.
//子控件布局中宽高参数是WRAP_CONTENT,则获取父控件的剩余空间,模式为最多
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
//如果子控件的布局中的宽高不为0,则采用布局中定义的宽或高,模式为精确
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 = 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的创建与父控件的MeasureSpec和子控件本身的LayoutParams、margin,以及父控件的padding,已使用的宽高有关.
measure
在measureChild方法中还会调用各个子控件的measure方法进行子控件的本身的测量,该方法是View的方法,它是一个final类型方法,这意味着子类不能重写次方法,在View的measure方法中会去调用View的onMeasure方法.
/**
* <p>
* This is called to find out how big a view should be. The parent
* supplies constraint information in the width and height parameters.
* </p>
*
* <p>
* The actual measurement work of a view is performed in
* {@link #onMeasure(int, int)}, called by this method. Therefore, only
* {@link #onMeasure(int, int)} can and must be overridden by subclasses.
* </p>
*
*
* @param widthMeasureSpec Horizontal space requirements as imposed by the
* parent 子控件的宽度测量模式
* @param heightMeasureSpec Vertical space requirements as imposed by the
* parent 子控件的高度测量模式
*
* @see #onMeasure(int, int)
*/
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//......
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
//看到没,当调用measure方法,它会触发View的onMeasure方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
//.......
}
onMeasure
/* 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}.
*
* @see #getMeasuredWidth()
* @see #getMeasuredHeight()
* @see #setMeasuredDimension(int, int)
* @see #getSuggestedMinimumHeight()
* @see #getSuggestedMinimumWidth()
* @see android.view.View.MeasureSpec#getMode(int)
* @see android.view.View.MeasureSpec#getSize(int)
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//最终设置View的大小
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
getSuggestedMinimumWidth
/**
* Returns the suggested minimum width that the view should use. This
* returns the maximum of the view's minimum width)
* and the background's minimum width
* ({@link android.graphics.drawable.Drawable#getMinimumWidth()}).
* <p>
* When being used in {@link #onMeasure(int, int)}, the caller should still
* ensure the returned width is within the requirements of the parent.
*
* @return The suggested minimum width of the view.
*/
protected int getSuggestedMinimumWidth() {
//返回当前子控件的最小宽度或者背景图的宽度
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
getSuggestedMinimumHeight同理,返回的是当前子控件的最小高度或背景图的高度这里就不贴源码了。
这里有必要解释下最小宽/高度和背景图宽高
mMinWidth :对应布局中android:minWidth属性,默认为0
mMinHeight :对应布局中android:mMinHeight 属性,默认为0
mBackground.getMinimumHeight():返回的是Drawable的原始高度,即图片的尺寸,例如ImageView设置了背景图片就会有
mBackground.getMinimumWidth():返回的是Drawable的原始宽度
getMinimumHeight
/**
* Returns the minimum height suggested by this Drawable. If a View uses this
* Drawable as a background, it is suggested that the View use at least this
* value for its height. (There will be some scenarios where this will not be
* possible.) This value should INCLUDE any padding.
*
* @return The minimum height suggested by this Drawable. If this Drawable
* doesn't have a suggested minimum height, 0 is returned.
*/
public int getMinimumHeight() {
final int intrinsicHeight = getIntrinsicHeight();
return intrinsicHeight > 0 ? intrinsicHeight : 0;
}
getMinimumHeight代码类似就不贴了。
getDefaultSize
接着看该方法的内部实现
/**
* Utility to return a default size. Uses the supplied size if the
* MeasureSpec imposed no constraints. Will get larger if allowed
* by the MeasureSpec.
* 子控件的最小宽/高
* @param size Default size for this view
* 子控件的宽/高测量模式
* @param measureSpec Constraints imposed by the parent
* @return The size this view should be.
*/
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;
}
从上面的源码中分析可知,getDefaultSize方法其实返回的其实就是measureSpec的specSize,即子View本身的测量规格大小;而这个measureSpec就是子View在父View中的getChildMeasureSpec计算后的测量模式.
总结
1.从ViewGroup的getChildMeasureSpec中分析可知,无论父控件是精确模式还是最多模式,只要子View的布局参数是match_parent或者wrap_content,那么子View获取的宽高都是父控件的剩余空间,除非子View重写了onMeasure方法去setMeasuredDimension定义宽高,因为View的onMeasure方法其实并没有修改宽高信息,具体从getDefaultSize源码中可以看出.系统提供的控件例如TextView,ImageView等等都是有重写onMeasure方法的.
这也就能解释为什么我们自定义的View明明写着wrap_content,但运行起来却是匹配父窗体的效果.解决这个问题除了重写onMeasure的方法之外,我们还可以在布局文件中定义android:width和android:height为具体的某个dp值也是可以解决的,因为getChildMeasureSpec方法中判断如果子View有具体的布局宽高,则会优先使用.
2.从getChildMeasureSpec还可以知道,只要父控件的宽高模式是最多,那么无论子View的布局参数是match_parent或者wrap_content,它的模式也只能是最多.
3.如果父控件是精确模式,那么只要子View的宽高布局参数不是wrap_content,则子View的模式也是精确模式.
4.只要子View的宽高布局参数是具体的dp值,那么子view的模式一定是精确模式.