preface

By learning the source code of Android official Layout, you can help yourself to better understand the Android UI framework system, understand the internal convenient encapsulation good API call, help to Layout optimization and custom view implementation and other work. Here the learning results through writing blog summary, easy to remember, not forgotten in the future.

The source code in this blog is based on Android 8.1

LinearLayout characteristics

LinearLayout is one of the most commonly used layouts in Android development. It supports horizontal or vertical linear layouts, and supports child to set the weight weight, so that the child can fill the LinearLayout in proportion to the main axis.

The source code to explore

The layout properties

LinearLayout constructor = LinearLayout constructor = LinearLayout constructor = LinearLayout constructor

public LinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);

    final TypedArray a = context.obtainStyledAttributes(
            attrs, com.android.internal.R.styleable.LinearLayout, defStyleAttr, defStyleRes);

    // HORIZONTAL or VERTICAL (0: HORIZONTAL, 1: VERTICAL)
    int index = a.getInt(com.android.internal.R.styleable.LinearLayout_orientation, -1);
    if (index >= 0) {
        setOrientation(index);
    }

    // The alignment of child
    index = a.getInt(com.android.internal.R.styleable.LinearLayout_gravity, -1);
    if (index >= 0) {
        setGravity(index);
    }

    // All text children are aligned with the literal baseline
    boolean baselineAligned = a.getBoolean(R.styleable.LinearLayout_baselineAligned, true);
    if(! baselineAligned) { setBaselineAligned(baselineAligned); }// Total weight (if not set, calculate the sum of the weight of child)
    mWeightSum = a.getFloat(R.styleable.LinearLayout_weightSum, -1.0 f);

    // Specify alignment with the literal baseline of a child as the base line
    mBaselineAlignedChildIndex =
            a.getInt(com.android.internal.R.styleable.LinearLayout_baselineAlignedChildIndex, -1);

    // For all children with weights and no exact size, change their size to match the size of the largest child
    mUseLargestChild = a.getBoolean(R.styleable.LinearLayout_measureWithLargestChild, false);

    // Display the dividing line
    mShowDividers = a.getInt(R.styleable.LinearLayout_showDividers, SHOW_DIVIDER_NONE);
    // The distance between the two ends of the dividing line
    mDividerPadding = a.getDimensionPixelSize(R.styleable.LinearLayout_dividerPadding, 0);
    // Save the Drawable and width of the split line
    setDividerDrawable(a.getDrawable(R.styleable.LinearLayout_divider));

    final int version = context.getApplicationInfo().targetSdkVersion;
    mAllowInconsistentMeasurement = version <= Build.VERSION_CODES.M;

    a.recycle();
}
Copy the code

Attribute Description:

  • Orientation the horizontal or vertical arrangement of children in a LinearLayout.

  • Gravity Child alignment in the LinearLayout.

  • BaselineAligned child is aligned with text baseline. Ex. :

  • WeightSum, if not set, the sum of the weights of each child will be calculated. Ex. :

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:weightSum="1"
    android:orientation="vertical"
    android:background="#dcdcdc">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="0.6"
        android:background="#3399cc"/>
</LinearLayout>
Copy the code

  • BaselineAlignedChildIndex baseline as a benchmark to one child text alignment. Ex. :

    Root layout for horizontal LinearLayout, purple, green, and blue blocks of three vertical LinearLayout, respectively set up baselineAlignedChildIndex attribute value is 2, 0, 1, means that the purple block to index 2 is the third child baseline as a baseline, The blue block is aligned against the first child and the green block against the second child.

  • MeasureWithLargestChild Consolidates the size of all children with weights and exact sizes to the size of the largest child. Ex. :

  • The showDividers display the dividing line. Support setting beginning(the dividing line precedes the first child), end(the dividing line follows the last child), middle(the dividing line between each child), and None (default, no dividing line is displayed).

  • DividerPadding Sets the distance between the two ends of the dividing line if the dividing line is displayed.

  • Divider Sets a pattern of dividing lines. Ex. :

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:divider="@drawable/shape_line"
    android:dividerPadding="30dp"
    android:showDividers="middle"
    android:orientation="vertical" >

    <View
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:background="#e8eaf6"/>

    <View
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:background="#e0f2f1"/>

    <View
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:background="#e8f5e9"/>
</LinearLayout>
Copy the code

