I believe that the vast majority of Android developers have the experience of customizing views to meet various needs, and also know that the drawing and display of a View must go through three processes: Measure, layout and Draw. Among the three processes, measure is a little more complicated. This article, as a basic sharing of Android, will share the process of view/viewGroup measure, how view/viewGroup determines its width and height by measure, and finally customize a streaming layout to practice.

While layout is essentially calculating your own coordinates or your child’s coordinates, Draw requires the canvas and brush and the rich apis it provides to draw the effect you want.

This paper is mainly divided into the following three parts:

  • What is MeasureSpec and what does it do
  • What does the measure process look like and how does it determine the width and height of a View/ViewGroup
  • Override the onMeasure() method to customize a streaming layout

MeasureSpec 1. What is MeasureSpec and what does it do

The role of the MeasureSpec

We know that a View uses onMeasure() to determine its width and height (ViewGroup is an abstract class that inherits from a View, It doesn’t override onMeasure(), so if we don’t override onMeasure() when we customize the ViewGroup, it ends up calling the View’s onMeasure() method, assuming we don’t override onMeasure(), what is the width and height of the View? View.onMeasure:

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

Clearly, the setMeasureDimension() method is used to determine the width and height. Look at this method:

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        ```
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }
Copy the code
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        GetMeasuredWidth () and getMeasuredHeight() can be used to measure the View width
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }
Copy the code

SetMeasuredDimension (int measuredWidth, int measuredHeight) sets the width and height of the View onMeasure(). So where do the two width and height parameters come from? Then look at getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec):

public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        // Measure specMode and specSize from MeasureSpec
        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:
            // Assign specSize to result
            result = specSize;
            break;
        }
        return result;
    }
Copy the code

MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec MeasureSpec determines the width and height of the View/ViewGroup without overwriting the onMeasure() method.

MeasureSpec Basics

MeasureSpec: MeasureSpec: MeasureSpec: MeasureSpec

As can be seen from the getDefaultSize method, MeasureSpec contains two parts: a SpecMode and a SpecSize. It is represented by a 32-bit int value, with the code SpecMode 2 bits higher and SpecSize 30 bits lower.

Specmodes fall into three main categories:

model instructions
EXACTLY Set the exact width and height. If width and height are specified or match_parent is set to this mode
AT_MOST This mode is used when width and height are set to wrap_content
UNSPECIFIED The above two modes are common in our layout, and the maximum is not larger than the parent layout, and this mode is generally used in the system, the parent container does not have any restrictions on the View

How is MeasureSpec generated

A MeasureSpec of a View/ViewGroup determines the width and height of a View/ViewGroup without overwriting onMeasure(). A View/ViewGroup MeasureSpec is a View/ViewGroup MeasureSpec generated by its parent when it calls its child’s measure() method. A View/ViewGroup MeasureSpec is a View/ViewGroup MeasureSpec. What determines the SpecSize and SpecMode?

The parent container generates the child View’s MeasureSpec by calling getChildMeasureSpec() in the ViewGroup. The parent container’s MeasureSpec and the child View’s width and height determine the child View’s MeasureSpec’s SpecMode and SpecSize.

The generation rule in the getChildMeasureSpec() code:

1. If the width and height of the child View is set to a specific value, obviously we can directly obtain the width and height of the child View, then the child View width and height is determined, and we do not need to worry about the parent container’s SpecMode, so the child View’s SpecMode is EXACTLY the set width and height.

2. When the width and height of the child View is set to match_parent, the child View’s SpecSize is equal to the width and height of the parent, and the child View’s SpecMode is the same as the parent’s SpecMode. (The UNSPECIFIED mode is not considered here. If the parent container is UNSPECIFIED mode, the SpecSize of the child View is 0 and the SpecMode is UNSPECIFIED.)

3. When the child View is set to wrAP_content, the parent does not know how wide or high the child View should be. Therefore, the SpecSize of the child View is the parent View’s width and height. The SpecMode of the child View is AT_MOST. UNSPECIFIED mode is not considered here. If the parent container is UNSPECIFIED mode, the SpecSize of the child View is 0 and the SpecMode is UNSPECIFIED.

If you set the width and height of a View/ViewGroup to a specific value or match_parent, it will display correctly, but if you set wrap_content, it will display the size of its parent by default. If you want it to display properly as wrAP_content, then you have to override onMeasure() to calculate its width and set it yourself. So the reason we override onMeasure() when customizing a View/ViewGroup is to make wrap_content work.

2. What does the measure process look like? How does it determine the width and height of a View/ViewGroup

We know that the entire rendering process starts from performTraversals() in ViewRootImpl, which executes performMeasure, performLayout and performDraw respectively to complete the three main rendering processes. The three processes are all top-down. Today we only talk about the process of measure.

A DecorView(root View) with a ViewGroup(ViewB) inside the ViewGroup is used as an example to illustrate the process:

1. ViewRootImpl.performTraversals()->performMeasure():

Call getRootMeasureSpec () to generate the DecorView’s MeasureSpec based on the width and height of the screen and the DecorView’s LayoutParams, and then call the DecorView’s measure() to start measuring the DecorView

2.DecorView.measure()->onMeasure():

DecorView descends from FrameLayout, so it goes to FrameLayout onMeasure(),onMeasure() and measureChild() to generate MeasureSpec for ViewGroupA. And start the ViewGroupA measurement by viewgroupa.measure ()

3.ViewGroupA.measure()->onMeasure():

This is our custom ViewGroup(inherited from ViewGroup). If we don’t override onMeasure(), the default is view.onMeasure (). If the width and height of the ViewGroup are not set, the measure of the sub-view will not be initiated and the sub-view inside it will not be measured (0). (wrAP_content) the ViewGroup displays the width and height of the parent container (according to the MeasureSpec generation rules above).

So if we inherit from a ViewGroup and customize a ViewGroup, we definitely overwrite onMeasure(), MeasureChild () generates MeasureSpec for the child View and calls Child.measure () to measure the child so that the View can be measured. If we want our wrap_content to work, we need to calculate our own width and height based on the child View measurements, Finally, set the measuredDimension (int measuredWidth, int measuredHeight) to achieve wrAP_content.

4. ViewB.measure()->onMeasure():

A View measurement is easier than a ViewGroup because it doesn’t need to Measure a Child, but again, the onMeasure() calculation needs to be overridden to make wrap_conten work.

3. Override the onMeasure() method to customize a streaming layout

For a streaming layout, also known as a wrap layout, it automatically folds when a row does not fit.

Specific implementation address :github.com/zhengcx/Lin…

According to the above measure process analysis, this effect should be relatively easy to achieve. We don’t need to worry too much about width measurement, just take the SpecSize in MeasureSpec directly. Therefore, it is mainly about height measurement.

MeasureSpec (ViewGroup.measureChild()) = MeasureSpec (viewGroup.measureChild ()) = MeasureSpec (ViewGroup.measureChild()) = MeasureSpec (ViewGroup.measureChild())

2. Get the measured width and height of the subview and calculate the width and height of your wrAP_content. In this example, the width of the subview is used to determine whether the line should be folded. If the line is folded, the height of the ViewGroup will become larger. In short, the height of the ViewGroup should be calculated.

3. Set your width and height by setting setMeasuredDimension(int measuredWidth, int measuredHeight).

Specific implementation see github:github.com/zhengcx/Lin…