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

Posted by zako234 on Fri, 04 Mar 2022 23:12:36 +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 RelativeLayout 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;

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

demo:RelativeLayoutTestActivity

<?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"
    tools:context=".measure.RelativeLayoutTestActivity">

    <com.tinytongtong.androidstudy.measure.view.CustomRelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <com.tinytongtong.androidstudy.measure.view.CustomSingleView
            android:id="@+id/view1"
            android:layout_width="200dp"
            android:layout_height="100dp"
            android:layout_alignParentRight="true"
            android:layout_alignParentBottom="true"
            android:background="@color/colorPrimaryDark" />

        <com.tinytongtong.androidstudy.measure.view.CustomTextView
            android:id="@+id/view2"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#99dddddd"
            android:text="match_parent" />

        <com.tinytongtong.androidstudy.measure.view.CustomButton
            android:id="@+id/view3"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignBaseline="@id/view5"
            android:text="wrap_content" />

        <com.tinytongtong.androidstudy.measure.view.CustomImageView
            android:id="@+id/view4"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_toLeftOf="@id/view5"
            android:background="@drawable/ic_launcher"
            android:contentDescription="wrap_content" />

        <com.tinytongtong.androidstudy.measure.view.CustomHelloView
            android:id="@+id/view5"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_centerInParent="true"
            android:background="@color/background_debug" />

        <com.tinytongtong.androidstudy.measure.view.CustomWorldView
            android:id="@+id/view6"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_below="@id/view5"
            android:layout_centerHorizontal="true"
            android:background="@color/background_info"
            android:gravity="center" />

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

</LinearLayout>

Here RelativeLayout Four child ren are added and different width and height are set respectively. Then with RelativeLayout 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, layout_alignParentRight, layout_alignParentBottom)view2(w:match_parent,h:match_parent)view3(w:match_parent,h:wrap_content,android:layout_alignBaseline="@id/view5")view4(w:wrap_content,h:match_parent,android:layout_toLeftOf="@id/view5")View5 (fixed width and height, android:layout_centerInParent = "true")View6 (fixed width and height, android:layout_below="@id/view5", android:layout_centerHorizontal = "true")Note: the first drawing process is mSortedHorizontalChildren. The order of children is 6, 5, 4, 3, 2 and 1; The second drawing process is mSortedVerticalChildren, in the order of 5, 3, 6, 4, 2 and 1;
match_parentmatch_parentM:2,L:1,D:0 (don't participate in onDraw by default)M:4,L:1,D:1 (participate in the first and second 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)M:4,L:1,D:1 (participate in the first and second onMeasure)M: 4, l: 1, D: 1 (participate in the first and second onMeasure)
match_parentwrap_contentM:2,L:1,D:0dittodittodittodittodittoditto
wrap_contentmatch_parentM:2,L:1,D:0dittodittodittodittodittoditto
wrap_contentwrap_contentM:2,L:1,D:0dittodittodittodittodittoditto

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, RelativeLayout The child of is measured at least once and at most twice. The dependency will affect the number of measurements.

Relativelayout #onmeasure (int widthmeasurespec, int hightmeasurespec) source code analysis

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

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // Prepare data. Traverse all children and add the children without horizontal dependency to mSortedHorizontalChildren;
    // Add a child that does not contain a vertical dependency to mSortedVerticalChildren.
    if (mDirtyHierarchy) {
        mDirtyHierarchy = false;
        sortChildren();
    }

    ...
    // For the first measurement, measure all child ren without horizontal dependence
    View[] views = mSortedHorizontalChildren;
    int count = views.length;
    for (int i = 0; i < count; i++) {
        View child = views[i];
        if (child.getVisibility() != GONE) {
            ...
            measureChildHorizontal(child, params, myWidth, myHeight);
            ...
        }
    }
    // The second measurement measures all child ren without vertical dependency
    views = mSortedVerticalChildren;
    count = views.length;
    ...
    for (int i = 0; i < count; i++) {
        final View child = views[i];
        if (child.getVisibility() != GONE) {
            ...
            measureChild(child, params, myWidth, myHeight);
            ...
        }
    }
    ...
    setMeasuredDimension(width, height);
}

As shown above, the source code will measure the child twice.
1. Data will be prepared before measurement, and children will be classified according to different dependencies. Add children that do not contain horizontal dependencies to mSortedHorizontalChildren; Add a child that does not contain a vertical dependency to mSortedVerticalChildren.
2. For the first measurement, measure all child ren without horizontal dependence.
3. The second measurement measures all child ren without vertical dependency.

Explain that the horizontal dependency here refers to rules_ Some dependencies defined in vertical refer to rules_ Some dependencies defined in horizontal.

private static final int[] RULES_VERTICAL = {
        ABOVE, BELOW, ALIGN_BASELINE, ALIGN_TOP, ALIGN_BOTTOM
};

private static final int[] RULES_HORIZONTAL = {
        LEFT_OF, RIGHT_OF, ALIGN_LEFT, ALIGN_RIGHT, START_OF, END_OF, ALIGN_START, ALIGN_END
};

It should be noted that mSortedHorizontalChildren adds a child that does not contain horizontal dependencies, and mSortedVerticalChildren adds a child that does not contain vertical dependencies. If a child has neither horizontal dependency nor vertical dependency, it will be added to mSortedHorizontalChildren and mSortedVerticalChildren respectively and will be measured twice.

It is often said that if LinearLayout is compared with RelativeLayout, it is recommended to use LinearLayout because LinearLayout has less measurement times. Through the previous analysis, we can know that by default, if a child does not set any additional attributes, it will only be measured once in LinearLayout and twice in RelativeLayout. This is the answer to this question. You can talk about the principle next time.

Summary:

To sum up, in RelativeLayout In a measurement process, RelativeLayout Your child will undergo at least one measurement (required) and at most two.

If the corresponding dependency is not set, the child probability will be measured twice.

Relevant information

RelativeLayout
demo:RelativeLayoutTestActivity

Topics: Android