LayoutParams

LinearLayout defines static inner class LayoutParams inherited from MarginLayoutParams, which defines two members weight and gravity:

public float weight;
public int gravity = -1;
Copy the code

Child is therefore supported to set weights and alignments.

OnMeasure measurement

The LinearLayout will make multiple measurements during the measurement process according to the child’s LayoutParams. The measurement process is long. Here, the measurement is divided into predictive and supplementary measurements.

Start measuring

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // Perform different measurement methods according to the alignment direction.
    if (mOrientation == VERTICAL) {
        // Align vertically
        measureVertical(widthMeasureSpec, heightMeasureSpec);
    } else {
        // horizontal arrangementmeasureHorizontal(widthMeasureSpec, heightMeasureSpec); }}Copy the code

The logic in the measureVertical and measureHorizontal methods is similar here using vertical alignment as an example.

Preparation of initial parameters

If we enter the measureVertical method, we first initialize some variables to assist the measurement calculation:

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
    // Total height of content (total measured height of all children + Divider height + margin)
    mTotalLength = 0;
    // Maximum width (maximum child width + margin)
    int maxWidth = 0;
    MEASURED_STATE_TOO_SMALL = MEASURED_STATE_TOO_SMALL = MEASURED_STATE_TOO_SMALL = MEASURED_STATE_TOO_SMALL
    int childState = 0;
    // Alternate maximum width (record the maximum width of unweighted child)
    int alternativeMaxWidth = 0;
    // Record the maximum width of the child with the weight
    int weightedMaxWidth = 0;
    // Flag whether all child layoutparams.width is MATCH_PARENT
    boolean allFillParent = true;
    // Calculate the sum of the weights of child
    float totalWeight = 0;

    // Number of children (call getChildCount directly within this method, subclasses can override this method)
    final int count = getVirtualChildCount();

    // Retrieve the measurement specification mode passed in by the parent layout.
    final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

    // Measure the child whose layoutparams.width is MATCH_PARENT again after the LinearLayout width is determined.
    boolean matchWidth = false;
    // mark whether a child is not measured temporarily.
    boolean skippedMeasure = false;

    / / baselineAlignedChildIndex attribute values, the default is 1.
    final int baselineChildIndex = mBaselineAlignedChildIndex;
    MeasureWithLargestChild value, false by default.
    final boolean useLargestChild = mUseLargestChild;

    // The height of the maximum child, useful when useLargestChild is true.
    int largestChildHeight = Integer.MIN_VALUE;
    // Record the total height of the child whose layoutparams. height is 0 pixels and whose weight is greater than 0
    int consumedExcessSpace = 0;

    // Record the number of valid children in the first round of traversal.
    int nonSkippedChildCount = 0;
    
    // omit the rest
    / /...
}
Copy the code

Predictive quantity stage

After preparing the argument variables, we can start traversing the subview:

