onMeasure()方法解读
measureWithLargestChild 作用 : 该属性为true的时候, 所有带权重的子元素都会具有最大子元素的最小尺寸; 且只有当父view布局方向上的宽度或高度为wrap_content才有效
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 根据VERTICAL或者HORIZONTAL 分别不同的去计算相应布局
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
跟踪
measureVertical()
VERTICAL垂直方向的看看
自定义View的onMeasure()方法中,最后要记得调用setMeasuredDimension()
更新你计算好的大小信息等
/**
* Measures the children when the orientation of this LinearLayout is set
* to {@link #VERTICAL}.
*
* @param widthMeasureSpec Horizontal space requirements as imposed by the parent.
* @param heightMeasureSpec Vertical space requirements as imposed by the parent.
*
* @see #getOrientation()
* @see #setOrientation(int)
* @see #onMeasure(int, int)
*/
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = 0; //记录总长度
int maxWidth = 0; //最大宽
int childState = 0; //子View的状态
int alternativeMaxWidth = 0; //改变源生的最大宽度
int weightedMaxWidth = 0; //加权最大宽度
boolean allFillParent = true; //填充满父布局
float totalWeight = 0; //总加权数
final int count = getVirtualChildCount(); //获取子view的数量
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);
//如果为空 则加0 measureNullChild()返回的是 0
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
//如果为不可见 则i 加0
if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
//除去上面的判断条件
// 下面的为没有跳过的子布局控件
nonSkippedChildCount++;
//hasDividerBeforeChildAt 查看又没有divider分割线在子控件前面
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
//获取控件的属性信息
final LayoutParams lp =(LayoutParams)child.getLayoutParams();
//下面开始根据测量模式进行逻辑运算
//将weight信息提出来加到totalWeight
totalWeight += lp.weight;
//如果高度为0 且设置了比重weight useExcessSpace= true
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
// 高度为精确 且使用了比重
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
//优化:不用费心测量那些只使用多余空间(比重)的孩子。如果我们有空间分配,这些视图将在稍后测量。
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true; //这里设置跳过测量的标识符
} else {
if (useExcessSpace) {
//高度模式不是UNSPECIFIED就是AT_MOST,并且这个孩子只是用多余的空间布置的。测量使用wrap_content以便我们可以找到视图的最佳高度。我们将恢复0的原始高度测量后
lp.height = LayoutParams.WRAP_CONTENT;
}
//不使用比重的情况
//确定这个孩子想要多大。如果这个或以前的孩子已经给了一个比重,那么我们允许它使用所有可用的空间(如果需要,我们将在以后缩小东西)。
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
//在layout之前测量子控件
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
final int childHeight = child.getMeasuredHeight();
if (useExcessSpace) {
//恢复原来的高度和记录我们分配多少空间excess-only孩子,这样我们可以匹配精确测量的行为。
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;
}
//这个是重新计算一下总高度信息
// useLargestChild 作用 : 该属性为true的时候, 所有带权重的子元素都会具有最大子元素的最小尺寸; 且只有当父view布局方向上的宽度或高度为wrap_content才有效(网上说的 我并没有验证)
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;
//要么扩大儿童比重可用空间或缩小他们是否超出我们目前的界限。如果我们跳过测量任何孩子,现在我们需要衡量他们。
// 剩余可用空间
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);
}
}
measureChildBeforeLayout()
方法
/**
* child :当前要测量的子控件
* totalWidth 父布局中已经被其它子控件使用的宽度
* childIndex:当前子控件存在的下标
* widthMeasureSpec:父控件的宽度大小参数
* heightMeasureSpec:父控件的高度大小参数
* totalHeight 已经使用的高度
**/
void measureChildBeforeLayout(View child, int childIndex,
int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
int totalHeight) {
measureChildWithMargins(child, widthMeasureSpec, totalWidth,
heightMeasureSpec, totalHeight);
}
measureChildWithMargins()
/**
* child :当前要测量的子控件
* parentWidthMeasureSpec:父控件的宽度大小参数
* widthUsed :父布局中已经被其它子控件使用的宽度
* parentHeightMeasureSpec :父控件的高度大小参数
* heightUsed :父布局中已经被其它子控件使用的高度
*/
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//计算获取控件的测量值
//传入( 父控件的宽度测量值,父控件的padding左右值+Margin左右值+加上已经被使用的宽度值,本控件的宽度)
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);
}
getChildMeasureSpec(int spec, int padding, int childDimension)
计算出子控件的测量参数
/** spec :父控件的测量信息
* padding : 父控件padding+这个子控件的margin的信息
* childDimension : 子控件的需要的测量大小
**/
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) {
// 精确模式的时候
case MeasureSpec.EXACTLY:
// 子控件大小>=0
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//给最大值 填充满
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 子控件不能大于父控件的剩余空间
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 父母对我们实施了一个最大尺寸
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) {
//这个跟MATCH_PARENT是一样的大小 且模式一样
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 父布局未定具体大小 看子布局需要的具体大小
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);
}
总结:当父布局三个模式对应的子控件三个模式的大小为:
- MeasureSpec.EXACTLY:
childDimension为精确值>0
:返回控件需要的大小 +MeasureSpec.EXACTLY
LayoutParams.MATCH_PARENT
:返回父布局剩下的空间+ MeasureSpec.EXACTLY
LayoutParams.WRAP_CONTENT
:返回父布局剩下的空间+ MeasureSpec.AT_MOST
- MeasureSpec.AT_MOST
childDimension为精确值>0: 返回控件需要的大小 +MeasureSpec.EXACTLY
LayoutParams.MATCH_PARENT
:返回父布局剩下的空间+MeasureSpec.AT_MOST
LayoutParams.WRAP_CONTENT
:返回父布局剩下的空间+MeasureSpec.AT_MOST
- MeasureSpec.UNSPECIFIED
根据sUseZeroUnspecifiedMeasureSpec
决定返回的大小
childDimension为精确值>0: 返回控件需要的大小 +MeasureSpec.EXACTLY
LayoutParams.MATCH_PARENT: 根据sUseZeroUnspecifiedMeasureSpec决定返回的大小 0或者剩余空间大小 + MeasureSpec.UNSPECIFIED
LayoutParams.WRAP_CONTENT: 根据sUseZeroUnspecifiedMeasureSpec决定返回的大小 0或者剩余空间大小 + MeasureSpec.UNSPECIFIED
measure(int widthMeasureSpec, int heightMeasureSpec)
这个是用于父控件轮训调用各个子类 将子类的大小信息传入到子控件中
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()便是我们这篇文章的起点阅读,它是在measure()中调用到的 也就是说measure先与 onMeasure()
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
}
总结:measure(int widthMeasureSpec, int heightMeasureSpec) 这个方法中有调用到的onMeasure()便是我们这篇文章的起点阅读,既然 它是在measure()中调用到的 也就是说measure()先于onMeasure()被调用
网友评论