Drawing-measure ment process of View

Keywords: Android xml Google Attribute

Catalog

Effect

Used to measure the width of the View, when executing the layout, according to the measured width to determine their own and sub-View position.

Basic knowledge

In the measure ment process, two knowledge points, Layout Params and MesureSpec, are designed. Let's briefly say that if there's anything else that we don't understand, Google it!

LayoutParams

Simply put, it's the layout parameters, which contain the width and height of the View. Each subclass of ViewGroup has corresponding LayoutParams, such as LinearLayout.LayoutParams, RelativeLayout.LayoutParams. You can see that LayoutParams is an internal class of the ViewGroup subclass.

value Meaning
LayoutParams.MATCH_PARENT Equivalent to setting View properties in xml to match_parent and fill_parent
LayoutParams.WRAP_CONTENT Equivalent to setting View's attribute to wrap_content in xml

MeasureSpec

MeasureSpec is the measurement rule of View. Usually, when a parent controls to measure a child control, the values of widthMeasureSpec and heightMeasureSpec int are passed to the child controls. This value contains two information, SpecMode and SpecSize. How can an int value contain two messages? We know that int is a 4-byte 32-bit data. In these two types of int data, the first two bits are SpecMode, and the second 30 bits are SpecSize.

There are three types of mode: UNSPECIFIED, EXACTLY, AT_MOST.

Measurement mode application
EXACTLY Precision mode, when width or height is fixed xxdp or MACH_PARENT, is the measurement mode.
AT_MOST When width or height is set to warp_content, this is the measurement mode.
UNSPECIFIED The parent container has no display of the current View, and the child View can take any size. Usually used within the system, such as: Scrollview, ListView.

How do we extract two messages from an int? Don't worry, there is a MeasureSpec class inside View. This class has already encapsulated various methods for us:

//Combine Size and mode into an int value
int measureSpec = MeasureSpec.makeMeasureSpec(size,mode);
//Get size
int size = MeasureSpec.getSize(measureSpec);
//Get the mode l type
int mode = MeasureSpec.getMode(measureSpec);

Specific implementation details, you can see the source code, or Google it!

Execution process

Note: The following are about source code, all of which are version 27.

We know that the root View of a View is DecorView. When we open an Activity, we add DecorView to window s, create a RootViewImpl object, and associate the RootViewImpl object with the DecorView object. RootViewImpl is the link between Windows Manager and DecorView. Detailed DecorView can be seen This article

The drawing process of View starts with RootViewImpl. The performMeasure(), performLayout and performDraw methods are executed in its performTraversals () method. And these three methods execute view.measure(), view.layout(), view.draw() respectively, so as to start the drawing process of the whole view tree.

The execution process of measure in ViewGroup

ViewGroup itself inherits View, as we all know. The measure method is not found in the ViewGroup, so it is found in its parent View. The specific source code is as follows:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    /*....Eliminate code...*/
    if (forceLayout || needsLayout) {
     /*....Eliminate code...*/
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
            // measure ourselves, this should set the measured dimension flag back
            //Execute the onMeasure method
            onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
        /*....Eliminate code...*/

    }
    /*....Eliminate code...*/
}

As we can see, the measure method is modified by final, and subclasses cannot be overridden. The onMeasure method is called in the measure method.

Then we continue to look for the onMeasure method, and we will find that the onMeasure method is not implemented in ViewGroup, but only found in View. WTF? Does the onMeasure of ViewGroup follow the same approach in View? No, ViewGroup itself is an abstract class. There are many subclasses of ViewGroup in Android SDK, such as LinearLayout, Relative Layout, FrameLayout, etc. These controls have different characteristics and different measurement rules. Each of them implements the onMeasure method and then measures the control according to its own specific measurement rules. PS: If our custom controls inherit ViewGroup, we must rewrite the onMeasure method and make measurement rules according to requirements.

Here we take Linear Layout as an example to do source code analysis:

//LinearLayout class
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (mOrientation == VERTICAL) {
    //If the direction is vertical, the vertical direction is measured.
        measureVertical(widthMeasureSpec, heightMeasureSpec);
    } else {
    //Horizontal direction measurement
        measureHorizontal(widthMeasureSpec, heightMeasureSpec);
    }
}

The measureVertical process is similar to the measureHorizontal process. We analyze the measureVertical process. (The following source code has been deleted)