void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) {
    // omit the above part
    / /...
    
    // See how tall everyone is. Also remember max width.
    // Walk through the measurement subview and compare the width of the widest subview.
    for (int i = 0; i < count; ++i) {
        // Return the child view based on the index (call getChildAt directly within the method, subclasses can re-extend the method, so it may return NULL).
        final View child = getVirtualChildAt(i);
        if (child == null) {
            // Skip empty child, measureNullChild returns 0.
            mTotalLength += measureNullChild(i);
            continue;
        }

        if (child.getVisibility() == View.GONE) {
           // Skip the GONE child, getChildrenSkipCount returns 0, and subclasses can override it to skip subsequent children.
           i += getChildrenSkipCount(child, i);
           continue;
        }

        // After the non-null and non-gone checks are completed, the count is incremented by one. This count is used for subsequent judgements by the Divider
        nonSkippedChildCount++;
        // First check whether the divider height is calculated
        // hasDividerBeforeChildAt Based on the current index and the value of the showDividers property
        // Check whether the current location is divided by the divider (possibly beginning and middle).
        if (hasDividerBeforeChildAt(i)) {
            // If there are dividers, you need to increase the height of the divider.
            mTotalLength += mDividerHeight;
        }

        final LayoutParams lp = (LayoutParams) child.getLayoutParams();

        // Add the total weight of child
        totalWeight += lp.weight;

        // Mark whether the child uses the remaining space (when the child is set to 0 pixel height and the weight is greater than 0, the LinearLayout allocates space to other children first,
        // Then use the remaining space to assign weights.
        final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
        if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
            // If the height mode is EXACTLY, the LinearLayout's height is fixed
            // Set the exact pixel size. Or MATCH_PARENT, but the height of the parent LinearLayout is also fixed.
            // At the same time, the child is marked to use the remaining space, then the measured height of the child is not measured and obtained, only the margin is calculated.
            
            // 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.
          
            // The reason why we can optimize this is because the LinearLayout layoutparams. height is clear and not WRAP_CONTENT,
            // Calculate the height of the LinearLayout without knowing the height of the child's content. And child's layoutparams. height is 0 pixels,
            // Fully depends on the remaining space of the LinearLayout to allocate the weight. If the remaining space is 0, the Child will not be displayed.
          
            final int totalLength = mTotalLength;
            // The margin is negative.
            mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
            // Mark skippedMeasure for subsequent judgment and supplementary measurement.
            skippedMeasure = true;
        } else {
            // Routine measurement process
            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.
                // Entering this condition, the height mode can only be UNSPECIFIED or AT_MOST, and the child is tagged with the remaining space.
                // Change layoutparams. height temporarily to WRAP_CONTENT before the child is measured, so that the child can measure the height of its content, and then change it back to 0 after the measurement is complete.
                lp.height = 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).
            If no child has been set to a weight greater than 0, set to the total content height, otherwise set to 0.
            final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
            // Call the child measure for the first time.
            measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                    heightMeasureSpec, usedHeight);

            // Get the height measured by child.
            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.
                // Restore layoutparams. height to 0.
                lp.height = 0;
                // Record the height consumed for subsequent calculation of the remaining space. (Why is consumption height recorded separately here? Because when I add up the total height of the content,
                // The height of the child will also be added, but the child actually needs to wait for the remaining space to allocate the height, so this value will be added in the subsequent calculation)
                consumedExcessSpace += childHeight;
            }

            final int totalLength = mTotalLength;
            // Add the total content height.
            mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                   lp.bottomMargin + getNextLocationOffset(child));

            // If measureWithLargestChild is set, record the maximum child height. (This command is not executed by default and can be ignored)
            if(useLargestChild) { largestChildHeight = Math.max(childHeight, largestChildHeight); }}// Omit the logical part of the baselineChildIndex attribute. It is not executed by default and does not affect the main measurement process.
        / /...
      
        // Call child to end the measurement, and here is the logic to handle the width.
        // Is used to mark whether the measured width of the child is valid.
        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 child is MATCH_PARENT, it depends on the width of the parent layout. So the markers matchWidth, matchWidthLocally are true,
            // The child measure will be called again later.
            matchWidth = true;
            matchWidthLocally = true;
        }

        // Calculate the margin of the child
        final int margin = lp.leftMargin + lp.rightMargin;
        // Measure width of child plus margin
        final int measuredWidth = child.getMeasuredWidth() + margin;
        // Record the maximum child width
        maxWidth = Math.max(maxWidth, measuredWidth);
        // Merge the measurement state of child
        childState = combineMeasuredStates(childState, child.getMeasuredState());

        // Record whether all child widths are MATCH_PARENT
        allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
        // Record different maximum widths for different weights.
        if (lp.weight > 0) {
            /* * Widths of weighted Views are bogus if we end up * remeasuring, so keep them separate. */
          	// If the measurement width marked above is invalid, then only margin is used for comparison.
            weightedMaxWidth = Math.max(weightedMaxWidth,
                    matchWidthLocally ? margin : measuredWidth);
        } else {
            // If the measurement width marked above is invalid, then only margin is used for comparison.
            alternativeMaxWidth = Math.max(alternativeMaxWidth,
                    matchWidthLocally ? margin : measuredWidth);
        }

        GetChildrenSkipCount returns 0 directly in the getChildrenSkipCount method, which subclasses can override to skip subsequent children.
        i += getChildrenSkipCount(child, i);
    }
  
    // omit the rest
    / /...
}
Copy the code

