preface

In the process of drawing a View, measure is the first step. The View first needs to measure to get the specific length and width, which is just like drawing a picture first needs to get the approximate width and height of the object to be painted.

Remember the performTraversals method in the top-level view?

If you forget, you can take a look at the View system in Android in the previous article

PerformTraversals is a method with nearly a thousand lines of code, so it is not necessary to go through it in order to understand the View rendering process, but to analyze the core method of rendering

Source code analysis

The measure of the entrance

In the performTraversals method there will be a method like this:

WindowManager.LayoutParams lp = mWindowAttributes;

// Get the relevant size parameters
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

// As the name implies, this is a method that executes a measure
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
Copy the code

MeasureSpec: MeasureSpec: MeasureSpec: MeasureSpec: MeasureSpec: MeasureSpec: MeasureSpec: MeasureSpec

About measurement specification MeasureSpec

MeasureSpec can be understood as a criterion for measuring a View. MeasureSpec is an inner class of a View and consists of two parts:

MeasureSpec = Measurement mode + measurement size

  • Measurement specification: 32 bit Int type
  • Measurement mode: 2 bits higher than the measurement specification
  • Measurement size: 30 bits lower than the measurement specification

There are three measurement modes:

Specifications and types The specification
UNSPECIFIED The parent View does not restrict the size of the child View, which is usually used in list-based controls, but not in custom views
EXACTLY The parent view specifies a certain size for the child view. This is used when the control is of fixed size or match_parent, in which case the parent view can directly determine the size of the child view.
AT_MOST The parent view specifies a maximum size for the child view. This is usually applied to the wrAP_content control. In this case, the parent view cannot get the size of the child view, only the child view can get it.

So the MeasureSpec class can be understood as an abstraction of the View dimension specification for subsequent measurement calculations.

So let’s continue our process analysis.

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    if (mView == null) {
        return;
    }

    try {
        // This mView is the layout we define ourselves by passing in our setContentView
        // Measure method is called by View. Start measure process
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally{ Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}Copy the code

Analysis 1. Start the measure process

// View's measure method
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    boolean optical = isLayoutModeOptical(this);
    if(optical ! = isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets();int oWidth  = insets.left + insets.right;
        int oHeight = insets.top  + insets.bottom;
        widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
        heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
    }
    
    // Analyze the response process to measureonMeasure(widthMeasureSpec, heightMeasureSpec); ...}Copy the code

In the method initiated by measure, the measurement specification of width and height is first obtained and then passed to the method responding to measure.

2. Analyze the response process to measure

// View's onMeasure method
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // Set the measure size
    // The parameter of this method is the measured width and height, but the method of obtaining the parameter value is complicated
    / / getSuggestedMinimumWidth () & getSuggestedMinimumHeight () method
    // getDefaultSize(int size, int measureSpec
    // setMeasuredDimension(int measuredWidth, int measuredHeight)
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
Copy the code

GetSuggestedMinimumWidth () & getSuggestedMinimumHeight () analysis

Get the minimum control width and height depending on whether there is a background

protected int getSuggestedMinimumWidth(a) {
    return (mBackground == null)? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }Copy the code
protected int getSuggestedMinimumHeight(a) {
    return (mBackground == null)? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); }Copy the code

