preface

Before saying that the Event distribution process of Android View, belonging to the Android knowledge review series, and then to review the drawing process of Android View, nothing more than the measurement, layout and drawing of these three processes, but usually when doing custom View and no in-depth study, so here to take a good look.

The body of the

Again, it’s too much, so here, as before, we’ll do it step by step.

Who is responsible for drawing the View

We often say that the View drawing process, this is responsible for doing this thing actually through the ViewRootImpl, for Windows, ViewRootImpl, etc. ](Activity, Window, ViewRoot, decorView-nuggets (juejin. Cn))

To put it bluntly, ViewRootImpl will be responsible for the drawing of all views in this View chain. It will call corresponding methods to complete the three processes of measure, layout and draw of top-level View. In each step, the process of sub-view will be carried out. Finally complete the page of all the View drawing.

MeasureSpec

A MeasureSpec is a View’s MeasureSpec, so it’s important to know what a MeasureSpec is and how to measure it.

The design of the MeasureSpec

In fact, when measuring the width or height of a View, we need two key information, one is the measurement mode, one is the size, if we design the code, we might use enumeration plus int to solve the problem, but in Android source code, it is not the case, let’s look at the source code:

/ / static class
public static class MeasureSpec {
    // Shift by 30 bits
    private static final int MODE_SHIFT = 30;
    //MODE_MASK = 110000... 000 is 11 followed by 30 zeros
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
    //UNSPECIFED mode = 00... 000 is 32 0's
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    //EXACTLY = 01000... 000 is 01 followed by 30 zeros
    public static final int EXACTLY     = 1 << MODE_SHIFT;
    // the value of AT_MOST is 10000... 000 is 10 followed by 30 zeros
    public static final int AT_MOST     = 2 << MODE_SHIFT;

     // Create a 32-bit MeasureSpec value with up to 30 bits of binary size and three modes
     // The middle and high 32 bits 00 01 10 indicate the mode, and the lower 30 bits represent the size
    public static int makeMeasureSpec(int size, @MeasureSpecMode int mode) {
        // Do not consider this case
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return(size & ~MODE_MASK) | (mode & MODE_MASK); }}// Get mode
    public static int getMode(int measureSpec) {
        //noinspection ResourceType
        return (measureSpec & MODE_MASK);
    }

    // Get the size
    public static int getSize(int measureSpec) {
        return(measureSpec & ~MODE_MASK); }}Copy the code

You will find that there is only one int representing two kinds of information, and the most important thing is to use the bit operation, which is faster in the computer.

The meaning of the MeasureSpec

In fact, we can think about how we normally set the size of a layout in XML. The key points are the layout_width and layout_height properties, and there are three options that can be filled in, Match_parent and wrap_content are match_parent and wrap_content respectively, where both match_parent and wrap_content are known (if the parent View is known) and wrap_content is not.

The size of a parent View depends on the size of the parent View and the size of the parent View. (MeasureSpec)

All three options in XML are converted to the LayoutParams class of the current View when parsing the XML, so it’s important to take a look at this class.

LayoutParams

This class is very common when we use code to set the layout. In fact, it is to record some attributes in the XML. Let’s look at the source code:

// This is LayoutParams in ViewGroup
public static class LayoutParams {
    // corresponds to match_parent and wrap_parent in XML
    public static final int FILL_PARENT = -1;
    public static final int MATCH_PARENT = -1;
    public static final int WRAP_CONTENT = -2;
    / / width
    public int width;
    / / height
    public int height;
    // constructor
    public LayoutParams(Context c, AttributeSet attrs) {
        TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
        // Parse out the attributes defined by the XML and assign values to the width and height attributes
        setBaseAttributes(a,
                R.styleable.ViewGroup_Layout_layout_width,
                R.styleable.ViewGroup_Layout_layout_height);
        a.recycle();
    }
    // constructor for code to create instances
    public LayoutParams(int width, int height) {
        this.width = width;
        this.height = height;
    }
    // Read the corresponding attribute in XML
    protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
        width = a.getLayoutDimension(widthAttr, "layout_width");
        height = a.getLayoutDimension(heightAttr, "layout_height"); }}Copy the code

Here we see that the width and height properties that we set in the XML are recorded in the LayoutParams of the ViewGroup.

Now, LayoutParams, we’re just going to extend it a little bit, because one of the mistakes we make when we set up this LayoutParams in our code is to get the LayoutParams of this View, which is usually not viewGroup.LayoutParams, But the other ones, if you don’t pay attention to it, you can force it to fail, so here are two more common subclasses.