This part of the logic mainly iterates over the child measurements, recording both the total height and the maximum child width. For the child with the weight set, the traversal measurement does not really allocate space according to the weight, and for the child that meets the characteristic conditions, the measurement method of child is not called temporarily.

Supplementary measurement stage

Next, supplementary measurement will be made to the sub-view according to the measurement of the first round:

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
    // omit the first pass measurement part
    / /...
    // Omit the divider part (use the hasDividerBeforeChildAt method to check whether there is a divider in the end position. If so, add dividerHeight to the total height of the content)
    / /...
    // Omit the measureWithLargestChild attribute section (recalculate the total height of the content, and replace the measured height of each child with the maximum child height recorded in the previous section)
    / /...

    // The total height of the content plus the upper and lower inner margins of the LinearLayout itself.
    // Add in our padding
    mTotalLength += mPaddingTop + mPaddingBottom;

    int heightSize = mTotalLength;

    // Ensure that the height is not less than the minimum height.
    // Check against our minimum height
    heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

    // Reconcile our calculated size with the heightMeasureSpec
    // Adjust the height according to the specification mode.
    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.
    / / calculate the remaining space (mAllowInconsistentMeasurement on above M version to false)
    // Note: remainingExcess may be negative because heightSize may be much smaller than mTotalLength after the resolveSizeAndState adjustment.
    int remainingExcess = heightSize - mTotalLength
            + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
    if(skippedMeasure || remainingExcess ! =0 && totalWeight > 0.0 f) {
        // Space needs to be allocated according to weights
        WeightSum (if the weightSum attribute is set, otherwise the child weightSum is calculated)
        float remainingWeightSum = mWeightSum > 0.0 f ? mWeightSum : totalWeight;

        // Clear the total content height
        mTotalLength = 0;

        // Walk through the subview a second time
        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;
            // Calculate the weight allocation space for child whose weight is greater than 0.
            if (childWeight > 0) {
                // Calculate the allocated height from the remaining space according to the weight ratio (may be negative, so may occur a heavy weight child, the actual height is small).
                final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
                // Calculate the remaining height and total remaining weight
                remainingExcess -= share;
                remainingWeightSum -= childWeight;

                // Calculate the height of the child. This value is later used to generate the height gauge of the child.
                final int childHeight;
                if(mUseLargestChild && heightMode ! = MeasureSpec.EXACTLY) {// Use the maximum child height, which defaults to false and can be ignored.
                    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.
                    // If the height of Child is 0 pixels, and the LinearLayout itself determines the height, the child height is the height allocated according to the weight.
                    childHeight = share;
                } else {
                    // This child had some intrinsic height to which we
                    // need to add its share of excess space.
                    // Child height is the measured height of the child plus the height assigned by the weight (so the child is not exactly proportional to the weight).
                    childHeight = child.getMeasuredHeight() + share;
                }

                // Use childHeight to generate a height measurement directly, specifying a specific height and ensuring that the height is not negative.
                final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                        Math.max(0, childHeight), MeasureSpec.EXACTLY);
                // Use the getChildMeasureSpec method to generate the width measurement specification according to the system default rules.
                final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                        mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
                        lp.width);
                // Call the child measure. Now the height is clear and clear.
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

                // Child may now not fit in vertical dimension.
                // Combine child to measure state.
                childState = combineMeasuredStates(childState, child.getMeasuredState()
                        & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
            }

            // Record the maximum child width
            final int margin =  lp.leftMargin + lp.rightMargin;
            final int measuredWidth = child.getMeasuredWidth() + margin;
            maxWidth = Math.max(maxWidth, measuredWidth);

            // mark the measurement width of child as useful
            booleanmatchWidthLocally = widthMode ! = MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT;// Record the maximum alternate width
            alternativeMaxWidth = Math.max(alternativeMaxWidth,
                    matchWidthLocally ? margin : measuredWidth);

            // record whether all child's are MATCH_PARENT
            allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;

            // Total content height
            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 {
        // Compare the maximum width
        alternativeMaxWidth = Math.max(alternativeMaxWidth,
                                       weightedMaxWidth);

        // Omit measureWithLargestChild attribute section (measure child View at maximum child height)
        / /...
    }
    
    // Omit the final part of the measurement
    / /...
}
Copy the code

