Understand the number of linearlayout#on measure calls to child from the perspective of source code

Posted by QSDragon on Fri, 04 Mar 2022 15:36:02 +0100

Those who are familiar with the drawing process know that ViewGroup can decide the drawing time and call times of child.

Today we will start from LinearLayout Start learning and see how many times it calls onMeasure for the child View.

For simplicity, we choose the time to enter the Activity in the previous blog When entering Activity, why is View#onMeasure called twice in the page layout? As mentioned, when entering the page, we will go through the drawing process at least twice. We need to observe the execution times of child's onMeasure in each drawing process.

Observation of phenomena by log

Timing: enter the page;

android:orientation="vertical"

xml layout: the custom views inside are only added with log s.

demo:LinearLayoutTestActivity

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".measure.LinearLayoutTestActivity">

    <com.tinytongtong.androidstudy.measure.view.CustomLinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal"
        tools:context=".measure.LinearLayoutTestActivity">

        <com.tinytongtong.androidstudy.measure.view.CustomSingleView
            android:layout_width="20dp"
            android:layout_height="100dp"
            android:background="@color/colorPrimaryDark" />

        <com.tinytongtong.androidstudy.measure.view.CustomTextView
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:background="#99dddddd"
            android:gravity="center"
            android:text="match_parent" />

        <com.tinytongtong.androidstudy.measure.view.CustomButton
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_weight="2"
            android:text="wrap_content" />

        <com.tinytongtong.androidstudy.measure.view.CustomImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="3"
            android:background="@drawable/ic_launcher"
            android:contentDescription="wrap_content" />

    </com.tinytongtong.androidstudy.measure.view.CustomLinearLayout>

</LinearLayout>

Here LinearLayout Four child ren are added and different width and height are set respectively. Then with LinearLayout The width and height of are variables, and match is set respectively_ Parent and wrap_content, let's observe the corresponding execution times of onMeasure.

Realistic effect

There are four combinations of width and height. The specific effects are as follows:

widehighoneselfView1 (fixed width and height, no weight)view2(w:match_parent,h:0,weight:1)view3(w:match_parent,h:wrap_content,weight:2)view4(w:wrap_content,h:wrap_content,weight:3)remarks
match_parentmatch_parentM:2,L:1,D:0 (don't participate in onDraw by default)M:2,L:1,D:1 (only participate in the first onMeasure)M: 2, l: 1, D: 1 (do not participate in the first onMeasure, participate in the second onMeasure)M: 4, l: 1, D: 2 (participate in the first and second onMeasure, participate in the first and second onDraw)M: 4, l: 1, D: 1 (participate in the first and second onMeasure)When height is 0 and weight > 0, you will not participate in the first onMeasure. When weight > 0, it will participate in the second onMeasure.
match_parentwrap_contentM:2,L:1,D:0M:2,L:1,D:1 (only participate in the first onMeasure)M: 4, l: 1, D: 1 (participate in the first and second onMeasure)M: 4, l: 1, D: 2 (participate in the first and second onMeasure, participate in the first and second onDraw)M: 4, l: 1, D: 1 (participate in the first and second onMeasure)Those with weight will participate in the second onMeasure.
wrap_contentmatch_parentM:2,L:1,D:0M:2,L:1,D:1 (only participate in the first onMeasure)M: 4, l: 1, D: 1 (do not participate in the first onMeasure, participate in the second and third onMeasure)M: 6, l: 1, D: 2 (participate in the first, second and third onMeasure, and participate in the first and second onDraw)M: 4, l: 1, D: 1 (participate in the first and second onMeasure)
wrap_contentwrap_contentM:2,L:1,D:0M:2,L:1,D:1 (only participate in the first onMeasure)M: 6, l: 1, D: 1 (participate in the first, second and third onMeasure)M: 6, l: 1, D: 2 (participate in the first, second and third onMeasure, and participate in the first and second onDraw)M: 4, l: 1, D: 1 (participate in the first and second onMeasure)

explain:
M: onMeasure;L: onLayout;D: onDraw.
M: 2. L: 1, D: 1 indicates that onMeasure is called twice, onLayout is called once, and onDraw is called once.

We know that the onMeasure method will be used at least twice when entering the Activity. For details, please see When entering Activity, why is View#onMeasure called twice in the page layout?.

Looking at the contents of the table, we found that in a measurement process, LinearLayout The child is measured at least once and at most three times. weight and size will have an impact.

LinearLayout#measureVertical source code analysis

What is the specific situation? Let's look at the source code:

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    // See how tall everyone is. Also remember max width.
    // For the first measurement, the height of LinearLayout is fixed (measurespec. Actual). Meanwhile, the height of children is 0 and the weight of children is greater than 0. These children do not participate in the first measurement, and all other children participate in the measurement.
    for (int i = 0; i < count; ++i) {
        final View child = getVirtualChildAt(i);
        ...

        final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
        if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
            ...
        } else {
            ...
            measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                    heightMeasureSpec, usedHeight);
            ...
            // Focus width
            if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
                ...
                matchWidth = true;
                ...
            }
            ...
        }
        ...
    }
    ...

    // The second measurement is for the weighted child. The View skipped in the first measurement will be measured here.
    if (skippedMeasure
            || ((sRemeasureWeightedChildren || remainingExcess != 0) && totalWeight > 0.0f)) {
        ...
        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
            ...
            if (childWeight > 0) {
                ...
                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);
                ...
            }
            ...
        }
        ...
    } else {
        ...
    }
    ...
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            heightSizeAndState);
    
    // The third measurement focuses on the measurement of width. If widthmode= MeasureSpec. Actually, and the width of child is match_parent,
    // Then execute the forceUniformWidth method, open the for loop, and set the width to match_ Re measure the parent's view.
    if (matchWidth) {
        forceUniformWidth(count, heightMeasureSpec);
    }
}