MarginLayoutParams: MarginLayoutParams: MarginLayoutParams: MarginLayoutParams: MarginLayoutParams: MarginLayoutParams: MarginLayoutParams:

public static class MarginLayoutParams extends ViewGroup.LayoutParams {
    // The spacing of the four directions
    public int leftMargin;
    public int topMargin;
    public int rightMargin;
    public int bottomMargin;

    // Parse the margin, topMargin, leftMargin, bottomMargin, and rightMargin attributes in XML respectively
    public MarginLayoutParams(Context c, AttributeSet attrs) {
        super(a); TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout); setBaseAttributes(a, R.styleable.ViewGroup_MarginLayout_layout_width, R.styleable.ViewGroup_MarginLayout_layout_height);/ / to omit

        a.recycle();
    }

   / / to omit
}
Copy the code

We can set margins in XML regardless of which ViewGroup our View is in, and the values of those margins will be saved.

The second is the specific LayoutParams, such as, for example, here LinearLayout. LayoutParams, first of all, in retrospect, what are some of the characteristics of the layout of the linear layout parameters, in XML to write new View in a linear layout, then you can set the width or height of 0 dp, then set the weight, And set the layout_gravity properties, so these properties are saved in the corresponding layout parameter LayoutParams when parsing the XML. The layout parameter code for linear layout is as follows:

// LayoutParams with linear layout
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
    // Weight attributes
    @InspectableProperty(name = "layout_weight")
    public float weight;
    / / layout_gravity attribute
    @ViewDebug.ExportedProperty(category = "layout", mapping = {
        @ViewDebug.IntToString(from =  -1,                       to = "NONE"),
        @ViewDebug.IntToString(from = Gravity.NO_GRAVITY,        to = "NONE"),
        @ViewDebug.IntToString(from = Gravity.TOP,               to = "TOP"),
        @ViewDebug.IntToString(from = Gravity.BOTTOM,            to = "BOTTOM"),
        @ViewDebug.IntToString(from = Gravity.LEFT,              to = "LEFT"),
        @ViewDebug.IntToString(from = Gravity.RIGHT,             to = "RIGHT"),
        @ViewDebug.IntToString(from = Gravity.START,             to = "START"),
        @ViewDebug.IntToString(from = Gravity.END,               to = "END"),
        @ViewDebug.IntToString(from = Gravity.CENTER_VERTICAL,   to = "CENTER_VERTICAL"),
        @ViewDebug.IntToString(from = Gravity.FILL_VERTICAL,     to = "FILL_VERTICAL"),
        @ViewDebug.IntToString(from = Gravity.CENTER_HORIZONTAL, to = "CENTER_HORIZONTAL"),
        @ViewDebug.IntToString(from = Gravity.FILL_HORIZONTAL,   to = "FILL_HORIZONTAL"),
        @ViewDebug.IntToString(from = Gravity.CENTER,            to = "CENTER"),
        @ViewDebug.IntToString(from = Gravity.FILL,              to = "FILL")
    })
    @InspectableProperty(
            name = "layout_gravity",
            valueType = InspectableProperty.ValueType.GRAVITY)
    public int gravity = -1;
    // Get the corresponding attribute from the constructor
    public LayoutParams(Context c, AttributeSet attrs) {
        super(c, attrs);
        TypedArray a =
                c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout);

        weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0);
        gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1); a.recycle(); }}Copy the code

So that’s it. LayoutParams is a class that puts the layout properties that we wrote in XML into this class, and it has its own unique LayoutParams for different viewgroups, which explains why when you create and add views in code, You can’t go wrong with this LayoutParams.

MeasureSpec and LayoutParams

The LayoutParams class is the same as the LayoutParams class. The LayoutParams class is the same as the LayoutParams class. The LayoutParams class is the same as the LayoutParams class. So it’s important to understand the transformation relationship between MeasureSpec and LayoutParams.

XML -> LayoutParams -> MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec;

Normally, a View’s MeasureSpec is derived from its parent’s MeasureSpec and its own LayoutParams, but the conversion relationship is a bit different for different views. Let’s look at each of them.

The DecorView MeasureSpec

Since a DecorView is a top-level View, it does not have a parent View, let’s look at how its MeasureSpec is generated in the measureHierarchy method of ViewRootImpl, as follows:

// Get the decorView's MeasureSpec
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
// Start measuring the DecorView
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
Copy the code

Take a look at the getRootMeasureSpec method:

//windowSize is the size of the current Window
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {
    case ViewGroup.LayoutParams.MATCH_PARENT:
        // When the layout parameter is match_parent, the measurement mode is EXACTLY
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
    caseViewGroup.LayoutParams.WRAP_CONTENT: / when is wrap_content layout parameters, measuring model is AT_MOST measureSpec = measureSpec. MakeMeasureSpec (windowSize, measureSpec. AT_MOST);break;
    default:
        // EXACTLY
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}
Copy the code

Here we can see that the Measure of the DecorView is very simple: when it is match_parent, the Measure mode is EXACTLY the exact mode and the size is Window, when it is wrap_content, the Measure mode is AT_MOST, The maximum size is window.

The View of the MeasureSpec

The MeasureSpec of a View is slightly different because it must have a parent View, so its MeasureSpec creation is related not only to its own LayoutParam, but also to the parent View’s MeasureSpec.

Instead of talking about the ViewGroup and how the View distributes the measurement process, here’s a common method we use to customize a ViewGroup:

// The code in the ViewGroup iterates through the child views while customizing the ViewGroup and then measures them one by one
protected void measureChildWithMargins(
        / / the View
        View child,
        // The ViewGroup's MeasureSpec is the parent View's MeasureSpec
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
    // The LayoutParams of the child View
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    // Get the child View's MeasureSpec
    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);
    // Subview to measure
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
Copy the code

A parent View’s MeasureSpec and its own LayoutParams is a parent View’s MeasureSpec. A parent View’s MeasureSpec and its own LayoutParams are a parent View’s MeasureSpec.

// The code to call
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
        mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                + widthUsed, lp.width);
Copy the code

The parent View’s MeasureSpec is the parent View’s MeasureSpec, the parent View’s layout is the parent View’s MeasureSpec, the parent View’s layout is the parent View’s MeasureSpec, the parent View’s layout is the parent View’s layout, and the parent View’s padding is the parent View’s layout. Because this property is not recorded in LayoutParams, and what it means is internal spacing, in this case it’s a property value written in the parent ViewGroup, so for example if you add this paddingLeft property, the child View doesn’t draw from the origin, the width available to it gets smaller, So the View has to exclude the padding when it measures its size.

Then look at the source code implementation:

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    // The parent View's measurement mode
    int specMode = MeasureSpec.getMode(spec);
    // Size of the parent View
    int specSize = MeasureSpec.getSize(spec);
    // Whether the padding is larger than the parent View
    int size = Math.max(0, specSize - padding);

    // The size of the subview
    int resultSize = 0;
    // The measurement mode of the subview
    int resultMode = 0;
    
    // Wrap_content in layoutParams is -2, match_parent is -1, which is greater than 0
    switch (specMode) {
    // The measurement mode of the parent View is precise mode
    case MeasureSpec.EXACTLY:
        if (childDimension >= 0) {
            // The subview is currently sized, so the measurement mode must be exact
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // The child View is the same size as the parent View, so the measurement mode must be exact
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // The child View is the contents of the package, and its maximum value is the size of the parent View
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

    // The measurement mode of the parent View is maximum mode
    case MeasureSpec.AT_MOST:
        if (childDimension >= 0) {
            // The subview is case-insensitive, and the measurement mode must be exact
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // As with the superclass, it is the superclass's maximum mode
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // It is important to note that this View is at most mode because of the maximum size of its parent class
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

    / / no analysis
    case MeasureSpec.UNSPECIFIED:
        if (childDimension >= 0) {
            // Child wants a specific size... let them have it
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size... find out how big it should
            // be
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size.... find out how
            // big it should be
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        }
        break;
    }
    //noinspection ResourceType
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
Copy the code

As you can see from this code, unlike the DecorView, the parent View’s MeasureSpec is related to its own LayoutParams.

MeasureSpec creates the relationship

A parent View and its Own LayoutParams are a MeasureSpec. A parent View and its own LayoutParams are a MeasureSpec. A parent View and its own LayoutParams are a MeasureSpec.

conclusion

This article will stop here, but it is actually quite easy to derive a MeasureSpec for each View when we understand the relationships between attributes in XML and the LayoutParams attribute, combined with the MeasureSpec structure.

So what’s the problem

  • When does the ViewGroup measure the child View, that is, distribute the measure event

  • How does a custom ViewGroup measure its own MeasureSpec

  • If one layer is wrapped with wrap_content, what should the size of the top View look like

  • Since drawing starts in the decorView of ViewRootImpl, can I refresh only one View? When do I need to refresh only the current View instead of the entire View chain

With the above questions, we will continue our analysis in the next article.