LinearLayout source code details

Points of concern

1: The linearLayout is only measured once, but it will be measured twice after setting the weight

Source code analysis

Generally all control class source, will start from measure, layout and draw3 methods, check their callback function onMeasure, onLayout and onDraw as long as understand these three processes, LinearLayout, as a subclass of ViewGroup, mainly appears as a layout container, so we need to focus on the onMeasure method,

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mOrientation == VERTICAL) { measureVertical(widthMeasureSpec, heightMeasureSpec); } else { measureHorizontal(widthMeasureSpec, heightMeasureSpec); }}Copy the code

In onMeasure, longitudinal or horizontal measurements are carried out according to the value of orientation. The logic of longitudinal and horizontal measurements is familiar, so we only need to select one of them for analysis. Here, we only analyze the longitudinal measurements

MTotalLength = 0; mTotalLength = 0; // The maximum width of the childView is used to calculate the width of the LinearLayout 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;Copy the code

I initialized a bunch of constants, so I’ll just focus on the ones I commented out

    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;
        }
        
Copy the code

First is to take out the child control, determine whether the control is null, if it is empty or Visibility is gone, the next control directly, here can also see the difference between gone and invisible, if there is a partition line, and then add the height of the partition line

// Sometimes we load a layout in code using an Inflater service, and then set its LayoutParams. If we don't reference the parent's LayoutParams, we will report a strong error. Final LayoutParams lp = (LayoutParams) child-getLayOutParams (); final LayoutParams lp = (LayoutParams) child-getLayoutParams (); WeightSum totalWeight += lp.weight; // * UNSPECIFIED: The parent control has no constraints on the child control, which is available only in ScrollView's sliding layout. // * Exactly: The parent control has strong constraints on the child control, and the child control is always within the boundaries of the parent control. // * AT_Most: when the child control is wrap_content, the measurement value is AT_Most. Final Boolean useExcessSpace = lp.height == 0 && lp.weight > 0; If (heightMode == MeasureSpec.EXACTLY && useExcessSpace) { // * The height of the LinearLayout is match_parent(or has a specific value) // the height of the child is 0 // the weight of the child is >0 We'll give it a flag bit and we'll deal with it later. // Optimization can also improve performance by setting attributes in this case, except that the child controls need not be measured. 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. // Optimize: Don't bother measuring children who only use extra space. If we have spatial distribution, these views will be measured later. final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin); skippedMeasure = true; } else {if (useExcessSpace) {// The LinearLayout is wrAP_content, The mode UNSPECIFIED // Set the height of the current child control to Wrap_content. The reason for this is that the size of the parent wrap_content is controlled by the child control. The heightMode is either UNSPECIFIED or AT_MOST, as The default value of wrap_content prevents The child control from having a height of 0. and // this child is only laid out using excess space. Measure // using WRAP_CONTENT so that we can find out the view's We'll restore the original height of 0 // After measurement. // The heightMode is UNSPECIFIED or AT_MOST, ChildView only uses free space // Measures with WRAP_CONTENT to find the best height for the view. 0 lp.height = layoutparams.wrap_content; } // If the current LinearLayout is not EXACTLY mode, and the subview weight is greater than 0, the full available height of the current LinearLayout will be used for the subview measurement first. In getChildMeasureSpec(), the child controls need to measure the parent control's padding, its margin, and an adjustable size. So if the weight is always 0 before measuring a child control, then the control needs to consider the total height before the control when measuring, to allocate its size according to the rest of the control. If there is weight, the already occupied control is ignored, because with weight, the height of the child control will be reassigned later. // 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). If this or the previous child gives a weight, then we allow it to use all the available space //(we will narrow the content down later if necessary). 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. So that we can accurately match the measured behavior. lp.height = 0; consumedExcessSpace += childHeight; } final int totalLength = mTotalLength; // getNextLocationOffset always returns 0, MTotalLength = math.max (totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); // If measureWithLargestChild is set to true, If (useLargestChild) {largestChildHeight = math. Max (childHeight, largestChildHeight); }}Copy the code

UseLargestChild can through XML attributes android: measureWithLargestChild set, means all weighted attribute View will use maximum View of minimal size

/ / useLargestChild attribute specifies / / so according to the height of largestChildHeight recalculate the 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)); }}Copy the code

Recalculate mTotalLength using largestChildHeight. As you can also see in the code, this property only works with wrap_content

