preface

Learning with questions can make us more purposeful and organized in the process of learning. For example, in the process of reading source code, if we start from the beginning to read, often numerous threads, do not grasp the essentials. If you start reading with a few questions, it gives you a main thread as you read the source code. Finally, you can judge whether your problem has been solved or not, to judge whether you really understand, otherwise in the face of thousands of lines of code, it is easy to have the feeling of white read. Before reading this article, here are a few questions

1. When does the View draw? 2. What happens after setContentView? 3. How to measure the size of the View? What does MeasureSpec do? How? MeasureSpec contains UNSPECIFIED. The MeasureSpec contains UNSPECIFIED. 6. How to customize FlowLayout

When does the View draw

In an Android application, the View is first drawn when the Activity starts. When the Activity lifecycle reaches onCreate, we all know that the setContentView method is called at this point. This is where the drawing of the View starts.

In addition, when the View in the View tree changes, the drawing of the View starts; Or actively call the View’s drawing method, such as the invalidate method. This is going to initiate the drawing of the View.

What happens after setContentView?

Start loading the View by calling the setContentView method in the Activity. This process is loaded through the Window object. We can find this method in PhoneWindow. You can see an installDecor method in this method. The purpose of this method is to initialize a top-level View, called a DecorView. And then the workflow of the View starts with the DecorView, and I’ll talk about that later.

SetContentView just initializes the top level View, it doesn’t start drawing the View yet. Moving on, the ActivityThread continues to execute the Activity lifecycle. When the ActivityThread executes into the handleResumeActivity method, the Activity lifecycle function onResume is called. The DecorView is then added via Windows Manager and the View workflow begins. This explains why the user can’t interact with the App until the Activity has finished executing onResume.

Finally, we create an instance of ViewRootImpl. ViewRootImpl is used to communicate the View and WindowManager, implement the protocols needed by both, and manage the View workflow. In the performTraversals method in the ViewRootImpl, you can see that three methods are executed. They are performMeasure, performLayout and performDraw. From the name, we can also think of the three methods to implement the measure, layout and draw methods of View

What happens after you setConteView

How does a View measure its size?

The function of measure is to determine the size of View. The whole View tree is made up of views and viewgroups. Measure is also divided into two drawing methods. The View measure only tests its own size. In addition to measuring its own size, the ViewGroup is responsible for measuring the size of its subviews.

The role of the MeasureSpec

MeasureSpec encapsulates the size parameters of the View, including the width and height of the View and the measurement mode. Its high 2 bits represent the measurement mode (calculated by mode & MODE_MASK) and its low 30 bits represent the size. There are a total of three measurement modes.

  • UNSPECIFIED: The UNSPECIFIED mode does not limit the size of the child View.
  • AT_MOST: Maximum mode corresponds to the wrAP_CONTENT attribute. The parent container has determined the size of the child View, and the child View cannot be larger than this value.
  • The exact mode corresponds to the match_parent property and the exact value. The child View can reach the size specified by the parent container.

For each View, there is a MeasureSpec property that holds the size specification information for the View. In View measurement, use makeMeasureSpec to save the width and height information, use getMode to get the measurement mode, and use getSize to get the width or height.

How is MeasureSpec generated

MeasureSpec is equivalent to a specification in the View measurement process. You need to instruct the MeasureSpec in which way the View should measure before the View starts measuring. MeasureSpec generation is determined by the parent layout and, for top-level ViewDecorViews, by LayoutParams. At the beginning of the analysis of the View workflow above, before starting the workflow in ViewRootImpl, there is a method measureHierarchy(), which is how the DecorView is generated.

private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
        final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {...if(baseSize ! =0&& desiredWindowWidth > baseSize) { childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); }... }Copy the code

You can see in the code that you get the DecorView’s MeasureSpec through the getRootMeasureSpec() method.

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

GetRootMeasureSpec () is also not complicated, as you can see from the method that if it is layoutParams.match_parent, the size of the DecorView is the size of the Window; If it is layoutparams.wrap_content, the size of the DecorView is uncertain. For normal views, MeasureSpec is generated from the parent layout (ViewGroup).

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

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

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
Copy the code

Here you can see that the MeasureSpec that generates the child View is related to the MeasureSpec and padding of the parent layout, as well as to the margin of the View itself.

MeasureSpec contains UNSPECIFIED

The parent View has an UNSPECIFIED width and height of the child View, such as ScrollView 1. It is transmitted to the child View 2 in the Measure method of the ScrollView. The subview receives UNSPECIFIED, and it determines its height based on its actual content size. The difference between the subview and AT_MOST is that it has no maximum size limit, as well as UNSPECIFIED, which makes it useful in ScrollView Settings. As the ScrllView does not need to limit the size of the child View, it can be rolled, as shown in the detail: Measure UNSPECIFIED

