Measurement process of ViewGroup subclass LinearLayout
In the note "View and ViewGroup's measurement process", it has been mentioned that ViewGroup does not perform a specific measurement process, but only calls the measure() method of child view. This is because there are too many ViewGroup and other methods, which are not easy to handle uniformly. In fact, each of them rewrite onMeasure from its own processing. The following is mainly to analyze the measurement process of LinearLayout, a subclass of ViewGroup.
Start with onMeasure()
LinearLayout.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);//Vertical layout
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);//Horizontal layout
}
}
Here I will only analyze the process of vertical layout measureVertical().
For detailed analysis, see the comments in the code.
LinearLayout.java
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = 0;//Height of child view
int maxWidth = 0;
int childState = 0;
int alternativeMaxWidth = 0;//Maximum width of subview (excluding subview of layout_weight > 0)
int weightedMaxWidth = 0;//Maximum width of the subview (including only the subview of layout_weight > 0)
boolean allFillParent = true;//Whether the width of the subview is all fillParent is used for subsequent judgment on whether recalculation is necessary
float totalWeight = 0; //The sum of weight s of all child view s
//Number of child views (including only direct child views, such as Linear Layout 1)
//There are Textview 1, Linear Layout 2, TextView 2, respectively.
//Linear Layout 2 has multiple sub-view s. At this point, LinearLayout1's getVirtualChildCount(); 3)
final int count = getVirtualChildCount();
//Linear Layout Width Mode
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
//Linear Layout Height Mode
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//The width of the child view is determined by the parent. If the parent LinearLayout is layout_width=wrap_content,
//If the child view is fill_parent, matchWidth =true
boolean matchWidth = false;
boolean skippedMeasure = false;
//baseLine of the number of sub view s in LinearLayout is used as the baseLine of LinearLayout
final int baselineChildIndex = mBaselineAlignedChildIndex;
final boolean useLargestChild = mUseLargestChild;
int largestChildHeight = Integer.MIN_VALUE;
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] + Pass.1, this=" + this);
}
// 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);//By default, all return 0. Extended reservation is useless here.
continue;
}
if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);//By default, all return 0. Extended reservation is useless here.
continue;
}
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] + Child in Pass1: " + i + ", child=" + child
+ ", this=" + this);
}
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
//Layout Params of child
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
// Optimization: don't bother measuring children who are going to use
// leftover space. These views will get measured again down below if
// there is any leftover space.
final int totalLength = mTotalLength;
//If the Linear Layout height is determined. And the height of this child view is equal to 0, weight > 0,
//Then mTotal Length only needs to add margin.
//Because it's weight > 0; the view's specific height will be calculated later.
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Child in Pass1-1: " + i
+ ", child=" + child + ", this=" + this + ", "
+ "status=Skipped, mTotalLength=" + mTotalLength
+ ", totalWeight=" + totalWeight);
}
skippedMeasure = true;
} else {
int oldHeight = Integer.MIN_VALUE;
if (lp.height == 0 && lp.weight > 0) {
// heightMode is either UNSPECIFIED or AT_MOST, and this
// child wanted to stretch to fill available space.
// Translate that to WRAP_CONTENT so that it does not end up
// with a height of 0
oldHeight = 0;
lp.height = LayoutParams.WRAP_CONTENT;
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Child in Pass1-2-1: " + i
+ ", child=" + child + ", this=" + this + ", "
+ "status=Modified, LayoutParams.height change to 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).
measureChildBeforeLayout(
child, i, widthMeasureSpec, 0, heightMeasureSpec,
totalWeight == 0 ? mTotalLength : 0);
if (oldHeight != Integer.MIN_VALUE) {
lp.height = oldHeight;
}
final int childHeight = child.getMeasuredHeight();
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
if (useLargestChild) {//Whether android: measure WithLargestChild is set
largestChildHeight = Math.max(childHeight, largestChildHeight);
}
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Child in Pass1-2-2: " + i
+ ", child=" + child + ", this=" + this + ", "
+ "status=Measured, mTotalLength=" + mTotalLength
+ ", totalWeight=" + totalWeight);
}
}
/**
* 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) {//i cannot be less than android: baseline Aligned ChildIndex with simultaneous 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.
//If the LinearLayout width is not determined, such as wrap_content, and the child view is FILL_PARENT,
//Then mark matchWidth=true; matchWidthLocally = true;
matchWidth = true;
matchWidthLocally = true;
}
final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);//Width of Maximum Subview
childState = combineMeasuredStates(childState, child.getMeasuredState());
//Is the child view width all FILL_PARENT?
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.
*/
//If the parent width is wrap_content and the child fill_parent, the width of the child needs to be determined by the parent. This is not the true width.
weightedMaxWidth = Math.max(weightedMaxWidth,
matchWidthLocally ? margin : measuredWidth);
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
}
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Child in Pass1: " + i + ", child=" + child
+ ", this=" + this + ", " + "matchWidth=" + matchWidth
+ ", matchWidthLocally=" + matchWidthLocally + ", "
+ "weightedMaxWidth=" + weightedMaxWidth
+ ", alternativeMaxWidth=" + alternativeMaxWidth);
}
i += getChildrenSkipCount(child, i);
}
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Pass.1, this=" + this + ", "
+ "maxWidth=" + maxWidth + ", weightedMaxWidth=" + weightedMaxWidth
+ ", alternativeMaxWidth=" + alternativeMaxWidth + ", "
+ "mTotalLength=" + mTotalLength + ", totalWeight=" + totalWeight);
}
if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
mTotalLength += mDividerHeight;
}
if (useLargestChild &&
(heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] + Pass.1.5, this=" + this);
}
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));
}
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Pass.1.5, this=" + this
+ ", largestChildHeight=" + largestChildHeight
+ ", mTotalLength=" + mTotalLength);
}
}
// 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 delta = heightSize - mTotalLength;
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] + Pass.2, this=" + this + ", "
+ "delta=" + delta + ", " + "heightSize=" + heightSize
+ ", mTotalLength=" + mTotalLength);
}
//SkppedMeasure = true: If the height of LinearLayout is determined and there are child view s with height=0, weight > 0,
if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
mTotalLength = 0;
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child.getVisibility() == View.GONE) {
continue;
}
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] + Child in Pass2: " + i + ", "
+ "child=" + child + ", this=" + this);
}
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
float childExtra = lp.weight;
if (childExtra > 0) {
// Child said it could absorb extra space -- give him his share
int share = (int) (childExtra * delta / weightSum);
weightSum -= childExtra;
delta -= share;
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
mPaddingLeft + mPaddingRight +
lp.leftMargin + lp.rightMargin, lp.width);
// TODO: Use a field like lp.isMeasured to figure out if this
// child has been previously measured
if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
// child was measured once already above...
// base new measurement on stored values
int childHeight = child.getMeasuredHeight() + share;
if (childHeight < 0) {
childHeight = 0;
}
child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Child in Pass2-1-1-1: " + i
+ ", child=" + child + ", this=" + this + ", "
+ "status=Measured, share=" + share
+ ", childHeight=" + childHeight
+ ", mTotalLength=" + mTotalLength);
}
} else {
// child was skipped in the loop above.
// Measure for this first time here
child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
MeasureSpec.EXACTLY));
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Child in Pass2-1-1-2: " + i
+ ", child=" + child + ", this=" + this + ", "
+ "status=Measured, share=" + share
+ ", mTotalLength=" + mTotalLength);
}
}
// Child may now not fit in vertical dimension.
childState = combineMeasuredStates(childState, child.getMeasuredState()
& (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
} else {
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Child in Pass2-1-2: " + i
+ ", child=" + child + ", this=" + this + ", "
+ "status=Skipped, ChildExtra=" + childExtra
+ ", mTotalLength=" + mTotalLength);
}
}
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;
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Pass.2-1, this=" + this + ", "
+ "maxWidth=" + maxWidth + ", weightedMaxWidth=" + weightedMaxWidth
+ ", alternativeMaxWidth=" + alternativeMaxWidth + ", "
+ "delta=" + delta + ", mTotalLength=" + mTotalLength);
}
// 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 (sDebugLayout) {
String measureString = (childExtra > 0)
? "status=Measured" : "status=Skipped";
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Child in Pass2-2: " + i
+ ", child=" + child + ", this=" + this + ", " + measureString);
}
}
}
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Pass.2-2, this=" + this + ", "
+ "maxWidth=" + maxWidth + ", weightedMaxWidth=" + weightedMaxWidth
+ ", alternativeMaxWidth=" + alternativeMaxWidth + ", " + "delta=" + delta
+ ", mTotalLength=" + mTotalLength
+ ", useLargestChild=" + useLargestChild);
}
}
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) {
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] + Pass.3, this=" + this
+ ", uniformWidth=" + getMeasuredWidth() + ", this=" + this);
}
forceUniformWidth(count, heightMeasureSpec);
if (sDebugLayout) {
Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Pass.3, this=" + this);
}
}
}
As you can see from the above, when you actually assign the height of each child view of LinearLayout, you first assign the control without setting weight and height=0 attribute and execute the measure ment process of the child view, and then give those child views with weight > 0 and height=0. This is skippedMeasure of 79 lines.
=
true will eventually perform the work in the if judgment of 256.
To verify, here are a few examples.
The following 480dip is 480*3= 1140px on the phone I tested.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/doov_ll_root"
android:layout_width="fill_parent"
android:layout_height="480dip" >
<LinearLayout
android:id="@+id/doov_linear1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#ff888888"
android:orientation="vertical" >
<TextView
android:id="@+id/doov_id1"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="2"
android:background="#ff765423"
android:text="11111" />
<TextView
android:id="@+id/doov_id2"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:background="#ffff0000"
android:text="aaaaa" />
<TextView
android:id="@+id/doov_id3"
android:layout_width="fill_parent"
android:layout_height="90dip"
android:background="#ff234532"
android:text="2222222" />
</LinearLayout>
</LinearLayout>
Let's start with the actual results:
doov_ll_root:1440
doov_linear1:1440
doov_id3:270
doov_id1:(1440-270)=1170 ;1170*2/3=760
doov_id2:380
The calculation process is as follows:
256 lines: delta = 1170, heightSize = 1440, mTotal Length = 270
280 rows - child =0: share = 780, mTotal Length = 0, delta=1170-780=390
280 lines - child =1: share=390, mTotalLength=780,delat=0
child =2 cannot enter 257 lines of judgment, and no other processing is performed.
The actual effect of the above layout is as follows:


