ViewGroup的measure流程
Posted 呼啸
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ViewGroup的measure流程相关的知识,希望对你有一定的参考价值。
上篇文章我们讲了View的measure流程。这篇我们讲ViewGroup的measure流程。我们知道,对于ViewGroup来说,它不仅要测量自身,还要遍历地调用子元素的measure元素。但ViewGroup中没有定义onMeasure方法,不过他定义了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);
我们看这段代码做了什么事。他对子元素进行遍历,并且,调用了measureChild方法,把子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);
这里,首先调用getLayoutParams获得子元素的LayoutParams属性,然后获取子元素的measureSpec并调用子元素的measure方法进行测量。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)
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 them 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);
这个方法接收的第一个参数是parentMeasureSpec。也就是说是根据父容器的MeasureSpec模式再结合子元素的LayoutParams属性来得出的子元素的MeasureSpec属性。有一点需要注意的是,如果父容器的MeasureSpec属性为AT_MOST,子元素的LayoutParams属性WRAP_CONTENT,那么上面的这块代码:
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;
我们发现子元素的MeasureSpec属性也为AT_MOST。他的specSize的值为父容器的specSize减去padding的值。换句话说,这和子元素设置LayoutParams的属性为match_parent的效果是一样的。为了解决这个问题。需要在LayoutParams属性为wrap_content的时候指定一下默认的宽和高。ViewGroup没有提供onMeasure方法,而是让子其子类各自实现测量的方法。原因就是ViewGroup有不同的布局需要,实在无法统一。接下来,咱们随便看一个ViewGroup的子类LinerLayout的measure流程。现在我们先来看下它的onMeasure方法。代码如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
if (mOrientation == VERTICAL)
measureVertical(widthMeasureSpec, heightMeasureSpec);
else
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
这个方法的逻辑看上去非常简单。首先,判断如果是垂直方向,则调用measureVertical方法。否则就会调用measureHorizontal方法。下面我们就看看meaureVertical方法:
void measureVertical(int widthMeasureSpec, int heightMeasureSpec)
mTotalLength = 0;
int maxWidth = 0;
int childState = 0;
int alternativeMaxWidth = 0;
int weightedMaxWidth = 0;
boolean allFillParent = true;
float totalWeight = 0;
final int count = getVirtualChildCount();
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean matchWidth = false;
boolean skippedMeasure = false;
final int baselineChildIndex = mBaselineAlignedChildIndex;
final boolean useLargestChild = mUseLargestChild;
int largestChildHeight = Integer.MIN_VALUE;
int consumedExcessSpace = 0;
int nonSkippedChildCount = 0;
// See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i)
final View child = getVirtualChildAt(i);
if (child == null)
mTotalLength += measureNullChild(i);
continue;
if (child.getVisibility() == View.GONE)
i += getChildrenSkipCount(child, i);
continue;
nonSkippedChildCount++;
if (hasDividerBeforeChildAt(i))
mTotalLength += mDividerHeight;
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace)
// Optimization: don't bother measuring children who are only
// laid out using excess space. These views will get measured
// later if we have space to distribute.
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
else
if (useExcessSpace)
// The heightMode is either UNSPECIFIED or AT_MOST, and
// this child is only laid out using excess space. Measure
// using WRAP_CONTENT so that we can find out the view's
// optimal height. We'll restore the original height of 0
// after measurement.
lp.height = LayoutParams.WRAP_CONTENT;
// 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).
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
final int childHeight = child.getMeasuredHeight();
if (useExcessSpace)
// Restore the original height and record how much space
// we've allocated to excess-only children so that we can
// match the behavior of EXACTLY measurement.
lp.height = 0;
consumedExcessSpace += childHeight;
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
if (useLargestChild)
largestChildHeight = Math.max(childHeight, largestChildHeight);
/**
* If applicable, compute the additional offset to the child's baseline
* we'll need later when asked @link #getBaseline.
*/
if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1))
mBaselineChildTop = mTotalLength;
// if we are trying to use a child index for our baseline, the above
// book keeping only works if there are no children above it with
// weight. fail fast to aid the developer.
if (i < baselineChildIndex && lp.weight > 0)
throw new RuntimeException("A child of LinearLayout with index "
+ "less than mBaselineAlignedChildIndex has weight > 0, which "
+ "won't work. Either remove the weight, or don't set "
+ "mBaselineAlignedChildIndex.");
boolean matchWidthLocally = false;
if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT)
// The width of the linear layout will scale, and at least one
// child said it wanted to match our width. Set a flag
// indicating that we need to remeasure at least that view when
// we know our width.
matchWidth = true;
matchWidthLocally = true;
final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
childState = combineMeasuredStates(childState, child.getMeasuredState());
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
if (lp.weight > 0)
/*
* Widths of weighted Views are bogus if we end up
* remeasuring, so keep them separate.
*/
weightedMaxWidth = Math.max(weightedMaxWidth,
matchWidthLocally ? margin : measuredWidth);
else
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
i += getChildrenSkipCount(child, i);
if (nonSkippedChildCount > 0 && hasDividerBeforeChildAt(count))
mTotalLength += mDividerHeight;
if (useLargestChild &&
(heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED))
mTotalLength = 0;
for (int i = 0; i < count; ++i)
final View child = getVirtualChildAt(i);
if (child == null)
mTotalLength += measureNullChild(i);
continue;
if (child.getVisibility() == GONE)
i += getChildrenSkipCount(child, i);
continue;
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
child.getLayoutParams();
// Account for negative margins
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
// 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;
// Either expand children with weight to take up available space or
// shrink them if they extend beyond our current bounds. If we skipped
// measurement on any children, we need to measure them now.
int remainingExcess = heightSize - mTotalLength
+ (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
if (skippedMeasure
|| ((sRemeasureWeightedChildren || remainingExcess != 0) && totalWeight > 0.0f))
float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
mTotalLength = 0;
for (int i = 0; i < count; ++i)
final View child = getVirtualChildAt(i);
if (child == null || child.getVisibility() == View.GONE)
continue;
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final float childWeight = lp.weight;
if (childWeight > 0)
final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
remainingExcess -= share;
remainingWeightSum -= childWeight;
final int childHeight;
if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY)
childHeight = largestChildHeight;
else if (lp.height == 0 && (!mAllowInconsistentMeasurement
|| heightMode == MeasureSpec.EXACTLY))
// This child needs to be laid out from scratch using
// only its share of excess space.
childHeight = share;
else
// This child had some intrinsic height to which we
// need to add its share of excess space.
childHeight = child.getMeasuredHeight() + share;
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
Math.max(0, childHeight), MeasureSpec.EXACTLY);
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
lp.width);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
// Child may now not fit in vertical dimension.
childState = combineMeasuredStates(childState, child.getMeasuredState()
& (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
lp.width == LayoutParams.MATCH_PARENT;
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
// TODO: Should we recompute the heightSpec based on the new total length?
else
alternativeMaxWidth = Math.max(alternativeMaxWidth,
weightedMaxWidth);
// We have no limit, so make all weighted views as tall as the largest child.
// Children will have already been measured once.
if (useLargestChild && heightMode != MeasureSpec.EXACTLY)
for (int i = 0; i < count; i++)
final View child = getVirtualChildAt(i);
if (child == null || child.getVisibility() == View.GONE)
continue;
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
float childExtra = lp.weight;
if (childExtra > 0)
child.measure(
MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(largestChildHeight,
MeasureSpec.EXACTLY));
if (!allFillParent && widthMode != MeasureSpec.EXACTLY)
maxWidth = alternativeMaxWidth;
maxWidth += mPaddingLeft + mPaddingRight;
// Check against our minimum width
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
if (matchWidth)
forceUniformWidth(count, heightMeasureSpec);
这段代码足足有三百行,还是比较多的。但原理其实不难,大致流程就是,定义了mTotalLength用来存储LinerLayout在垂直方向的高度。然后遍历子元素,根据MeasureSpec模式分别计算每个子元素的高度。如果是wrap_content,则将每个子元素的高度和margin垂直高度等值相加并赋值给mTotalLength。最后再加上垂直方向的padding的值。如果布局高度设置为macth_parent或者具体的值,则就和View的measure流程是一样的。
以上是关于ViewGroup的measure流程的主要内容,如果未能解决你的问题,请参考以下文章