View workflow

The workflow of View is measure, layout and draw. Measure is used to measure the width and height of the View, layout is used to determine the position of the View, and Draw is used to draw the View. Here, measure is relatively complex. Measure process can be divided into View measure process and ViewGroup measure process. Except that the ViewGroup’s measure process has to do its own measurement and traverse to call the child’s measure() method.

The View of measurement

Let’s start with the onMeasure() method (view.java) :

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

The setMeasuredDimension() method of onMeasure() calls the onMeasure() method directly, and the setMeasuredDimension() method of onMeasure() cannot be modified.

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
       boolean optical = isLayoutModeOptical(this);
       if(optical ! = isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets(); int opticalWidth = insets.left + insets.right; int opticalHeight = insets.top + insets.bottom; measuredWidth += optical ? opticalWidth : -opticalWidth; measuredHeight += optical ? opticalHeight : -opticalHeight; }setMeasuredDimensionRaw(measuredWidth, measuredHeight);
   }Copy the code

Set the width and height of the View. Let’s see what getDefaultSize() does:

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

MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec MeasureSpec class MeasureSpec class MeasureSpec class MeasureSpec class MeasureSpec class MeasureSpec class MeasureSpec class MeasureSpec class MeasureSpec class MeasureSpec class MeasureSpec class MeasureSpec class MeasureSpec class MeasureSpec class MeasureSpec class MeasureSpec class MeasureSpec class MeasureSpec class MeasureSpec class MeasureSpec

  • UNSPECIFIED: The mode is UNSPECIFIED, the View is UNSPECIFIED, the parent container is UNSPECIFIED, and is generally used for internal measurements.

  • AT_MOST: Maximum mode, corresponding to the WRap_comtent property, as long as the size does not exceed the maximum size allowed by the parent control.

  • EXACTLY: EXACTLY mode, corresponding to the match_parent property and specific value, the parent container measures the required size of the View, i.e., the value of specSize.

Looking back at the getDefaultSize() method, it is clear that both AT_MOST and EXACTLY modes return specSize, the size measured by the View, The UNSPECIFIED mode returns the value of the first parameter of the getDefaultSize() method, The first parameter from onMeasure () method is getSuggestedMinimumWidth () method and getSuggestedMinimumHeight (), Let’s see what the getSuggestedMinimumWidth() method did, and we just need to understand it because it works the same way:

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

Is clear, if the View does not set the background values as mMinWidth, if the View is set to the background in the value for Max (mMinWidth, mBackground getMinimumWidth ()), MMinWidth values and mBackground getMinimumWidth (), the maximum of mMinWidth can be set up, it corresponds to the android: the value of the attribute set or the View of setMinimumWidth minWidth value, If not specified, the default is 0, mBackground. GetMinimumWidth (), is this mBackground Drawable type, look at the Drawable class getMinimumWidth () method (Drawable. Java) :

public int getMinimumWidth() {
       final int intrinsicWidth = getIntrinsicWidth();
       return intrinsicWidth > 0 ? intrinsicWidth : 0;
   }Copy the code

IntrinsicWidth retrits the intrinsicWidth of the Drawable, returning the intrinsicWidth if greater than 0, otherwise 0. Suggestedminimumwidth () is used to return mMinWidth if the View has no background set, and the maximum mMinWidth and Drawable minimum width if the View has a background set. Suggestedminimumwidth ()

The measurement of ViewGroup

After talking about the measure process of a View, let’s look at the measure process of a ViewGroup. For a ViewGroup, it not only requires the measure itself, but also traverses the measure() method that calls its child elements. The ViewGroup does not define onMeasure(), but it does define measureChildren(), which can be called or not called when we implement onMeasure ourselves. This is a template. It is implemented in linear layout, relative layout, etc., which will be analyzed later. (ViewGroup. Java) :

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        if((child.mViewFlags & VISIBILITY_MASK) ! = GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } } protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }Copy the code

