preface

Android development we usually contact most is a variety of View, View is a relatively large system, including drawing process, event distribution, a variety of animation, custom View and so on. A few days ago I wrote an article on event distribution source code parsing, and today we’ll explore the somewhat esoteric measurement process of the drawing process.

Basic knowledge preparation

Related classes and methods involved in measurement:

  • The measurement specification consists of two parts: SpecMode(measurement mode) and SpecSize(corresponding measurement mode specification size). It is represented by a 32-bit int value, with the higher two bits representing SpecMode and the lower 30 bits SpecSize
  • Measure (): a measure entry method that calls the onMeasure method
  • OnMeasure (): exposes the developer to custom measure rules. If not overwritten, the View’s onMeasure method will be used by default

Now let’s talk about the onMeasure method of View

View onMeasure source code parsing

To make it easier for you to understand, here are three common pieces of code:

①View width and height are wrap_content


      
<FrameLayout 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:padding="16dp">

   <View
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_gravity="center"
       android:background="@color/red" />

</FrameLayout>
Copy the code

The width and height of the View are match_parent


      
<FrameLayout 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:padding="16dp">

   <View
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:layout_gravity="center"
       android:background="@color/red" />

</FrameLayout>
Copy the code

③ The width and height of the View are 100dp


      
<FrameLayout 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:padding="16dp">

   <View
       android:layout_width="100dp"
       android:layout_height="100dp"
       android:layout_gravity="center"
       android:background="@color/red" />

</FrameLayout>
Copy the code

The above three pieces of code look like this:

Scenes ① and ② :

Scenario 3:

A brief description of the situation above:

  • When the width and height of the view are match_parent and wrap_content, the display size is the same as the parent view
  • When the width and height of the view is set to a fixed value (XXDP), the display size is the fixed value we set

If you have no doubt about the above results and already know why, you can skip the rest. Just like it and go…

And so on. Just kidding. How can you take it seriously. Hahaha.. Nonsense not to say, go, we point into the source Chou Chou.

View.onMeasure

//View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {.../ / 1onMeasure(widthMeasureSpec, heightMeasureSpec); . }Copy the code

WidthMeasureSpec and heightMeasureSpec call widthMeasureSpec and heightMeasureSpec. Now let’s look at the View’s onMeasure method:

//View.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    / / 1
    setMeasuredDimension(
        getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),/ / 4
    	getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)/ / 4
    );
}

/ / 2
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {... setMeasuredDimensionRaw(measuredWidth, measuredHeight); }/ / 3
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    mMeasuredWidth = measuredWidth;
    mMeasuredHeight = measuredHeight;

    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

// Return background(Drawable) minWidth, mMinWidth(android_minWitdh) larger values
protected int getSuggestedMinimumWidth(a) {
    return (mBackground == null)? mMinWidth : max(mMinWidth,mBackground.getMinimumWidth()); }// Return background(Drawable) minHeight, mMinHeight(android_minHeight) larger values
protected int getSuggestedMinimumHeight(a) {
    return (mBackground == null)? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); }/ / 4
public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
        / / 5
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        / / 6
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
    }
    return result;
}

Copy the code

The above code is a little bit more, but the methods are relatively simple, let’s introduce them one by one:

  • Note ①②③ onMeasure–>setMeasuredDimension–>setMeasuredDimension –>setMeasuredDimensionRaw If both mMeasuredWidth and mMeasuredHeight are assigned, we can obtain the measuredWidth () and measuredHeight () values
  • Method ④getDefaultSize is also called at method ②setMeasuredDimension
  • methods(4) getDefaultSizeThe general logic is:
    • Return specSize if the value of measureSpec.AT_MOST or measureSpec.EXACTLY is passed as measureSpec.
    • The value of measureSpec.Mode is measureSpec.UNSPECIFIED Returns the size (getSuggestedMinimumWidth (), getSuggestedMinimumHeight ()).

Now the question is: where do the onMeasure parameters come from

So let’s take a look at FrameLayout related measurement source code

FrameLayout onMeasure source code parsing

Before we talk about the onMeasure of FrameLayout, let’s default to the fact that the FrameLayout is the root View of the Activity, and it’s added to the DecorView, which is actually a FramLayout, The following source code explains the origin of DecorView’s MeasureSpec:

//ViewRootImpl.java

private void performTraversals(a) {...// Get the DecorView's MeasureSpec
	int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    // Measure the entryperformMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ./ / layout entranceperformLayout(lp, mWidth, mHeight); ./ / the draw entranceperformDraw(); . }private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {
		
        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            // The default DecorView measureSpec is :size=windowSize mode is EXACTLY
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension,MeasureSpec.EXACTLY);
            break;
    }
    return measureSpec;
}

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    if (mView == null) {
        return; }.../ / mView is DecorViewmView.measure(childWidthMeasureSpec, childHeightMeasureSpec); . }Copy the code

=windowSize (1920px 1080px) mode=EXACTLY, FrameLayout measureSpec and DecorView measureSpec match each other because the Activity root’s width and height are both match_parent. FrameLayout onMeasure FrameLayout onMeasure

//FrameLayout.java

// Here widthMeasureSpec is the size of the phone screen
//heightMeasureSpec size is the screen height - status bar height mode is EXACTLY
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {...int maxHeight = 0;
    int maxWidth = 0;
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if(mMeasureAllChildren || child.getVisibility() ! = GONE) {/ / 1
         	measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            finalLayoutParams lp = (LayoutParams) child.getLayoutParams(); maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); . }}.../ / 2
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));
}