How do I customize a FlowLayout

1. The size of the custom control, that is, how much to set the width and height respectively; 2. If it is a ViewGroup, how to properly arrange the placement of its internal child views. 3. How to draw UI elements to the interface according to the corresponding attributes;

The above three problems, in turn, be resolved in the following three methods: onMeasure, onLayout, ontouch

OnMeasure method of FlowLayout

Because the custom control is a container, the onMeasure method is a little more complicated. Before a ViewGroup can measure its own width and height, it needs to determine the size of its inner subviews before determining its own size. As follows:

// Measure the width and height of the control
 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     // Obtain the measurement mode and measured value of width and height
     int widthMode = MeasureSpec.getMode(widthMeasureSpec);
     int widthSize = MeasureSpec.getSize(widthMeasureSpec);
     int heightSize = MeasureSpec.getSize(heightMeasureSpec);
     int heightMode = MeasureSpec.getMode(heightMeasureSpec);

     // Get the number of neutron views in the container
     int childCount = getChildCount();
     // Record the total width of each row of views
     int totalLineWidth = 0;
     // Record the height of the highest View in each row
     int perLineMaxHeight = 0;
     // Record the total height of the ViewGroup
     int totalHeight = 0;
     for (int i = 0; i < childCount; i++) {
         View childView = getChildAt(i);
         // Measure the subview
         measureChild(childView, widthMeasureSpec, heightMeasureSpec);
         MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
         // Get the measured width of the subview
         int childWidth = childView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
         // Get the measured height of the subview
         int childHeight = childView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
         if (totalLineWidth + childWidth > widthSize) {
             // Count the total height
             totalHeight += perLineMaxHeight;
             // Open a new line
             totalLineWidth = childWidth;
             perLineMaxHeight = childHeight;
         } else {
             // Record the total width of each row
             totalLineWidth += childWidth;
             // Compare the highest View in each row
             perLineMaxHeight = Math.max(perLineMaxHeight, childHeight);
         }
         // when the View is the last View, add the maximum height of the row to the totalHeight
         if (i == childCount - 1) { totalHeight += perLineMaxHeight; }}// If the height measurement mode is EXACTLY, use the measured value, otherwise use the calculated total height (in this case, set the height to wrap_content)
     heightSize = heightMode == MeasureSpec.EXACTLY ? heightSize : totalHeight;
     setMeasuredDimension(widthSize, heightSize);
 }
Copy the code

The purpose of the onMeasure method is to recursively measure the child View by calling the measureChild method. 2. Calculate the final height totalHeight of the final FlowLayout by stacking the height of each row.

The onLayout method of FlowLayout

The onMeasure method in the FlowLayout above only calculates the final display width and height of the ViewGroup, but does not specify where a child View should be displayed. To define the rules for displaying child views within a ViewGroup, you need to copy and implement the onLayout method. OnLayout is an abstract method, which means that each custom ViewGroup must actively implement how to arrange child views. This means that each child View must be traversed and child.(l, t, r, b) is called to set the layout position for each child View. The four parameters represent the coordinates of the upper left and lower right respectively. A simple FlowLayout is implemented as follows:

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    mAllViews.clear();
    mPerLineMaxHeight.clear();
    // Store the child views for each row
    List<View> lineViews = new ArrayList<>();
    // Record the total width of views stored in each row
    int totalLineWidth = 0;
    // Record the height of the highest View in each row
    int lineMaxHeight = 0;
    / * * * * iterate through all the View, the View is added to the List < List < View > > the collection * * * * * * * * * * /
    // Get the total number of subviews
    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
        View childView = getChildAt(i);
        MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
        int childWidth = childView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
        int childHeight = childView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
        if (totalLineWidth + childWidth > getWidth()) {
            mAllViews.add(lineViews);
            mPerLineMaxHeight.add(lineMaxHeight);
            // Open a new line
            totalLineWidth = 0;
            lineMaxHeight = 0;
            lineViews = new ArrayList<>();
        }
        totalLineWidth += childWidth;
        lineViews.add(childView);
        lineMaxHeight = Math.max(lineMaxHeight, childHeight);
    }
    // Process the last row separately
    mAllViews.add(lineViews);
    mPerLineMaxHeight.add(lineMaxHeight);
    /************ traverses all views in the collection and displays ************/
    // represents the distance to the left of a View from its parent container
    int mLeft = 0;
    // represents the distance between the View and the top of the parent container
    int mTop = 0;
    for (int i = 0; i < mAllViews.size(); i++) {
        // Get all views for each row
        lineViews = mAllViews.get(i);
        lineMaxHeight = mPerLineMaxHeight.get(i);
        for (int j = 0; j < lineViews.size(); j++) {
            View childView = lineViews.get(j);
            MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
            int leftChild = mLeft + lp.leftMargin;
            int topChild = mTop + lp.topMargin;
            intRightChild = leftChild + childView. GetMeasuredWidth ();int bottomChild = topChild + childView.getMeasuredHeight();
            // The four arguments represent the upper left and lower right corners of the View
            childView.layout(leftChild, topChild, rightChild, bottomChild);
            mLeft += lp.leftMargin + childView.getMeasuredWidth() + lp.rightMargin;
        }
        mLeft = 0; mTop += lineMaxHeight; }}Copy the code