Simply traverse the child, call measureChild, and then internally send the child to measure, and you get to View. So what does getChildMeasureSpec() say here? Click to see:

//1. The parent View's measurespec //2. The size of the parent View that the child can't use //3. Public static int getChildChildMeasurespec (int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); Int size = math.max (0, specsie-padding); // Int size = math.max (0, specsie-padding); int resultSize = 0; int resultMode = 0; Switch (specMode) {// Parent has an exact size on us // The exact size of the Parent container can be determined by using the exact size of the Parent container.caseMeasureSpec.EXACTLY: // EXACTLY the exact size of the childif(childDimension >= 0) {resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; // The child is MATCH_PARENT}else if(childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; // Children are package contents}else if(childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can not be // bigger than Us. // The child is the package content, so the measurement mode of the child is AT_MOST, and size means the maximum possible size of the child, not the specific size of the child. resultMode = MeasureSpec.AT_MOST; }break; // if the Parent is AT_MOST, the size of the father is not determined, but the father cannot exceed a certain number, which is knowncaseMeasureSpec.AT_MOST: // The child is a specific number, the child is like the following, the exact pattern, the specific sizeif(childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; // The child is MATCH_PARENT, so the child is not exact, but the child can determine its maximum size, that is, the father's maximum size, mode is AT_MOST}else if(childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; // The size of the child is the content of the package, so the size of the child can not be determined, but the maximum size is known, can not exceed the maximum size of the father, as follows. }else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can not be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        // Parent asked to see how big we want to be
        caseMeasureSpec.UNSPECIFIED: // This casebreak;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }Copy the code

The parent container’s MeasureSpec schema is combined with the child element’s LayoutParams attribute to derive the child element’s MeasureSpec attribute.

LinearLayout measure process

Viewgroups do not provide onMeasure() methods, but let its subclasses implement their own measurement methods. The reason is that viewgroups have different layout needs that are difficult to be unified. OnMeasure (); onMeasure(); onMeasure(); onMeasure(); onMeasure(); onMeasure(); onMeasure();

@Override
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       if (mOrientation == VERTICAL) {
           measureVertical(widthMeasureSpec, heightMeasureSpec);
       } else{ measureHorizontal(widthMeasureSpec, heightMeasureSpec); }}Copy the code

The measureVertical() method is the same as the measureVertical() method.

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) { mTotalLength = 0; mTotalLength = 0; .for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
                mTotalLength += measureNullChild(i);
                continue;
            }
            if (child.getVisibility() == View.GONE) {
               i += getChildrenSkipCount(child, i);
               continue;
            }
            if (hasDividerBeforeChildAt(i)) {
                mTotalLength += mDividerHeight;
            }
            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
            totalWeight += lp.weight;

            if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
                // Optimization: don not bother measuring children who are going to use
                // leftover space. These views will get measured again down below if
                // there is any leftover space.
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true;
            } else {
                int oldHeight = Integer.MIN_VALUE;
                if (lp.height == 0 && lp.weight > 0) {
                    // heightMode is either UNSPECIFIED or AT_MOST, and this
                    // child wanted to stretch to fill available space.
                    // Translate that to WRAP_CONTENT so that it does not end up
                    // with a height of 0
                    oldHeight = 0;
                    lp.height = LayoutParams.WRAP_CONTENT;
                }
                // Determine how big this child would like to be. If this or
                // previous children have given a weight, then we allow it to
                // use all available space (and we will shrink things later
                // if needed).
                measureChildBeforeLayout(
                       child, i, widthMeasureSpec, 0, heightMeasureSpec,
                       totalWeight == 0 ? mTotalLength : 0);
                if(oldHeight ! = Integer.MIN_VALUE) { lp.height = oldHeight; } final int childHeight = child.getMeasuredHeight(); final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); .if (useLargestChild &&
                (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
            mTotalLength = 0;
            for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);
                if (child == null) {
                    mTotalLength += measureNullChild(i);
                    continue;
                }
                if (child.getVisibility() == GONE) {
                    i += getChildrenSkipCount(child, i);
                    continue;
                }
                final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
                        child.getLayoutParams();
                // Account for negative margins
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
            }
        }
        // Add in our padding
        mTotalLength += mPaddingTop + mPaddingBottom;
        int heightSize = mTotalLength;
        // Check against our minimum heightCopy the code