//LinearLayout class
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
    mTotalLength = 0;
    float totalWeight = 0;

    final int count = getVirtualChildCount();
    //Get LinearLayout's Width Mode SpecMode
    final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

    boolean skippedMeasure = false;

    // See how tall everyone is. Also remember max width.
    //Traverse through the sub-View to see how high each sub-class is and remember the maximum width.
    for (int i = 0; i < count; ++i) {
        final View child = getVirtualChildAt(i);
        if (child == null) {
        //measureNullChild () invariably returns 0,
            mTotalLength += measureNullChild (i);
            continue;
        }
        //If the GONE state is in the child control, it is skipped and no measurement is made.
        //You can also see that if the child View is INVISIBLE, it is also measurable.
        if (child.getVisibility() == View.GONE) {
        //getChildrenSkipCount also returns constant to zero.
           i += getChildrenSkipCount(child, i);
           continue;
        }

        //Gets the parameter information of the child control.
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();

        totalWeight += lp.weight;
        //Does the child control set weight? 
        final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
        if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
            final int totalLength = mTotalLength;
            mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
            //If the weight is set, skippedMeasure is marked as true.
            //The value of skippedMeasure and other conditions are then used to determine whether to redraw.
            //So, using weight weight weight in Linear Layout results in two measurements, which is time-consuming.
            //Consider using Relative Layout or Constraint Layout
            skippedMeasure = true;
        } else {
            if (useExcessSpace) {
                lp.height = LayoutParams.WRAP_CONTENT;
            }

           //Calculate the height that has been used
            final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
            /*This code is the key, literally, to layout. 
            The sub-View measurements were made before.*/
            measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                    heightMeasureSpec, usedHeight);
        }
    }
}

So we're looking at the measureChildBeforeLayout method:

//LinearLayout class
void measureChildBeforeLayout(View child, int childIndex,
        int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
        int totalHeight) {
    measureChildWithMargins(child, widthMeasureSpec, totalWidth,
            heightMeasureSpec, totalHeight);
}

Then look at the measureChildWithMargins method, and finally come to the ViewGroup class:

//ViewGroup class
protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
        /*Get the layout parameter MarginLayoutParams of the sub-View to get the sub-View 
        The margin property set.*/
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    //Gets the MesureSpec value of the child View width.
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                    + widthUsed, lp.width);
    //Gets the MesureSpec value of the child View height.
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                    + heightUsed, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

Another method in ViewGroup is measureChild(int widthMeasureSpec, int heightMeasureSpec). This method works in the same way as measureChildWithMargins, both of which are measureSpec s that generate sub-Views. It's just different.

It is acquired by getChildMeasureSpec method when obtaining the width and height attributes of the child View. This method is the main process of the ViewGroup to set the measureSpec of the sub-View according to its own measureSpec and LayoutParams of the sub-View.

//ViewGroup class
/**
 * @param spec measureSpec of the parent class
 * @param padding padding of the parent class + margin of the child class
 * @param childDimension LayoutParams.width/LayoutParams.height attribute of subview
 */
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    //Get the measurement mode specMode of the parent control
    int specMode = MeasureSpec.getMode(spec);
    //Gets the measured size SpecSize of the parent control
    int specSize = MeasureSpec.getSize(spec);
    //Gets the remaining width/height size of the parent control
    int size = Math.max(0, specSize - padding);
    //Measurement Size of SubView
    int resultSize = 0;
    //Measurement Mode of Sub-View
    int resultMode = 0;

    switch (specMode) {
    // The width mode of the parent control is the precision mode EXACTLY
    case MeasureSpec.EXACTLY:
        if (childDimension >= 0) {
            //If the width/height of the subview is a specific value (specific xxdp/px)
            //Mode mode is set to exact mode EXACTLY, size size size is the size of specific settings.
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            //If the width/height of the subview is MATCH_PARENT
            //The mode mode is set to the exact mode EXACTLY, and the size size size is the remaining space of the parent control.
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            //If the width/height of the subview is WRAP_CONTENT
            /*The mode mode is set to the precise mode AT_MOST, and the size size size is the remaining space of the parent control.
            Subcontrols can set width and height in this size range*/
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

    // Parent has imposed a maximum size on us
    //The parent control measures in AT_MOST, giving the child View a maximum value
    case MeasureSpec.AT_MOST:
        if (childDimension >= 0) {
            //If the width/height of the subview is a specific value (specific xxdp/px)
            //Mode mode is set to exact mode EXACTLY, size size size is the size of specific settings.
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            //If the width/height of the subview is MATCH_PARENT
            /*The mode mode is set to the precise mode AT_MOST, and the size size size is the remaining space of the parent control.
            Subcontrols can set width and height in this size range*/
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            //If the width/height of the subview is MATCH_PARENT
            /*The mode mode is set to the precise mode AT_MOST, and the size size size is the remaining space of the parent control.
            Subcontrols can set width and height in this size range*/
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

    // Parent asked to see how big we want to be
    //The parent control does not limit the width of the child View, and is generally used for ListView, Scrollview
    //Usually, it's not necessary to analyze it.
    case MeasureSpec.UNSPECIFIED:
        if (childDimension >= 0) {
            // Child wants a specific size... let him 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;
    }
    //MeansSpec for Generating SubView
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

This is how ViewGroup generates measureSpec of sub-View based on its own measureSpec and LayoutParams of sub-View. The specific summary is as follows:

This is how LinearLayout measures the width and height of the child controls.

From the above table, we can also see that when we inherit View from a custom control, we still need to rewrite the onMeasure method of View to deal with wrap_content. If we do not deal with wrap_content, the wrap_content effect is the same as match_parent, which is filled with the parent control. You can add a < View android: layout_width= "match_parent" android: layout_height= "wrap_content"/> control directly to the xml layout to feel for yourself.

After LinearLayout has measured the child control, it sets its own width and height according to the child control's width and height:

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
    // Add in our padding
    //Add your own padding value
    mTotalLength += mPaddingTop + mPaddingBottom;

    int heightSize = mTotalLength;

    // Check against our minimum height
    //Gett Suggested Minimum Height from the minimum recommended height and heightSize is analyzed later.
    heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

    int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
    /*....Eliminate code...*/
    //After traversing the child control, set its own width and height
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            heightSizeAndState);
}
//If LinearLayout is high to a specific value, heightSize AndState is the specific value.
//Otherwise, it is the sum of the height of the child control, but it cannot exceed the remaining space of its parent container.
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:
            if (specSize < size) {
                result = specSize | MEASURED_STATE_TOO_SMALL;
            } else {
                result = size;
            }
            break;
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        case MeasureSpec.UNSPECIFIED:
        default:
            result = size;
    }
    return result | (childMeasuredState & MEASURED_STATE_MASK);
}