LinearLayout#measureVertical summary:

When android:orientation="vertical", LinearLayout The child of will undergo at least one measurement and at most three measurements.

1,The first measurement, LinearLayout Fixed height(MeasureSpec.EXACTLY),meanwhile child The height of the is 0 and child If the weight of is greater than 0, these child Do not participate in the first measurement, the rest child All participate in the measurement.
2,Second measurement, for weighted child Make a measurement and skip the first measurement View,It will be measured here.       
3,Third measurement, focus width Measurement of. If widthMode != MeasureSpec.EXACTLY,And there child The width of the is match_parent,Then execute forceUniformWidth Method, on for Loop, the width will be match_parent of view again measure Again.

Interested students can look at the log data in the table and try to correspond to the scene in the source code. I won't repeat it here.

LinearLayout#measureHorizontal

Next, let's analyze the android:orientation="vertical" scenario. The layout file will not be pasted. Directly look at the corresponding log table:

widehighoneselfView1 (fixed width and height, no weight)view2(w:0,h:match_parent,weight:1)view3(w:wrap_content,h:match_parent,weight:2)view4(w:wrap_content,h:wrap_content,weight:3)remarks
match_parentmatch_parentM:2,L:1,D:0 (don't participate in onDraw by default)M:2,L:1,D:1 (only participate in the first onMeasure)M: 4, l: 1, D: 1 (participate in the first and second onMeasure)M: 4, l: 1, D: 2 (participate in the first and second onMeasure, participate in the first and second onDraw)M: Measure 1, measure 4, l: 2
match_parentwrap_contentM:2,L:1,D:0M:2,L:1,D:1M: 6, l: 1, D: 1 (participate in the first, second and third onMeasure)M: 6, l: 1, D: 2 (participate in the first, second and third onMeasure, and participate in the first and second onDraw)M: 4, l: 1, D: 1 (participate in the first and second onMeasure)
wrap_contentmatch_parentM:2,L:1,D:0M:2,L:1,D:1M: 4, l: 1, D: 1 (participate in the first and second onMeasure)M: 4, l: 1, D: 2 (participate in the first and second onMeasure, participate in the first and second onDraw)M: 4, l: 1, D: 1 (participate in the first and second onMeasure)
wrap_contentwrap_contentM:2,L:1,D:0M:2,L:1,D:1M: 6, l: 1, D: 1 (participate in the first, second and third onMeasure)M: 6, l: 1, D: 2 (participate in the first, second and third onMeasure, and participate in the first and second onDraw)M: 4, l: 1, D: 1 (participate in the first and second onMeasure)

The situation is similar to that in the horizontal direction. In a measurement process, the child also has at least one measurement and at most three measurements.

LinearLayout#measureHorizontal source code analysis

See the LinearLayout#measureHorizontal source code for details:

void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    // See how wide everyone is. Also remember max height.
    // For the first measurement, child ren who meet the following conditions at the same time will not participate in the first measurement, otherwise they will participate in the first measurement.
    // ① The width of LinearLayout is fixed (measurespec. Actual);
    // ② The width of child is 0 and the weight of child is greater than 0;
    // ③ child sets the baselineAligned property.
    for (int i = 0; i < count; ++i) {
        final View child = getVirtualChildAt(i);
        ...
        if (widthMode == MeasureSpec.EXACTLY && useExcessSpace) {
            ...
            if (baselineAligned) {
                ...
                child.measure(freeWidthSpec, freeHeightSpec);
            } else {
                skippedMeasure = true;
            }
        } else {
            ...
            measureChildBeforeLayout(child, i, widthMeasureSpec, usedWidth,
                    heightMeasureSpec, 0);

            final int childWidth = child.getMeasuredWidth();
            if (useExcessSpace) {
                // Restore the original width and record how much space
                // we've allocated to excess-only children so that we can
                // match the behavior of EXACTLY measurement.
                lp.width = 0;
                usedExcessSpace += childWidth;
            }
            ...
            // Attention height
            if (useLargestChild) {
                largestChildWidth = Math.max(childWidth, largestChildWidth);
            }
        }
        ...
    }
    ...
    // The second measurement is for the weighted child. The View skipped in the first measurement will be measured here.
    if (skippedMeasure
            || ((sRemeasureWeightedChildren || remainingExcess != 0) && totalWeight > 0.0f)) {
        ...
        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
            ...
            if (childWeight > 0) {
                ...
                final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                        Math.max(0, childWidth), MeasureSpec.EXACTLY);
                final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                        mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin,
                        lp.height);
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
                ...
            }
            ...
        }
        ...
    } else {
        ...
    }
    ...
    setMeasuredDimension(widthSizeAndState | (childState&MEASURED_STATE_MASK),
            resolveSizeAndState(maxHeight, heightMeasureSpec,
                    (childState<<MEASURED_HEIGHT_STATE_SHIFT)));
    // The third measurement focuses on the measurement of height. If highmode= MeasureSpec. Actually, and the height of child is match_parent,
    // Then the forceUniformHeight method starts the for loop and sets the height to match_ Re measure the parent's view.
    if (matchHeight) {
        forceUniformHeight(count, widthMeasureSpec);
    }
}

LinearLayout#measureVertical summary:

When android:orientation="vertical", LinearLayout The child of will undergo at least one measurement and at most three measurements.

1,The first measurement shall meet the following conditions at the same time child Do not participate in the first measurement, otherwise participate in the first measurement.
①LinearLayout Fixed width(MeasureSpec.EXACTLY);
②child The width of the is 0 and child The weight of is greater than 0;
③child Set baselineAligned Properties.
2,Second measurement, for weighted child Make a measurement and skip the first measurement View,It will be measured here.   
3,Third measurement, focus height Measurement of. If heightMode != MeasureSpec.EXACTLY,And there child What is the height of the match_parent,be forceUniformHeight Method, on for Cycle, the height will be match_parent of view again measure Again.

summary

In general, in a measurement process, LinearLayout At least one measurement (required) and at most three measurements shall be made for the child of.
The first measurement is basically for all children (see the above analysis for special cases), the second measurement is for weighted children, and the third measurement is for the other direction and the size is match_ child of parent.

Relevant information

LinearLayout
demo:LinearLayoutTestActivity

Topics: Android