This part mainly carries out supplementary measurement of allocation space for weight. If there is an unmeasured child in the prediction stage, or the total weight of child is greater than 0 and there is remaining space, the measurement will be carried out.

As can be seen from the source, the height used for weight allocation is calculated from the total content height determined by subtracting the height of the LinearLayout itself in the prediction phase. The height of each child is not strictly the same as the weight ratio, but the height of the child plus the weight height. When the total height of the Child exceeds the height of the LinearLayout, the weight height will be negative, so the child with larger weight will be smaller.

Final stage of measurement

The LinearLayout size of the LinearLayout itself is set as follows:

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
    // omit the previous measurement part
    / /...

    if(! allFillParent && widthMode ! = MeasureSpec.EXACTLY) {// If not all children are MATCH_PARENT and the LinearLayout width is undefined
        maxWidth = alternativeMaxWidth;
    }

    // Maximum width plus inside margin
    maxWidth += mPaddingLeft + mPaddingRight;

    // Check against our minimum width
    // Make sure the width is not less than the minimum width
    maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

    // Set the LinearLayout size itself
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            heightSizeAndState);

    if (matchWidth) {
        // If the width marked with child depends on the width of the LinearLayout in the previous measurement phase, then the measurement needs to be done again at the end.forceUniformWidth(count, heightMeasureSpec); }}Copy the code

This part is to set the size of the LinearLayout itself, but in the end, we need to deal with the child whose layoutparams. width is MATCH_PARENT in the previous stage. Because the LinearLayout is dependent on the width, we can’t accurately measure the width.

private void forceUniformWidth(int count, int heightMeasureSpec) {
    // Pretend that the linear layout has an exact size.
    // Get the width of the LinearLayout itself to generate the width specification.
    int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(),
            MeasureSpec.EXACTLY);
    // Iterate over the subview
    for (int i = 0; i< count; ++i) {
       final View child = getVirtualChildAt(i);
       if(child ! =null&& child.getVisibility() ! = GONE) { LinearLayout.LayoutParams lp = ((LinearLayout.LayoutParams)child.getLayoutParams());// Measure the child of MATCH_PARENT
           if (lp.width == LayoutParams.MATCH_PARENT) {
               // Temporarily force children to reuse their old measured height
               // FIXME: this may not be right for something like wrapping text?
               int oldHeight = lp.height;
               // Temporarily change child's layoutparams. height to the measured height of the child, because the current measured height of the child has been completed.
               lp.height = child.getMeasuredHeight();

               // Remeasue with new dimensions
               // Call the child measure
               measureChildWithMargins(child, uniformMeasureSpec, 0, heightMeasureSpec, 0); lp.height = oldHeight; }}}}Copy the code

OnLayout layout

Layout also performs different methods according to the direction of arrangement:

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    if (mOrientation == VERTICAL) {
        layoutVertical(l, t, r, b);
    } else{ layoutHorizontal(l, t, r, b); }}Copy the code

The layout logic of layoutVertical and layoutVertical is similar. Here we take vertical arrangement as an example:

void layoutVertical(int left, int top, int right, int bottom) {
    final int paddingLeft = mPaddingLeft;

    int childTop;
    int childLeft;

    // Where right end of child should go
    final int width = right - left;
    int childRight = width - mPaddingRight;

    // Space available for child
    // child The available width
    int childSpace = width - paddingLeft - mPaddingRight;

    final int count = getVirtualChildCount();

    // Vertical alignment
    final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
    // Horizontal alignment
    final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;

    // Offset the upper boundary according to the vertical alignment.
    switch (majorGravity) {
       case Gravity.BOTTOM:
           // mTotalLength contains the padding already
           // At the bottom, the upper boundary needs to be offset downward. The offset distance is the height of the LinearLayout minus the height of the empty area.
           childTop = mPaddingTop + bottom - top - mTotalLength;
           break;

           // mTotalLength contains the padding already
       case Gravity.CENTER_VERTICAL:
           // Center vertically, with the upper boundary offset downward half the height of the blank area.
           childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
           break;

       case Gravity.TOP:
       default:
           // The default is near the top
           childTop = mPaddingTop;
           break;
    }

    // Iterate over the child view, calling child.layout once
    for (int i = 0; i < count; i++) {
        final View child = getVirtualChildAt(i);
        if (child == null) {
            childTop += measureNullChild(i);
        } else if(child.getVisibility() ! = GONE) {final int childWidth = child.getMeasuredWidth();
            final int childHeight = child.getMeasuredHeight();

            final LinearLayout.LayoutParams lp =
                    (LinearLayout.LayoutParams) child.getLayoutParams();

            // Get the alignment of LayoutParams set by child
            int gravity = lp.gravity;
            if (gravity < 0) {
                // If not, use the horizontal alignment of the LinearLayout.
                gravity = minorGravity;
            }
            // Get the content layout direction (RTL or LTR)
            final int layoutDirection = getLayoutDirection();
            // Convert relative alignment (convert START and END to LEFT and RIGHT)
            final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
            // Handle horizontal alignment
            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                case Gravity.CENTER_HORIZONTAL:
                    childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                            + lp.leftMargin - lp.rightMargin;
                    break;

                case Gravity.RIGHT:
                    childLeft = childRight - childWidth - lp.rightMargin;
                    break;

                case Gravity.LEFT:
                default:
                    childLeft = paddingLeft + lp.leftMargin;
                    break;
            }

            // Check whether there are dividers in the index position. If there are dividers, the upper border needs to be offset by DividerHeight.
            if (hasDividerBeforeChildAt(i)) {
                childTop += mDividerHeight;
            }

            childTop += lp.topMargin;
            // setChildFrame calls child's Layout directly.
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                    childWidth, childHeight);
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
            The getChildrenSkipCount method returns 0i += getChildrenSkipCount(child, i); }}}Copy the code

The layout method of the LinearLayout is simpler. The boundaries are offset according to the alignment. And arrange the child linearly, continuously shifting the upper boundary downward.

Ontouch draw

@Override
protected void onDraw(Canvas canvas) {
    // If no divider is set, return directly
    if (mDivider == null) {
        return;
    }

    if (mOrientation == VERTICAL) {
        drawDividersVertical(canvas);
    } else{ drawDividersHorizontal(canvas); }}Copy the code

See the source code, LinearLayout drawing only for the partition line. The drawing is also divided into different directions, taking the vertical direction as an example:

void drawDividersVertical(Canvas canvas) {
    final int count = getVirtualChildCount();
    // Iterate over the subview
    for (int i = 0; i < count; i++) {
        final View child = getVirtualChildAt(i);
        if(child ! =null&& child.getVisibility() ! = GONE) {// Check whether there are dividers in the index position
            if (hasDividerBeforeChildAt(i)) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                final int top = child.getTop() - lp.topMargin - mDividerHeight;
                // Set the Bounds to the Divider and draw on the canvas.drawHorizontalDivider(canvas, top); }}}// After iterating through the sub-view, determine whether there is a divider with the end position.
    if (hasDividerBeforeChildAt(count)) {
        final View child = getLastNonGoneChild();
        int bottom = 0;
        if (child == null) {
            bottom = getHeight() - getPaddingBottom() - mDividerHeight;
        } else {
            finalLayoutParams lp = (LayoutParams) child.getLayoutParams(); bottom = child.getBottom() + lp.bottomMargin; } drawHorizontalDivider(canvas, bottom); }}Copy the code

conclusion

The core and complex logic of LinearLayout is mainly in the measurement process. Because of the weight, the child may be measured several times. (The vertical direction is summarized here, and the horizontal direction is similar logically)

In the case that child does not set the weight, the LinearLayout only needs to follow the conventional measurement process of ViewGroup, call child measurement successively, calculate the height and width of the content, and finally set its own size according to the measurement specifications.

With the weight child, things get complicated. Firstly, a prediction quantity is carried out, during which the content height is calculated. The remaining height is calculated after subtracting the total height of the content from the height of the measurement specification of the LinearLayout. The remaining height is then allocated according to the weight ratio, and the height of the child is added to this part of the height. Finally, set the LinearLayout size itself.

Notice that the LinearLayout has no weight child. If during the measurement, the layoutparams. width of child is MATCH_PARENT, and the width measurement specification of LinearLayout is not EXACTLY. Means that the child needs to depend on the width of the parent layout, but the parent layout’s width is not yet known. So after the LinearLayout has set its size, the child will be called to measure.