//ViewGroup.java
/ / 3
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, 
        int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

    / / 4
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
    / / 5
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

    / / 6
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    
}

/ / 7
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    / / measureSpec mode
    int specMode = MeasureSpec.getMode(spec);
    / / the size of measureSpec
    int specSize = MeasureSpec.getSize(spec);

    int size = Math.max(0, specSize - padding);

    int resultSize = 0;
    int resultMode = 0;

    switch (specMode) {
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {// The width and height of child are fixed values such as 40dp
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child's lp.width is match_parent
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child's lp.width is wrap_content
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {// The width and height of child are fixed values such as 40dp
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child's match_parent is exactly the same as wrap_content's MeasureSpec
                / / mode are AT_MOST
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
               // The width and height of child are fixed values such as 40dp
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child's match_parent is exactly the same as wrap_content's MeasureSpec
                / / mode are AT_MOST
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
    }
    
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

//View.java

/ / 8
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState){
    final int specMode = MeasureSpec.getMode(measureSpec);
    final int specSize = MeasureSpec.getSize(measureSpec);
    final int result;
    switch (specMode) {
        case MeasureSpec.AT_MOST:// corresponds to wrap_content
            // If the size of the child view is greater than the size of measureSpec returns a specSize that cannot be greater than the size passed in by the parent class
            if (specSize < size) {
                result = specSize | MEASURED_STATE_TOO_SMALL;
            } else {// If the size of the child view is smaller than specSize, return the size of the child view
                result = size;
            }
            break;
        //EXACTLY mode (usually corresponding to fixed or match_parent) returns measureSpec's specSize
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        case MeasureSpec.UNSPECIFIED:// Return the size passed in without setting a specific measurement mode
        default:
            result = size;
    }
    return result | (childMeasuredState & MEASURED_STATE_MASK);
}

Copy the code

The above source code is a bit much, take your time, we will introduce it one by one:

  • MeasureChildWithMargins () {measureChildWithMargins ();}

  • Method ③measureChildWithMargins () calls ⑦getChildMeasureSpec

  • Method ⑦getChildMeasureSpec

    • = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = Ht), childDimension has three values: fixed XXDP match_parent wrap_content
    • Method ⑦getChildMeasureSpec returns the child’s measureSpec as follows:

  • Note (6) called child.measure into the view measurement process we talked about earlier, to this article we mentioned three scenarios can be explained:

    1. The view is match_parent or wrap_content, FrameLayout calculates the child view’s MeasureSpec (size=specSize) Mode =EXATLY or AT_MOST), but the value of EXACTLY and AT_MOST is the same when the measurement process goes into the sub-view.
    2. When the value of a child view is fixed, FrameLayout calculates the child view’s MeasureSpec (size=childDimension mode=EXATLY)
  • After all the child views are measured, we calculate and save the maxWidth and maxHeight, and then go to the setMeasuredDimension method at comment ②, Measure the width and height of the FramLayout by measuring the width and height of the FramLayout using the measure 8 resolveSizeAndState method and childMaxSize. ResolveSizeAndState: resolveSizeAndState: resolveSizeAndState: resolveSizeAndState

Through the above process analysis, we have basically combed the measurement process again, and its flow chart is as follows:

conclusion

  1. If the View/ViewGroup does not overwrite the onMeasure, match_parent and wrap_content display the same effect, both taking the size of parentMeasureSpec. So if we want to express the effect of WRAP_content, we need to rewrite the onMeasure method to define our own measurement rules, such as: We can set the width and height of the TextView to the rectangle size of the text in AT_MOST mode if the wrap_content of the TextView needs to be in effect. Sets the width and height of the Image to the original size of the Image

  2. Currently, the child view cannot be larger than the parent view. As UNSPECIFIED, the child View /ViewGroup is still UNSPECIFIED. The recommended reading class is NestedScrollView

  3. The setMeasuredDimension method is an endpoint of a View measurement that assigns mMeasureWidth and mMeasureHeight

  4. If a custom ViewGroup inherits from a ViewGroup, if the onMeasure is not overridden, the subview does not measure at all. MeasureChildren () can be used to measure subviews or to define measurement rules

expand

Here is a question for you, you need to explore and experience the measurement process, but also welcome to comment in the comment area:

Scenario 1.


      
<ScrollView 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">

   <View
       android:layout_width="360dp"
       android:layout_height="400dp"
       android:layout_gravity="center"
       android:background="@color/red" />

</ScrollView>
Copy the code

Scenario 2.


      
<ScrollView 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">

   <LinearLayout
       android:layout_width="wrap_content"
       android:layout_height="wrap_content">
      
      <View
          android:layout_width="360dp"
          android:layout_height="400dp"
          android:layout_gravity="center"
          android:background="@color/red" />
      
   </LinearLayout>
   
</ScrollView>
Copy the code

Whether the display effect of the two scenes is as you think, if not, fucking the source code and ask why! If you understand this problem and this article, you can try to write a simple LinearLayout or FlowLayout on your own, and feel the sense of accomplishment that the code you wrote is consistent with your expected results haha. Finally, I would like to recommend my excellent Android highlight library, which is linked as follows: github.com/hyy920109/H… , welcome to use and star!

subsequent

Look at the comments everyone and I have no communication, I sent an article specifically to introduce the phenomenon and reasons of the above problems, I believe you will have a harvest after reading. Gamerespec.UNSPECIFIED Uses of the Android source code