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()被調用