GetDefaultSize (int size, int measureSpec

// Get the default size according to the minimum width and height obtained and the measurement criteria
public static int getDefaultSize(int size, int measureSpec) {
    // Minimum size
    int result = size;
    // Measurement mode
    int specMode = MeasureSpec.getMode(measureSpec);
    // Measure the size
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:           // If you are not restricted to use the minimum size
        result = size;
        break;
    case MeasureSpec.AT_MOST:               // Measure size is used if the size is specified or limited
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}
Copy the code

SetMeasuredDimension (int measuredWidth, int measuredHeight) analysis

// measuredWidth & measuredHeight - measuredWidth and measuredHeight
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
    // Check whether it is a ViewGroup
    boolean optical = isLayoutModeOptical(this);
    // Check whether the parent layout is ViewGroup
    if(optical ! = isLayoutModeOptical(mParent)) {// Get visual width and height (depending on whether there is a background)
        Insets insets = getOpticalInsets();
        int opticalWidth  = insets.left + insets.right;
        int opticalHeight = insets.top  + insets.bottom;
        // Perform further calculations based on whether it is a ViewGroup
        measuredWidth  += optical ? opticalWidth  : -opticalWidth;
        measuredHeight += optical ? opticalHeight : -opticalHeight;
    }
    // Pass the final measurement width to the setMeasuredDimensionRaw method
    setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
Copy the code

SetMeasuredDimensionRaw (int measuredWidth, int measuredHeight) analysis

// View gets the measured width and height
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    mMeasuredWidth = measuredWidth;
    mMeasuredHeight = measuredHeight;

    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
Copy the code

Once the View has measured the mMeasuredWidth and mMeasuredHeight, we can move on to the next step, but first we need to look at the ViewGroup’s measure process.

ViewGroup drawing process

The onMeasure method of a ViewGroup is implemented by subclasses. For example, FrameLayout:

onMeasure

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // Get the number of child elements
    int count = getChildCount();

    final booleanmeasureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) ! = MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) ! = MeasureSpec.EXACTLY; mMatchParentChildren.clear();// Maximum width and height
    int maxHeight = 0;
    int maxWidth = 0;
    int childState = 0;

    // Measure the Margins
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if(mMeasureAllChildren || child.getVisibility() ! = GONE) { measureChildWithMargins(child, widthMeasureSpec,0, heightMeasureSpec, 0);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            maxWidth = Math.max(maxWidth,
                    child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
            maxHeight = Math.max(maxHeight,
                    child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
            childState = combineMeasuredStates(childState, child.getMeasuredState());
            if (measureMatchParentChildren) {
                if(lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); }}}}// Measure the padding
    maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
    maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

    // Compare the existing width and height with the background to get a larger value
    maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
    maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

    // Compare the existing width and height with the foreground to get a larger value
    final Drawable drawable = getForeground();
    if(drawable ! =null) {
        maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
        maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
    }

    // Pass in the measured width and height
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            resolveSizeAndState(maxHeight, heightMeasureSpec,
                    childState << MEASURED_HEIGHT_STATE_SHIFT));

    // Iterate over child elements to measure
    count = mMatchParentChildren.size();
    if (count > 1) {
        for (int i = 0; i < count; i++) {
            final View child = mMatchParentChildren.get(i);
            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

            final int childWidthMeasureSpec;
            if (lp.width == LayoutParams.MATCH_PARENT) {
                final int width = Math.max(0. getMeasuredWidth() - getPaddingLeftWithForeground() - getPaddingRightWithForeground() - lp.leftMargin - lp.rightMargin);  childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( width, MeasureSpec.EXACTLY); }else {
                childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                        getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                        lp.leftMargin + lp.rightMargin,
                        lp.width);
            }

            final int childHeightMeasureSpec;
            if (lp.height == LayoutParams.MATCH_PARENT) {
                final int height = Math.max(0. getMeasuredHeight() - getPaddingTopWithForeground() - getPaddingBottomWithForeground() - lp.topMargin - lp.bottomMargin); childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( height, MeasureSpec.EXACTLY); }else {
                childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                        getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                        lp.topMargin + lp.bottomMargin,
                        lp.height);
            }
            
            // The child controls the measurement operationchild.measure(childWidthMeasureSpec, childHeightMeasureSpec); }}}Copy the code

It is important to note that the measurement order of all viewgroups is the same. The order of different layout measurement child controls and measurement itself may change according to specific logic, which is not listed here. If you need to achieve a custom ViewGroup can be advanced to the source code to understand how similar ViewGroup is calculated.

conclusion

In general, measures of View can be divided into two categories:

  • The View of the Measure
  • The Measure of ViewGroup

The View of the Measure

The Measure process of a View is to calculate and store the width and height of the View according to the measurement specifications in onMeasure.

The ViewGroup Measue

The Measure process of a ViewGroup iterates the sub-views according to the logic of different viewgroups in onMeasure. By iterating the measures of the sub-views, the width and height of the ViewGroup are finally determined and stored.

In our daily custom View development, we usually copy the onMeasure method to get the measured width and height. If we do not want to use the parent method, we need to implement the specific logic, but we must call the setMeasuredDimension method to set the measured width and height

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    // You can get the width and height of the measurement
}
Copy the code