At this point, we can see that when ViewGroup generates a sub-View wide/high measureSpec, it begins to call the sub-View for measurement. If the sub-View inherits ViewGroup, it repeats the above process (each different ViewGroup subclass executes its own onMeasure method); if it is a specific View, it starts the measurement process of the specific View. Finally, according to the width of the child control and other conditions to determine its own width and height.

The Execution Process of measure in View

The specific source code of View measure has been analyzed in ViewGroup. Here we mainly analyze the onMeasure process of View.

//Class View
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //The width and height are obtained by getDefaultSize and set to the measured value.
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

getDefaultSize Specific Source Code

//Class View
/**
 * @param size Recommended minimum width obtained through getSuggested Minimum Width
 * @param measureSpec measureSpec generated by parent control
 */
public static int getDefaultSize(int size, int measureSpec) {
    //Wide / high value
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
    //If it's UNSPECIFIED, set it to the recommended minimum.
        result = size;
        break;
    /*Otherwise, they are all set to the values generated by the parent control (if the child control is specific)
    xxdp/px Value is the specific value, if not the remaining space of the parent control. See the above analysis for details.*/
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

// Recommended minimum

//Class View
protected int getSuggestedMinimumWidth() {
    //If there is no backgroundsetting, the minimum recommended value is minWidth.
    //If so, take the maximum of mMinWidth and background minimum.
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

What is the minimum background value? Click on the source code to see the Drawable class.

//Class Drawable
public int getMinimumWidth() {
    //First, get the original width of Drawable
    final int intrinsicWidth = getIntrinsicWidth();
    //If there is an original width, it returns the original width; if not, it returns 0.
    //Note: For example, Shape Drawable has no original width and Bitmap Drawable has original width and height (image size).
    return intrinsicWidth > 0 ? intrinsicWidth : 0;
}

At this point, the measure of View is analyzed.

Measurement Spec Computing Logic of DecorView

We may have doubts if all the measureSpecs of the child controls are generated by the parent control combining its own measureSpec with the LayoutParams of the child View. So how does DecorView, the top parent of a view, get its own measureSpec? Let's analyze the source code below: (the following source code has been deleted)

//ViewRootImpl class
private void performTraversals() {
    //MeasursureSpec to Get DecorView Width 
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    //Get the measure Spec of DecorView Height
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    // Ask host how big it wants to be
    //Start executing measurements
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
//ViewRootImpl class
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;
}

Windows Size is the width and height of the widow, so we can see that the measure Spec of DecorView is generated according to the width and size of the window and its own Layout Params.

summary

Finally, relevant Android data are collected:

Tien Zan+Jia Qun Free Access Architecture Design of Android IOC

Additive group Architecture Design of Android IOC Receive advanced Android architecture information, source code, notes, videos. Advanced UI, Performance Optimization, Architect Course, NDK, React Native + Weex, Flutter's all-round Android advanced practice technology, there are also technical bulls in the group to discuss and solve problems.

Posted by Youko on Tue, 23 Apr 2019 21:03:35 -0700