android View绘制流程分为3个步骤:分别是measure、 layout、 draw 。今天我们先来探究一下measure的过程。在上一节android开发笔记(三十四)中,我们研究了DecorView绘制到PhoneWindow上的流程,也就是View绘制的概况性流程(DecorView extends View), 我们回顾一下那个流程图:
今天我们要研究的就是measure阶段,上图中用红线圈住的部分。performMeasure函数会执行View的measure函数,而measure函数又会执行onMeasure函数。下面我们来分析一下这些测量相关的函数源码。
1. View.measure函数
代码如下:
-
/**
-
* <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) {
-
boolean optical = isLayoutModeOptical(this);
-
if (optical != isLayoutModeOptical(mParent)) {
-
Insets insets = getOpticalInsets();
-
int oWidth = insets.left + insets.right;
-
int oHeight = insets.top + insets.bottom;
-
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
-
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
-
}
-
-
// Suppress sign extension for the low bytes
-
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
-
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
-
-
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
-
-
// Optimize layout by avoiding an extra EXACTLY pass when the view is
-
// already measured as the correct size. In API 23 and below, this
-
// extra pass is required to make LinearLayout re-distribute weight.
-
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
-
|| heightMeasureSpec != mOldHeightMeasureSpec;
-
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
-
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
-
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
-
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
-
final boolean needsLayout = specChanged
-
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
-
-
if (forceLayout || needsLayout) {
-
// first clears the measured dimension flag
-
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
-
-
resolveRtlPropertiesIfNeeded();
-
-
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
-
if (cacheIndex < 0 || sIgnoreMeasureCache) {
-
// measure ourselves, this should set the measured dimension flag back
-
onMeasure(widthMeasureSpec, heightMeasureSpec);
-
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
-
} else {
-
long value = mMeasureCache.valueAt(cacheIndex);
-
// Casting a long to int drops the high 32 bits, no mask needed
-
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
-
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
-
}
-
-
// flag not set, setMeasuredDimension() was not invoked, we raise
-
// an exception to warn the developer
-
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
-
throw new IllegalStateException("View with id " + getId() + ": "
-
+ getClass().getName() + "#onMeasure() did not set the"
-
+ " measured dimension by calling"
-
+ " setMeasuredDimension()");
-
}
-
-
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
-
}
-
-
mOldWidthMeasureSpec = widthMeasureSpec;
-
mOldHeightMeasureSpec = heightMeasureSpec;
-
-
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
-
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
-
}
1.1 参数解释
void measure(int widthMeasureSpec, int heightMeasureSpec) 先看这个函数的2个参数:
1.1.1 widthMeasureSpec参数
int widthMeasureSpec:父容器给的测量规格(宽度),用于测量当前子View的宽度,也就是说在测量View时不但要考虑自身内容的宽度,还要考虑父容器的限制, 例如父容器要求子View的宽度最大不能超过父容器它自身的宽度,这就是一个限制,这个宽度限制用这个参数 ”widthMeasureSpec“ 来表示,这个int型参数总共占4个字节32位,高2位代表”测量模式“,后30位是父容器给子View指定的宽度大小。也就是说widthMeasureSpec这个规格,总共由 测试模式与宽度大小组成。这里我们解释一下测量模式,最后随着对测量程序的深入分析,来指出这些测量模式有什么用。
测量模式分为3种:
1. MeasureSpec.UNSPECIFIED: 未限制,子VIEW想要多大就多大(宽、高),常见的是Scrollview作为父容器,ListView控件。
2. MeasureSpec.AT_MOST:限制子View的大小最大不能超过父容器的大小specialSize,specialSize就是widthMeasureSpec中低30个bit所表示的大小(包括宽、高)。
3. MeasureSpec.EXACTLY:父容器已经测出了子VIEW的大小, 为子VIEW指定了一个精确值,这个值就是specialSize。通常子VIEW的宽高属性是一个确定值或match_parent。
1.1.2 heightMeasureSpec参数
int heightMeasureSpec:父容器给的测量规格(高度),用于测量当前子View的高度,它的概念可以参考上述 widthMeasureSpec,也有3种测试模式,在此不再赘述。测量规格暂且先简单介绍到这里,我们继续分析测量过程,在分析代码的过程中理解MeasureSpec上述View的measure方法中可以得知最终调用了onMeasure(widthMeasureSpec,heightMeasureSpec),测量当前View的核心代码都在这个函数里,我们可以看到在这里将widthMeasureSpec和heightMeasureSpec续传给了onMeasure。
2. View.onMeasure
-
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
-
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
-
}
2.1 参数解释
参数:widthMeasureSpec,heightMeasureSpec,父容器给的测量约束(测量规格);
widthMeasureSpec:horizontal space requirements as imposed by the parent. 水平空间上的约束,这个约束来自父容器; heightMeasureSpec:Vertical space requirements as imposed by the parent. 垂直空间上的约束,这个约束来自父容器。
2.2 onMeasure函数体分析
这个方法的作用测量当前View,和它里面的内容来决定View的宽、高
2.2.1 setMeasuredDimension作用
onMeasure中调用了setMeasuredDimension,用来存储View的宽高的,因为最终调用了setMeasuredDimensionRaw,而在setMeasuredDimensionRaw函数里将measuredWidth和measuredHeight保存在了View的成员变量里,代码如下:
-
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
-
mMeasuredWidth = measuredWidth;
-
mMeasuredHeight = measuredHeight;
-
-
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
-
}
2.2.2 onMeasure深入分析
onMeasure通常会被子类重写,如FrameLayout里就重写了onMeasure函数,用于重新测量自己的宽高以及childview的宽高。那么我们就来分析一下FrameLayout的这个onMeasure函数,先贴出代码如下:
-
@Override
-
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-
int count = getChildCount();
-
-
final boolean measureMatchParentChildren =
-
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
-
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
-
mMatchParentChildren.clear();
-
-
int maxHeight = 0;
-
int maxWidth = 0;
-
int childState = 0;
-
-
for (int i = 0; i < count; i++) {
-
final View child = getChildAt(i);
-
if (mMeasureAllChildren || child.getVisibility() != GONE) {
-
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
-
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
maxWidth = Math.max(maxWidth,
-
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
-
maxHeight = Math.max(maxHeight,
-
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
-
childState = combineMeasuredStates(childState, child.getMeasuredState());
-
if (measureMatchParentChildren) {
-
if (lp.width == LayoutParams.MATCH_PARENT ||
-
lp.height == LayoutParams.MATCH_PARENT) {
-
mMatchParentChildren.add(child);
-
}
-
}
-
}
-
}
-
-
// Account for padding too
-
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
-
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
-
-
// Check against our minimum height and width
-
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
-
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
-
-
// Check against our foreground's minimum height and width
-
final Drawable drawable = getForeground();
-
if (drawable != null) {
-
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
-
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
-
}
-
-
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
-
resolveSizeAndState(maxHeight, heightMeasureSpec,
-
childState << MEASURED_HEIGHT_STATE_SHIFT));
-
-
count = mMatchParentChildren.size();
-
if (count > 1) {
-
for (int i = 0; i < count; i++) {
-
final View child = mMatchParentChildren.get(i);
-
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
-
-
final int childWidthMeasureSpec;
-
if (lp.width == LayoutParams.MATCH_PARENT) {
-
final int width = Math.max(0, getMeasuredWidth()
-
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
-
- lp.leftMargin - lp.rightMargin);
-
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
-
width, MeasureSpec.EXACTLY);
-
} else {
-
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
-
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
-
lp.leftMargin + lp.rightMargin,
-
lp.width);
-
}
-
-
final int childHeightMeasureSpec;
-
if (lp.height == LayoutParams.MATCH_PARENT) {
-
final int height = Math.max(0, getMeasuredHeight()
-
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
-
- lp.topMargin - lp.bottomMargin);
-
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
-
height, MeasureSpec.EXACTLY);
-
} else {
-
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
-
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
-
lp.topMargin + lp.bottomMargin,
-
lp.height);
-
}
-
-
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
-
}
-
}
-
}
2.2.2.1 onMeasure参数解释
首先这个onMeasure的功能是测量Framelayout这个父布局的宽高,onMeasure的两个参数widthMeasureSpec和heightMeasureSpec是这个FrameLayout的父容器给这个FrameLayout的宽、高约束规格,在这里我们以DecorView为例(DecorView继承于FrameLayout,DecorView的onMeasure最终还是调用了FrameLayout的onMeasure),它的父容器是window窗口,window窗口给DecorView的宽高约束是:大小为windowSize,测量模式为MeasureSpec.EXACTLY,说明DecorView将来的宽高正好是一个确切值:windowSize,即宽高铺满整个屏幕。
下面两行代码就是window窗口为Decorview创建的宽,高规格,我们发现大小为windowSize,模式为EXACTLY
widthMeasureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
heightMeasureSpec= MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
那么为什么说在EXACTLY模式下,DecorView的宽高,正好是windowSize呢,这个结论是如何得出的呢?
我们看onMeasure函数中的这段代码:
-
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
-
resolveSizeAndState(maxHeight, heightMeasureSpec,
-
childState << MEASURED_HEIGHT_STATE_SHIFT));
这个函数比较熟悉吧,它就是最终用来保存一个容器ViewGroup(或view)的宽高的,将宽高值赋值给了view的成员变量:mMeasuredWidth,mMeasuredHeight;
resolveSizeAndState函数,在这里分别确定了宽高值,我们来分析一下resolveSizeAndState内部代码,就会得出上面那个结论:EXACTLY模式下,DecorView的宽高,正好是windowSize。贴出resolveSizeAndState函数的函数体如下:
-
/**
-
* Utility to reconcile a desired size and state, with constraints imposed
-
* by a MeasureSpec. Will take the desired size, unless a different size
-
* is imposed by the constraints. The returned value is a compound integer,
-
* with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and
-
* optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the
-
* resulting size is smaller than the size the view wants to be.
-
*
-
* @param size How big the view wants to be.
-
* @param measureSpec Constraints imposed by the parent.
-
* @param childMeasuredState Size information bit mask for the view's
-
* children.
-
* @return Size information bit mask as defined by
-
* {@link #MEASURED_SIZE_MASK} and
-
* {@link #MEASURED_STATE_TOO_SMALL}.
-
*/
-
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
-
final int specMode = MeasureSpec.getMode(measureSpec);
-
final int specSize = MeasureSpec.getSize(measureSpec);
-
final int result;
-
switch (specMode) {
-
case MeasureSpec.AT_MOST:
-
if (specSize < size) {
-
result = specSize | MEASURED_STATE_TOO_SMALL;
-
} else {
-
result = size;
-
}
-
break;
-
case MeasureSpec.EXACTLY:
-
result = specSize;
-
break;
-
case MeasureSpec.UNSPECIFIED:
-
default:
-
result = size;
-
}
-
return result | (childMeasuredState & MEASURED_STATE_MASK);
-
}
-
final int specMode = MeasureSpec.getMode(measureSpec);
-
获取父容器给的宽(高)规格中的测试模式,在这里specMode 是EXACT,因为Window(父容器)给Decorview(子ViewGroup)
-
的测试模式就是EXACT ,final int specSize = MeasureSpec.getSize(measureSpec);获取获取父容器给的宽(高)规格中的大小,
-
在这里因为测试模式是EXACT,所以将来Decorview的宽(高)就是一个精确值:specSize,代码依据如下:
-
case MeasureSpec.EXACTLY:
-
result = specSize;
-
break;
Ok,我们总结一下我们上面所讲的,我们主要说明了
onMeasure(int widthMeasureSpec, int heightMeasureSpec)中的两个参数widthMeasureSpec和heightMeasureSpec,
是父容器给的测量规格,用以测量当前FrameLayout(ViewGroup)的宽和高,同时当测量规格中的测量模式为EXACT时,
那么当前FrameLayout(ViewGroup)的宽和高就是测量规格中指定的宽和高,也就是说父容器指示给它的大小。
2.2.2.2 测量自己先得测量出子child
1. for循环测量各child的宽高,才能测量出自己的宽高
接下来继续看一下这个FrameLayout(ViewGroup)的测量函数onMeasure,内部有一个for循环,用于测量当前FrameLayout的子View。这里为什么要去测量FrameLayout的子View呢?如果子View又是一个ViewGroup容器类型,那么将继续嵌套递归测量更深层次的子View,直到子View不是一个容器,就可以结束了,并逐渐递归返回了。这里我们先贴出代码再加以分析,for循环测量子View的代码如下:
-
for (int i = 0; i < count; i++) {
-
final View child = getChildAt(i);
-
if (mMeasureAllChildren || child.getVisibility() != GONE) {
-
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
-
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
maxWidth = Math.max(maxWidth,
-
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
-
maxHeight = Math.max(maxHeight,
-
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
-
childState = combineMeasuredStates(childState, child.getMeasuredState());
-
if (measureMatchParentChildren) {
-
if (lp.width == LayoutParams.MATCH_PARENT ||
-
lp.height == LayoutParams.MATCH_PARENT) {
-
mMatchParentChildren.add(child);
-
}
-
}
-
}
-
}
首先,我们来回答我们刚才提出的问题,为什么要去测量子View?这是因为当前容器的大小有时候会受子View大小的影响,例如当FrameLaout的宽高属性设置为wrap_content时。FrameLaout的宽应该取各个子View宽度的最大值,同理高度也一样,取Views高度的最大值。
下面这个函数就是用于测量子View的宽高。
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
下面这段代码就是用于获取各个子View的宽高的最大值。
-
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
maxWidth = Math.max(maxWidth,
-
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
-
maxHeight = Math.max(maxHeight,
-
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
我们再回头看一下这行代码:
-
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
-
resolveSizeAndState(maxHeight, heightMeasureSpec,
-
childState << MEASURED_HEIGHT_STATE_SHIFT));
其中resolveSizeAndState函数,给当前FrameLayout的默认宽高正是maxWidth,maxHeight,然后再结合widthMeasureSpec和heightMeasureSpec来共同决定当前FrameLayout容器的最终大小。
2. measureChildWithMargins分析(深入分析如何测量child)
measureChildWithMargins配合for循环用来测量FrameLayout的各个子View的宽高,我们来分析它的源码。源码如下:
-
/**
-
* 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);
-
}
(1)参数说明
protected void measureChildWithMargins(
View child, : 被测量的child
int parentWidthMeasureSpec, 父容器的测量宽度规格(即child的 “祖父容器” 给child的父容器的测量规格)
int widthUsed, child的父容器已被其它子child占用的宽度空间
int parentHeightMeasureSpec, 父容器的测量高度规格(即child的 “祖父容器” 给child的父容器的测量规格)
int heightUsed child的父容器已被其它子child占用的高度空间
)
(2). 测量child的流程
1)按照惯例,需先得到child的父容器指定给child的测量规格,childWidthMeasureSpec 和childHeightMeasureSpec 。
measureChildWithMargins函数的如下代码实现了childWidthMeasureSpec和childHeightMeasureSpec的生成。
-
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);
我们可以看到getChildMeasureSpec函数,用到了parentWidthMeasureSpec和parentHeightMeasureSpec,可见子child的测量规格的生成离不开它的父容器的测量规格。我们深入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
-
* @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) {
-
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);
-
}
目前对于DecorView来说,这里的specMode就是EXACT, specSize实质就是child的父容器FrameLayout的宽高。
int size = Math.max(0, specSize - padding);中的size实质就是父容器DecorView留给child的最大空间。
当
childDimension>0 时,说明child的宽,高属性是一个数值,一个确切的值,那么结合父容器DecorView的测量模式为EXACT,会为child生成一个EXACT模式的测量规格,大小就是这个确切值:childDimension; 最终计算出两个测量规格childWidthMeasureSpec 和childHeightMeasureSpec,它们的测量模式都是EXACT,指定child的大小是一个精确值childDimension。
childDimension为match_parent时,说明child的宽,高是铺满整个父容器DecorView的,所以child的宽,高是确定的,就是父容器的宽,高(这里为可利用区域的宽,高)。所以在这里为child生成了一个EXACT模式的测量规格,并且大小是size(父容器的宽高减去父容器的padding和child的margin,即父容器的可利用区域,同时也是child所能占用的空间)。最终计算出两个测量规格childWidthMeasureSpec 和childHeightMeasureSpec,它们的测量模式都是EXACT,指定child的大小是一个精确值size。
childDimension为wrap_content,说明child的宽,高是随自己内容的大小而变化的,但是最大不能超过父容器给的可利用区域,即size. 所以在这里为child生成了一个测量模式为AT_MOST的测量规格,大小为size,也就是说 child想要多大(宽和高)凭自己的内容大小,但是最大不能超过 size。最终生成两个测量规格childWidthMeasureSpec 和childHeightMeasureSpec,它们的测量模式都是AT_MOST,指定child的大小最大不能超过size的值。
好了,有了childWidthMeasureSpec 和childHeightMeasureSpec,就可以用这两个测量规格(约束)来测量自己了,childWidthMeasureSpec 和childHeightMeasureSpec实质就是child的父容器给child的测量规格,因为它们的生成,用到了父容器自己的测量规格,结合child的 宽高属性配置(childDimension),所以说childWidthMeasureSpec 和childHeightMeasureSpec是父容器(DecorView)给child的测量规格,一点儿也不为过。
在这里我们只分析了父容器测量规格是EXACT的情况下,如何生成child的测量规格,至于其它测量模式的分支代码,读者可以自行分析。
Ok,child得到了childWidthMeasureSpec 和childHeightMeasureSpec,接下来就可以测量自己了,然后调用
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
这行代码我们似曾相识,我们可以知道child.measure完后,又会调用child.onMeasure,就又会走一遍我们上面分析的onMeasure的代码,如果当前child是一个类似于DecorView的ViewGroup容器的话,有会用for循环去测量当前child的child,是一个不断嵌套递归的过程。如果child本身就是叶子节点,是一个普通的view,则for循环不会进入,直接调用onMeasure中的
-
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
-
resolveSizeAndState(maxHeight, heightMeasureSpec,
-
childState << MEASURED_HEIGHT_STATE_SHIFT));
就能测量出它的大小,然后递归返回child测量出的宽,高,作为child的父容器DecorView(FrameLayout extends ViewGroup)测量宽高的依据或者说参数吧。
3. 总结:
所谓递归,就是要测量自身(继承于ViewGroup的容器)的大小,先得测量出child的大小,要想测出child的大小,又得先测量出child的child的大小。最终的叶子节点child的大小测量出之后,会层层返回(归的含义),逐层测量出每一个阶段的child的大小,最终递归到最外层的父容器,从而得到最初的父容器的大小,当然在这个递归过程中,各层的容器或view都得到了测量。所以,一个View(ViewGroup)的测量是一个递归的过程,同时每一层View的测量都离不开上层容器指定给它的两个测量规格:childWidthMeasureSpec 和childHeightMeasureSpec。
文章来源: blog.csdn.net,作者:冉航--小虾米,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/gaoxiaoweiandy/article/details/101163576