The complete code is visible: custom FlowLayout code

A didi interview question

I met such a question in the interview with Didi before. If you understand this question, I believe you have fully mastered the measure process of custom View, Activity, inner root layout, and so on. The background color is red, and the width and height are WRAP_content. The inner View background color is blue, and the width and height are also WRAP_content

<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@color/red"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <View
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/blue"
    />
</LinearLayout>
Copy the code

The answer is blue and I thought, since it’s all wrAP_content, the color of the interface should be white. But the correct answer is blue and let’s see why

LinearLayout onMeasure ()

OnMeasure () is simpler, but here we need to clarify what the parameters of this method mean:

MeasureSpec logs the dimensions of the current View and the measurement mode. The MeasureSpec is the parent View

/ * * *@param widthMeasureSpec horizontal space requirements as imposed by the parent.
 * @param heightMeasureSpec vertical space requirements as imposed by the parent.
 */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (mOrientation == VERTICAL) {
        measureVertical(widthMeasureSpec, heightMeasureSpec);
    } else{ measureHorizontal(widthMeasureSpec, heightMeasureSpec); }}Copy the code

The measureChildBeforeLayout() method is used to measure the child View. The measureChildBeforeLayout() method is used to measure the child View. The measureChildBeforeLayout() method is used to measure the child View. This method will eventually reach measureChildWithMargins() in the ViewGroup:

protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
    // This method basically does one thing: determine the child View's MeasureSpec from the child View's LayoutParams and the parent View's MeasureSpec
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

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

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
Copy the code

MeasureSpec generates a child View

This part of the logic is mainly in the getChildMeasureSpec() method, so we’ll just go after it:

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    // Omit part of initialization code
    switch (specMode) { 
        case MeasureSpec.EXACTLY: 
            if (childDimension >= 0) { 
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        case MeasureSpec.AT_MOST: 
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                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) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                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);
}
Copy the code

This part of the code, even Google made the rules, there is nothing to say. It all boils down to this image from Exploring the Art of Android Development:If the parent View (LinearLayout) is wrap_content and the child View is wrap_parent, what should the child View measure look like? If the parent View is wrap_content, the child View is wrap_parent.

With the above analysis, it is easy to come to the answer: parentSize + AT_MOST. So we know that in this scenario, the child View wrap_parent means that its width and height is the parent View’s width and height. So what is the width and height of the parent View?

Since the parent View is already the root View, its outer side is a DecorView, and the DecorView’s MeasureSpec is relatively simple, directly based on the Window’s width and height and its own LayoutParams.

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {
        case ViewGroup.LayoutParams.MATCH_PARENT:
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
    }
    return measureSpec;
}

public void setContentView(View view) {
    setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
Copy the code

So in this case, the DecorView’s MeasureSpec is the width and height of the screen + EXACTLY, so the parent View (LinearLayout) is clear: parentSize + AT_MOST.

(parentSize + AT_MOST 2) MeasureSpec (parentSize + AT_MOST 2) The parent View (LinearLayout) MeasureSpec AT_MOST parentSize + 3. DecorView MeasureSpec is the size of the screen + EXACTLY

Execute the measure() method of the child View

Let’s take a look at the measure() method of the subview, MeasureChildWithMargins () the measureChildWithMargins() method calculates the child View MeasureSpec based on the parent View’s MeasureSpec and the child View’s LayoutParams, and then calls the child View’s measure() :

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
		getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

public static int getDefaultSize(int size, int measureSpec) {
	int result = size;
	int specMode = MeasureSpec.getMode(measureSpec);
	int specSize = MeasureSpec.getSize(measureSpec);

	switch (specMode) {
		case MeasureSpec.UNSPECIFIED:
		result = size;
		break;
		case MeasureSpec.AT_MOST:
		case MeasureSpec.EXACTLY:
		result = specSize;
		break;
	}
	return result;
}
Copy the code

As you can see from the code, the width of the View is the width of the parent View, which is the width of the screen. So the final effect is full screen blue.

The resources

The Measure Contains UNSPECIFIED, as well as UNSPECIFIED, the layout of the View group. The measure contains UNSPECIFIED, as well as UNSPECIFIED, as well as UNSPECIFIED, as well as UNSPECIFIED.