There are some other calculations in the middle of the second measurement, so I’m not going to look at them all, but I’m going to look at the second measurement

// Either expand children with weight to take up available space or // shrink them if they extend beyond our current To be deflected or skipped over. To be deflected or skipped. Bounds. If we skipped on any children, we need to measure them now. // If there is still space available, expand childView and calculate its size // If childView is outside the LinearLayout boundary, The contraction childView int remainingExcess = heightSize - mTotalLength + (mAllowInconsistentMeasurement? 0 : consumedExcessSpace); if (skippedMeasure || ((sRemeasureWeightedChildren || remainingExcess ! WeightSum = weightSum; weightSum = weightSum; weightSum = weightSum; Weightingweightsum = 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; // This is the case with weight set, RemainingExcess height * (childView weight/remainingWeightSum) Weightsum 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)) {/ / if the current mode of LinearLayout is EXACTLY // If the subview is not EXACTLY measured, it needs to be measured once. 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 intrinsically  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); 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); }}Copy the code

For example,

In terms of weight calculation, we can clearly see why weight is allocated according to the principle of the remaining space. For example, suppose our LinearLayout weightSum=10, total height 100, and two child controls (their height=0dp), each with a weight of 2:8.

Then when measuring the first child control, the available remaining height is 100, the height of the first child control is 100* (2/10) =20, and the remaining height available is 80

We continue our measurement of the second control, at this point its height is essentially 80* (8/8) =80

So far, this seems to be true, but we have a problem with weight: ** is when we give the child control height=0dp and height=match_parent we find that the height ratio of our child control is different, the former is 2:8 and the latter is reversed to 8: 2 * *

For that, let’s move on to the code.

Next we’ll see a branch like this:

if ((lp.height ! = 0) || (heightMode ! = MeasureSpec.EXACTLY)) { } else {}

First of all, we ignore the heightMode, which is the measurement mode of the parent class, and the remaining criterion is lp.height, which is the height of the child class.

MeasureChildBeforeLayout () Means that the child must have been measured before. In fact, the measureChildBeforeLayout() method was described earlier.

Remember our measureChildBeforeLayout() implementation of the antecedent condition

YA, just u see, it is not meet (LinearLayout measurement model not EXACTLY/child. The height = = 0 / child. The weight/child. The weight > 0) of the child. The height = = 0

Because unless we specify height=0, match_parent is -1 and wrap_content is -2.

When measureChildBeforeLayout() is executed, the available space is essentially the entire LinearLayout, because the height of our child =match_parent. The mTotalLength is the size of the entire LinearLayout

Returning to our example, suppose our LinearLayout height is 100 and both children are match_parent, then after measureChildBeforeLayout(), the heights of both of our child controls will look like this:

child_1.height=100

child_2.height=100

mTotalLength=100+100=200

After a series of for, execute to our remaining space:

int delta = heightSize – mTotalLength;

(delta=100[Actual linearLayout height]-200=-100)

Yes, you are looking at a negative number.

The following formula applies to weight:

share=(int) (childExtra * delta / weightSum)

That is, share (2/10) = = – 100-20; *

And then we go into what we call if/else

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)); }Copy the code

We know that child.getMeasuredheight ()=100

And then there’s an int childHeight = child.getMeasuredHeight() + share;

This means that our childHeight=100+(-20)=80;

The next step is to go to child.measure and pass childHeight in, so that when it finally returns to the interface, we can see that the weight ratio is reversed in the two child controls of match_parent.

summary

At the end of the article, we summarize the differences and principles of the algorithm for measurement in different cases:

The parent control is match_parent (or the exact value) and the child control has weight and height is set to 0:

The height ratio of the child control will be the same as the layout_weight that we assigned, because the weight secondary measurement went else branch, passing in the calculated share value and the parent is match_parent (or exact value), the child control has the weight, But the height is given as match_parent (or the exact value) :

The height ratio of the child control will be the opposite of the layout_weight that we assigned, because the child control measured the height of the child control once before, and the child control measured the height of the parent control, which is a negative value when calculating the remaining space, and is smaller when adding its own measured height. The parent control is wrap_content, Child controls have weights:

The height of the child control will be forcibly set to its wrAP_content value and measured in wrAP_content mode. The parent control is WRap_content and the child control has no weight:

The height of the child controls is the same as that of the other viewGroups