现象
基本布局和样式
不同设置的情况
属性(只列出和基础布局不同的属性) | 样式 | TextView的onMeasure执行次数 | |
---|---|---|---|
和基础布局一致 | textA和TextB分别只测量1次 | ||
textB -android:layout_weight="1" |
textA测量1次 textB测量2次 |
||
textA - android:layout_weight="1" textB - android:layout_weight="1" |
- textA测量2次 - textB测量2次 |
||
textA - - android:layout_weight="0" textB - - android:layout_weight="0" |
- textA测量1次 - textB测量1次 |
||
textA - - - android:layout_height="0dp" - android:layout_weight="1" textB - android:layout_height="0dp" - android:layout_weight="1" |
- textA测量1次 - textB测量1次 |
||
textA - android:layout_height="100dp" - - android:layout_weight="1" textB - android:layout_height="0dp" - android:layout_weight="1" |
- textA测量2次 - textB测量1次 |
||
Linearlayout -layout_height="wrap_content" textA - android:layout_height="100dp" - - android:layout_weight="1" textB - android:layout_height="0dp" - android:layout_weight="1" |
- textA测量2次 - textB测量2次 |
结论
- LinearLayout的高度为wrap_content,且设置了layout_weight大于1的子view,会测量两次
- LinearLayout的高度为固定值或match_parent,且子view的layout_weight大于1并且layout_height不等于0,会测量两次
LinearLayout垂直布局下的测量流程
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
final int count = getVirtualChildCount();
final int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = View.MeasureSpec.getMode(heightMeasureSpec);
boolean skippedMeasure = false;
// 记录lp.height == 0 && lp.weight > 0 的子view第一次测量后需要的空间
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);
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
// 如果子view的height是0,并且weight>0,则useExcessSpace为true
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == View.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.
// 如果linearLayout的高度是EXACTLY,并且useExcessSpace,则省略一次测量
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.
// 如果如果linearLayout的高度不是EXACTLY,且子view的height是0,并且weight>0
// 此时将子view的height改为WRAP_CONTENT,并在后续进行测量。
// 分析:因为linearLayout的高度不是EXACTLY,因此需要对子view进行测量,否则无法确定自身的高度
lp.height = LinearLayout.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;
// 测量子view
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会在后面根据weight分配给各个子view
consumedExcessSpace += childHeight;
}
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + 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
// 根据所有子view高度,还有自己的高度限制,来决定自己的高度
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.
// 计算还有多少剩余空间,这里需要注意consumedExcessSpace字段
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);
final LinearLayout.LayoutParams lp = (LinearLayout.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 != View.MeasureSpec.EXACTLY) {
childHeight = largestChildHeight;
} else if (lp.height == 0 && (!mAllowInconsistentMeasurement
|| heightMode == View.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.
// 如果view的高度不是0(即固定值或者wrap_content,已经进行过一次测量了)
childHeight = child.getMeasuredHeight() + share;
}
// 根据计算出的子view高度,构造薪的measureSpec并重新测量
final int childHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec(
Math.max(0, childHeight), View.MeasureSpec.EXACTLY);
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
lp.width);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
}
分析一
- LinearLayout: 固定高度200dp
- TextA:不设置layout_weight
-
TextB: android:layout_weight="1"
image.png
- TextA和TextB进行第一测量,并计算获取mTotalLength
- 计算linearLayout的高度heightSize
- 计算remainingExcess,因为linearLayout的高度heightSize是大于mTotalLength的,因此remainingExcess > 0
int remainingExcess = heightSize - mTotalLength
+ (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
4.因为TextB设置了layout_weight,因此根据如下方式计算剩余空间,并将剩余空间和textB第一次测量的高度相加,作为新的高度,并重新进行测量流程。因此TextB一共执行了measuer流程。
// 按照weight权重分配空间
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;
}
// 根据计算出的子view高度,构造新的measureSpec并重新测量
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);
分析二
- LinearLayout: 固定高度200dp
- textA
- android:layout_height="0dp"
- android:layout_weight="1"
- textB
- android:layout_height="0dp"
-
android:layout_weight="1"
image.png
1.TextA和TextB不走第一次测量流程,mTotalLength的值为0
// TextA和TextB的useExcessSpace都是true
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.
// 因为Linearlayout的高度是固定值,即heightMode == MeasureSpec.EXACTLY,因此TextA和TextB均走此处逻辑,不会进行第一次测量
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
}
- 计算linearLayout的高度heightSize
- 计算remainingExcess,因为mTotalLength的值为0(如果Linearlayout设置了padding,则mTotalLength的值需加上上下padding的大小,但不影响分析),因此remainingExcess = linearLayout的高度
- 将剩余空间按照TextA和TextB的weight权重分配大小,并执行measure流程。因此这种情况下TextA和TextB只走一次measure流程。
// 按照weight权重分配空间
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;
}
// 根据计算出的子view高度,构造新的measureSpec并重新测量
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);
分析三
- LinearLayout: 固定高度200dp
- textA
- android:layout_height="100dp"
- android:layout_weight="1"
- textB
- android:layout_height="0dp"
-
android:layout_weight="1"
image.png
1.TextA设置了weight=1,且layout_height>0,因此先进行一次measure
2.TextB满足如下条件,因此,先不进行测量。因此mTotalLength的值为TextA的测量高度
// TextB的useExcessSpace是true
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.
// 因为Linearlayout的高度是固定值,即heightMode == MeasureSpec.EXACTLY,因此TextA和TextB均走此处逻辑,不会进行第一次测量
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
}
计算linearLayout的高度heightSize
- 计算remainingExcess,因为mTotalLength的值为TextA的测量高度,因此remainingExcess = linearLayout的高度 - TextA的测量高度
- 将剩余空间按照TextA和TextB的weight权重分配大小,并分别执行measure流程。至此TextA执行了两次measure,TextB只执行了一次。注意
- TextA因为设置了layout_height > 0,已经执行过一次测量,因此TextA的高度 = 之前测量得到的高度 + 按照权重分配的剩余空间。因此TextA的高度是高于TextB的。
- TextB的高度 = 按照权重分配的剩余空间
// 按照weight权重分配空间
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.
// TextB 走此处逻辑,计算最终的高度
childHeight = share;
} else {
// This child had some intrinsic height to which we
// need to add its share of excess space.
// TextA 走此处逻辑,计算最终的高度
childHeight = child.getMeasuredHeight() + share;
}
// 根据计算出的子view高度,构造新的measureSpec并重新测量
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);
分析四
- linearlayout
- layout_height="wrap_content"
- textA
- android:layout_height="100dp"
- android:layout_weight="1"
- textB
- android:layout_height="0dp"
- android:layout_weight="1"
image.png
1.TextA设置了weight=1,且layout_height>0,因此先进行一次measure
2.TextB按照如下流程进行测量,并设置consumedExcessSpace
// TextB的useExcessSpace=true
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) {
// 因为LinearLayout的高度不是EXACTLY,因此走此处逻辑,
// 注意: 这里设置height = LayoutParams.WRAP_CONTENT,用该参数走后续的测量流程
lp.height = LayoutParams.WRAP_CONTENT;
}
// 执行TextB的测量
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
final int childHeight = child.getMeasuredHeight();
if (useExcessSpace) {
lp.height = 0;
// 设置consumedExcessSpace的值
consumedExcessSpace += childHeight;
}
3.计算LinearLayout的高度,高度heightSize等于TextA和TextB的高度之和,即和mTotalLength相等
// mTotalLength是之前计算的TextA和TextB的高度之和,注意,TextB是按照WRAP_CONTENT的方式测量的
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;
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 {
// LinearLayout的高度为wrap_content,对应MeasureSpec.AT_MOST, 因此走到此处逻辑,
// 即LinearLayout的高度为传入的size
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
result = size;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
- 计算剩余空间remainingExcess,因为第2步执行TextB的测量的时候,consumedExcessSpace被赋值,等于TextB的测量高度,因此remainingExcess > 0
int remainingExcess = heightSize - mTotalLength
+ (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
- 将剩余空间按照TextA和TextB的weight权重分配大小,并分别执行measure流程。至此TextA和TextB分别执行了两次measure流程。
- 因为LinearLayout的高度是wrap_content,需要子view执行完测量后才能确定自己的大小,这是TextB需要执行第一次测量流程的原因。
- TextA因为设置了layout_height > 0,因此TextA的高度 = 之前测量得到的高度 + 按照权重分配的剩余空间。因此TextA的高度是高于TextB的。
- TextB的高度 = 按照权重分配的剩余空间
// 按照weight权重分配空间
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.
// TextB 走此处逻辑,计算最终的高度
childHeight = share;
} else {
// This child had some intrinsic height to which we
// need to add its share of excess space.
// TextA 走此处逻辑,计算最终的高度
childHeight = child.getMeasuredHeight() + share;
}
// 根据计算出的子view高度,构造新的measureSpec并重新测量
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);
问题
设置了layout_weight一定会走2次测量流程吗?
不是的,如下情况会走2次measure:
- LinearLayout的高度为wrap_content,且layout_weight大于1的子view,会测量两次
- LinearLayout的高度为固定值或match_parent,且子view的layout_weight大于1并且layout_height不等于0,会测量两次
如下设置,则只走一次measure:
- LinearLayout的高度为固定值或match_parent,且子view的layout_weight大于1并且layout_height等于0