In another case:
If the doov_id1 and doov_id2 configurations are changed as follows, that is, the height is changed to match_parent.
At this time, the height ratio of doov_id 1 to doov_id2 is obviously not 2:1, but 1:2. Let's analyze the specific calculation steps.
At 256 lines: delta=-1710, heightSize=1440, mTotal Length=3150 (mTotal Length=1440*2+270)
When 300 rows of child=0: share = - 1140, child Height = 300, mTotal Length = 0
When 300 rows of child=1: share = - 570, child Height = 870, mTotal Length = 300
The height of doov_id1 is 1440+(-1140)=300.
The height of doov_id2 is 1440+(-570).
)=870
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/doov_ll_root"
android:layout_width="fill_parent"
android:layout_height="480dip" >
<LinearLayout
android:id="@+id/doov_linear1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#ff888888"
android:orientation="vertical" >
<TextView
android:id="@+id/doov_id1"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:layout_weight="2"
android:background="#ff765423"
android:text="11111" />
<TextView
android:id="@+id/doov_id2"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#ffff0000"
android:text="aaaaa" />
<TextView
android:id="@+id/doov_id3"
android:layout_width="fill_parent"
android:layout_height="90dip"
android:background="#ff234532"
android:text="2222222" />
</LinearLayout>
</LinearLayout>
The actual display effect of the above layout file is as follows:


Summary: When using Linear Layout, if the weight attribute of child view is set, it is better to set the height (or width) attribute of view to 0dp, so as to avoid the actual displaying of the wrong proportion, and set the weight of two child views to 1 in time. Although the proportion may be 1:1 when displaying, it will actually measure more than once (specifically reflected in lines 63 and 102). So setting the height (or width) attribute of the child view to 0dp not only enables the view to display correctly, but also provides code efficiency.