MTotalLength Is used to store the vertical height of the LinearLayout, and then iterates over the child elements. The height of each child element is calculated according to the MeasureSpec mode of the child elements. If it is wrAP_content, add the height of each child element and the vertical height of the margin and assign the value to mTotalLength to get the height of the entire LinearLayout. If the layout height is set to match_parent, the exact value is measured in the same way as in View.

Layout

Layout method is used to determine the position of View itself, in layout called onLayout method, this method has no specific implementation, need to subclass their own implementation, mainly in order to determine the location of the child View

public void layout(int l, int t, int r, int b) { int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; // Set its position Boolean changed =setFrame(l, t, r, b);
    if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
        if(ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT); OnLayout (changed, l, t, r, b); mPrivateFlags &= ~LAYOUT_REQUIRED;if(mOnLayoutChangeListeners ! = null) { ArrayList<OnLayoutChangeListener> listenersCopy = (ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners.clone(); int numListeners = listenersCopy.size();for (int i = 0; i < numListeners; ++i) {
                listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
            }
        }
    }
    mPrivateFlags &= ~FORCE_LAYOUT;
}Copy the code

So in the Linearlayout, onLayout is basically iterating through the child, and then calling setChildFrame, inside that method is calling the Child layout method, so we’re back to the previous step.

Draw

public void draw(Canvas canvas) {

    /*
     * Draw traversal performs several drawing steps which must be executed
     * in the appropriate order:
     *
     *      1. Draw the background
     *      2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content
     *      4. Draw children
     *      5. If necessary, draw the fading edges and restore layers
     *      6. Draw decorations (scrollbars for instance)
     */
    // Step 1, draw the background, if needed

    // skip step 2 & 5 if possible (common case) final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) ! = 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) ! = 0;if(! verticalEdges && ! horizontalEdges) { // Step 3, draw the contentif(! dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 6, draw decorations (scrollbars) onDrawScrollBars(canvas); // we're done... return; } // Step 2, save the canvas' layers

    // Step 3, draw the content
    if(! dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 5, draw the fade effect and restore layers // Step 6, draw decorations (scrollbars) onDrawScrollBars(canvas); }Copy the code

A View’s draw process consists of the following steps:

  • Draw the background

  • Draw the content

  • Draw a child

  • Painted decoration

Draw distributes drawings to children via dispatchDraw

One method is setWillNotDraw(), which allows you to set the current view not to draw, generally inheriting from the ViewGroup, and to make sure it doesn’t need to draw itself, set it to true, which can be optimized. The default is false.

The custom view

The custom view section is about the general considerations, not specific expansion. If you want to learn more about this, we strongly recommend the custom View series HenCoder: The advanced manual for senior Android engineers, if you have not seen you out, the great work of the heart, now seems to have started to prepare for the internationalization of the foreign, Kai Ge (throwing line) “attention I can reach the master level, this I finally dare to say” is not cover.

  • A custom View inherited from a View

    Handle wrap_parent in onMeasure and handle padding custom XML attributes in onDraw. File names do not have to be attrs. Remember to call recycle after retrieving data from a custom property. A custom View inherited from a ViewGroup

  • Call measureChildren in onMeasure (or write your own logic), and then analyze your own Measurespec. Finally, call setMeasuredDimension onLayout to traverse the child according to the measured width and height.

Here is a picture of HenCoder: an advanced manual for advanced Android engineers