View drawing process

preface

When learning Android, I have to deal with layout files. I always want to find out what the internal principle is, so I have this article.
This article forms a collection of articles with several great gods. It can be regarded as helping you simply filter the key information. I believe you can basically understand what the drawing process looks like after reading it. (it may be difficult to understand without Android foundation hhh)

View drawing process

The drawing process of View is divided into three steps: when customizing the View, you generally need to override the three methods of the parent class: onMeasure(), onLayout(), onDraw(), to complete the display process of the View. Of course, these three methods exposed to developers' rewriting are just the tip of the iceberg of the whole drawing process. The system is responsible for more complex behind the scenes work. A complete drawing process includes three steps: measure, layout and draw, including:

 measure: Measurement. The system will first xml The settings of the control properties in the layout file and code to obtain or calculate each View and ViewGrop And save them.

 layout: Layout. According to the measured results and corresponding parameters, determine the position where each control should be displayed.

 draw: draw. Once positioned, these controls are drawn onto the screen.

The three are implemented successively.
Layout involves two processes: measurement process and layout process. The measurement process is realized through the measure method. It is a top-down traversal of the View tree. Each View passes the size details down in the cycle. When the measurement process is completed, all views store their own sizes. The second process is realized through the method layout, which is also top-down. In this process, each parent View is responsible for placing its child View through the calculated size.

1. Important internal class MeasureSpec

MeasureSpec is a static class in View, which represents measurement rules, and its means is to implement it with an int value. We know that an int value has 32 bit s. MeasureSpec uses its upper 2 bits to represent the measurement Mode and the lower 30 bits to represent the value Size.

1.1. Example of measurement mode

Example understanding:

son View stay xml For the layout parameters in, the corresponding measurement modes are as follows:
wrap_content ---> MeasureSpec.AT_MOST
match_parent -> MeasureSpec.EXACTLY
 Specific value -> MeasureSpec.EXACTLY
 about UNSPECIFIED Mode, general View It will not be used. It may be used in scrolling components or lists.

1.2. How is the measurespec value calculated?

In fact, the MeasureSpec value of the child View is calculated based on the layout parameters of the child View and the MeasureSpec value of the parent container. The specific calculation logic is encapsulated in getChildMeasureSpec() of ViewGroup.
That is, the size of the child view is determined by the MeasureSpec value of the parent view and the LayoutParams attribute of the child view itself.

1.3. MeasureSpec class structure

public class MeasureSpec {
      // Carry size = 30th power of 2
      // The size of int is 32 bits, so carry 30 bits = use 32 and 31 bits of int as flag bits
      private static final int MODE_SHIFT = 30;
      // Operation mask: 0x3 is hexadecimal, hexadecimal is 3, binary is 11
      // 3 carry left 30 = 11 00000000000 (11 followed by 30 zeros)  
      // Function: use 1 to mark the required value and 0 to mark the unwanted value. Because 1 and any number will get any number, and 0 and any number will get 0
      private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
      // Mode setting of UNSPECIFIED: carry 0 to the left 30 = 00 followed by 30 zeros, i.e. 00 00000000000
      public static final int UNSPECIFIED = 0 << MODE_SHIFT;
      // Mode setting of effectively: 1 carry left 30 = 01 followed by 30 zeros, i.e. 01 00000000000
      public static final int EXACTLY = 1 << MODE_SHIFT;
      // AT_MOST mode setting: 2 carry left 30 = 10 followed by 30 zeros, i.e. 10 00000000000
      public static final int AT_MOST = 2 << MODE_SHIFT;

      /**
        * makeMeasureSpec()method
        * Function: get a detailed measurement result according to the provided size and mode, i.e. measureSpec
        **/
      public static int makeMeasureSpec(int size, int mode) { 
            // Design purpose: use a 32-bit binary number, where the 32nd and 31st bits represent the measurement mode and the last 30 bits represent the measurement size
            // ~Indicates bitwise inversion 
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
      }
      
      /**
        * getMode()method
        * Function: obtain the measurement mode through measureSpec
        **/    
      public static int getMode(int measureSpec) {  
          return (measureSpec & MODE_MASK);  
          // Namely: measurement mode = measurespec & Mode_ MASK;  
          // MODE_MASK = operation mask = 11 00000000000 (11 followed by 30 zeros)
          //Principle: keep the upper 2 bits of measureSpec (i.e. measurement mode) and replace the last 30 bits with 0
          // For example, 10 00 00100 & 11 00.. 00 (11 followed by 30 zeros) = 10 00 00 (at_most), so the value of mode is obtained
      }
      /**
        * getSize method
        * Function: obtain the measured size through measureSpec
        **/       
      public static int getSize(int measureSpec) {  
          return (measureSpec & ~MODE_MASK);  
          // size = measureSpec & ~MODE_MASK;  
          // The principle is similar to the above, that is, MODE_MASK is reversed, that is, it becomes 00 111111 (00 followed by 30 1). Replace 32 and 31 with 0, that is, remove the mode and retain the size of the last 30 bits  
      }
}

1.4. getChildMeasureSpec method content

/** 
  * Class of method: ViewGroup
  * Parameter description
  *
  * @param spec Detailed measured value of parent view (MeasureSpec) 
  * @param padding view The inside margin of the current dimension 
  * @param childDimension The size (width / height) of the sub View. If the sub View is not measured, this value is the layout parameter of the sub View. When the measurement is completed, it is the size of the sub View
  */
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
      //Measurement mode of parent view
      int specMode = MeasureSpec.getMode(spec);
      //Size of parent view
      int specSize = MeasureSpec.getSize(spec);
      //The child view calculated from the parent view = parent size - margin (the size required by the parent, but the child view does not necessarily use this value)   
      int size = Math.max(0, specSize - padding);
      
      //Actual size and mode of sub view (need to be calculated)  
      int resultSize = 0;
      int resultMode = 0;

      // When the mode of the parent View is efficiency, the parent View imposes the exact value on the child View
      //Generally, the parent View is set to match_parent or fixed value ViewGroup
      switch (specMode) {
            case MeasureSpec.EXACTLY:
                  // When the sub View measurement is completed, there is an exact value
                  // The child View size is the value assigned by the child itself, and the mode size is actual
                  if (childDimension >= 0) {
                        resultSize = childDimension;
                        resultMode = MeasureSpec.EXACTLY;
                  } else if (childDimension == LayoutParams.MATCH_PARENT) {
                        // Measurement not completed
                        // When the LayoutParams of the child View is MATCH_PARENT (-1)
                        //The size of the child view is the size of the parent view, and the mode is actual
                        resultSize = size;
                        resultMode = MeasureSpec.EXACTLY;
                  } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                        // Measurement not completed
                        // When the LayoutParams of the child view is WRAP_CONTENT (- 2)
                        // The child view determines its own size, but the maximum can not exceed the parent view. The mode is AT_MOST
                        resultSize = size;
                        resultMode = MeasureSpec.AT_MOST;
                  }
                  break;
            case MeasureSpec.AT_MOST:
                  // When the mode of the parent view is at_ When most, the parent view imposes a maximum value on the child view. (usually the parent view is set to wrap_content)
                  // The meaning of the code is the same as above
                  if (childDimension >= 0) {
                      resultSize = childDimension;  
                      resultMode = MeasureSpec.EXACTLY;  
                  } else if (childDimension == LayoutParams.MATCH_PARENT) {  
                      resultSize = size;  
                      resultMode = MeasureSpec.AT_MOST;  
                  } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                      resultSize = size;  
                      resultMode = MeasureSpec.AT_MOST;  
                  }  
                  break;
            case MeasureSpec.UNSPECIFIED:
                  // When the mode of the parent View is UNSPECIFIED, the parent container has no restrictions on the size of the View
                  // It is more common in ListView and GridView
                  if (childDimension >= 0) {  
                      // The child value assigned by the view itself  
                      resultSize = childDimension;  
                      resultMode = MeasureSpec.EXACTLY;  
                  } else if (childDimension == LayoutParams.MATCH_PARENT) {  
                      // Because the parent View is UNSPECIFIED, when the API is greater than 23, the hint value can be passed for measurement. See View At the assignment of susezerounspecified measurespec. Usually resultSize is 0
                      resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; 
                      resultMode = MeasureSpec.UNSPECIFIED;  
                  } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
                      // The description is the same as above
                      resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                      resultMode = MeasureSpec.UNSPECIFIED;  
                  }
                  break;  
      }
      return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

2. Structure of Android layout

2.1. Structural diagram

DecorView is the root container of an application Window, which is essentially a FrameLayout. DecorView has only one child View, which is a vertical LinearLayout and contains two child elements, one is TitleView (container of ActionBar) and the other is ContentView (container of Window content). As for the ContentView, it is a FrameLayout (android.R.id.content). The setContentView we usually use is to set its sub View. The above figure also shows that each Activity is associated with a Window (specifically PhoneWindow), and the user interface is carried by Window.

2.1.1 window introduction

Window is the window. The implementation of this concept in Android Framework is Android view. Window is an abstract class, which is an abstraction of windows in Android system. Window is a macro idea. It is a rectangular area on the screen used to draw various UI elements and respond to user input events.
This abstract class contains three core components:

  • WindowManager.LayoutParams: the layout parameters of the window;
  • Callback: callback interface of window, usually implemented by Activity;
  • ViewTree: the control tree hosted by the window.

2.1.2.PhoneWindow

The PhoneWindow class is the concrete implementation of the Android window provided by the Framework. When we normally call setContentView() method to set the user interface of Activity, we actually finish setting the ViewTree of the associated PhoneWindow. We can also customize the appearance of the PhoneWindow associated with the Activity through the requestWindowFeature() method of the Activity class. What this method actually does is to store the window appearance characteristics we requested into the mFeatures member of PhoneWindow. When the appearance template is generated in the window drawing stage, a specific appearance will be drawn according to the value of mFeatures.
Before introducing the View drawing process, let's briefly introduce the Android View hierarchy and DecorView, because the entry of View drawing process is closely related to DecorView.

The views we usually see actually have the above nested relationship. The above figure is made for the older version of Android system. There will be slight differences in the new version. There is also a status bar, but it has not changed on the whole. The corresponding layout content in setContentView(...) in Activity corresponds to the tree structure of ViewGrop in the above figure. In fact, when added to the system, it will be wrapped with a layer of FrameLayout, which is the innermost light blue part in the above figure.

2.1.3. Case analysis

Here, let's continue to check through another example. A layout inspector tool is provided in the Android studio tool. You can view the layout of a specific Activity through Tools > Android > layout inspector. In the following figure, the tree structure on the left corresponds to the visual view on the right. It can be seen that DecorView is the root view of the whole interface, and the red box on the right corresponds to the size of the whole screen. The yellow border is the part of the status bar; There are two parts in the green frame. One is the ActionBar in the white frame, which corresponds to the title ActionBar in the purple part in the figure above, that is, the title bar. Usually, we can hide it in the Activity; Another blue border part corresponds to the innermost blue part in the figure above, that is, the ContentView part.
In the following figure, there are two blue boxes on the left, and there is a "contain_layout" on the top, which is the layout set in setContentView in Activity The outermost parent layout in the XML layout file, which we can directly and completely control through the layout file, is this one. When it is added to the view system, it will be wrapped with ContentFrameLayout (obviously a subclass of FrameLayout) by the system, which is why we add layout The reason why the method of XML view is called setContentView(...) instead of setView(...).

3.View drawing origin

3.1.View draw UML sequence diagram


If you have a certain understanding of the Activity startup process, you should know that the startup process will be in activitythread It is completed in the Java class. During the process of starting the Activity, the handleResumeActivity(...) method will be called. The drawing process of the view starts from this method.
The whole call chain is shown in the figure below. The drawing process is not officially started until performTraversals() in the ViewRootImpl class, so this method is generally used as the source of formal drawing.

In the source code, PhoneWindow and DecorView are connected through combination, and DecorView is the root View of the whole View system. In the previous code snippet of handleResumeActivity(...) method, when the activity is started, you can indirectly call performTraversals() in the ViewRootImpl class through the addView method on line 14, so as to realize the drawing of the View.

3.2.setContentView

When we customize the Activity, it inherits from Android app. During Activity, the called setContentView() method is the Activity class, and the source code is as follows:

public void setContentView(@LayoutRes int layoutResID) {    
  getWindow().setContentView(layoutResID);    
  . . .
}

The getWindow() method will return the PhoneWindow associated with the Activity, that is, it actually calls the setContentView() method of PhoneWindow. The source code is as follows:

@Override
public void setContentView(int layoutResID) {
  if (mContentParent == null) {
    // mContentParent is the parent container of the ContentView mentioned above. If it is empty, it will be generated by calling installDecor()
    installDecor();
  } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
    // With feature_ CONTENT_ The transitions attribute indicates that Transition is enabled
    // If mContentParent is not null, all child views of decorView will be removed
    mContentParent.removeAllViews();
  }
  if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
    // Turn on Transition and handle it accordingly. We won't discuss this situation
    // Interested students can refer to the source code
    . . .
  } else {
    // Generally, you will come here and call mlayoutinflator The populate () method to populate the layout
    // Filling the layout is to add the ContentView we set to mContentParent
    mLayoutInflater.inflate(layoutResID, mContentParent);
  }
  . . .
  // cb is the Activity associated with the Window
  final Callback cb = getCallback();
  if (cb != null && !isDestroyed()) {
    // Call the onContentChanged() callback method to notify the Activity that the content of the window has changed
    cb.onContentChanged();
  }

  . . .
}

LayoutInflater.inflate()

Principle: in fact, the xml parser is used to parse the layout file. In this process, the createViewFromTag is used to create the view and the rinflatchildren are used to recursively fill the layout
In middle note, we see that the inflate() method of LayoutInflater is used to fill the layout in the setContentView() method of PhoneWindow. The source of the method is as follows:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
  return inflate(resource, root, root != null);
}

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
  final Resources res = getContext().getResources();
  . . .
  //Get xml parser
  final XmlResourceParser parser = res.getLayout(resource);
  try {
    return inflate(parser, root, attachToRoot);
  } finally {
    parser.close();
  }
}

In the setContentView() method of PhoneWindow, a decorView is passed in as layouteinflator As for the root parameter of inflate (), we can see that through layer by layer calls, the final call is the inflate(XmlPullParser, ViewGroup, boolean) method to fill the layout. The source code of this method is as follows:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
  synchronized (mConstructorArgs) {
    . . .
    final Context inflaterContext = mContext;
    final AttributeSet attrs = Xml.asAttributeSet(parser);
    Context lastContext = (Context) mConstructorArgs[0];
    mConstructorArgs[0] = inflaterContext;

    View result = root;

    try {
      // Look for the root node.
      int type;
      // The xml file is read until the start tag is encountered
      while ((type = parser.next()) != XmlPullParser.START_TAG &&
          type != XmlPullParser.END_DOCUMENT) {
        // Empty
       }
      // The first thing you encounter is not the start tag, but an error is reported
      if (type != XmlPullParser.START_TAG) {
        throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
      }

      final String name = parser.getName();
      . . .
      // Deal with < merge > tags separately. For unfamiliar students, please refer to the instructions in the official documents
      if (TAG_MERGE.equals(name)) {
        // If the < merge > tag is included, the parent container (i.e. root parameter) cannot be empty and attachRoot must be true, otherwise an error will be reported
        if (root == null || !attachToRoot) {
          throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
        }
        
        // Recursively populate the layout
        rInflate(parser, root, inflaterContext, attrs, false);
     } else {
        // temp is the root View of the xml layout file
        final View temp = createViewFromTag(root, name, inflaterContext, attrs); 
        ViewGroup.LayoutParams params = null;
        if (root != null) {
          . . .
          // Get the layout parameters of the parent container (LayoutParams)
          params = root.generateLayoutParams(attrs);
          if (!attachToRoot) {
            // If the attachToRoot parameter is false, we will only set the layout parameter of the parent container to the root View
            temp.setLayoutParams(params);
          }

        }

        // Recursively loads all child views of the root View
        rInflateChildren(parser, temp, attrs, true);
        . . .

        if (root != null && attachToRoot) {
          // If the parent container is not empty and attachToRoot is true, wrap the parent container as the parent View of the root View
          root.addView(temp, params);
        }
      
        // If root is empty or attachToRoot is false, the root View will be used as the return value
        if (root == null || !attachToRoot) {
           result = temp;
        }
      }

    } catch (XmlPullParserException e) {
      . . . 
    } catch (Exception e) {
      . . . 
    } finally {

      . . .
    }
    return result;
  }
}

In the above source code, first, handle the labels in the layout file separately, and call the rintlate () method to recursively fill the layout. The source code of this method is as follows:

void rInflate(XmlPullParser parser, View parent, Context context,
    AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
    // Gets the depth of the current tag. The depth of the root tag is 0
    final int depth = parser.getDepth();
    int type;
    while (((type = parser.next()) != XmlPullParser.END_TAG ||
        parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
      // If it is not the start mark, continue to the next iteration
      if (type != XmlPullParser.START_TAG) {
        continue;
      }
      final String name = parser.getName();
      // Some special marks shall be treated separately
      if (TAG_REQUEST_FOCUS.equals(name)) {
        parseRequestFocus(parser, parent);
      } else if (TAG_TAG.equals(name)) {
        parseViewTag(parser, parent, attrs);
      } else if (TAG_INCLUDE.equals(name)) {
        if (parser.getDepth() == 0) {
          throw new InflateException("<include /> cannot be the root element");
        }
        // Handle < include >
        parseInclude(parser, context, parent, attrs);
      } else if (TAG_MERGE.equals(name)) {
        throw new InflateException("<merge /> must be the root element");
      } else {
        // Handling of general marks
        final View view = createViewFromTag(parent, name, context, attrs);
        final ViewGroup viewGroup = (ViewGroup) parent;
        final ViewGroup.LayoutParams params=viewGroup.generateLayoutParams(attrs);
        // Load child views recursively
        rInflateChildren(parser, view, attrs, true);
        viewGroup.addView(view, params);
      }
    }

    if (finishInflate) {
        parent.onFinishInflate();
    }
}

As we can see, both the above inflate() and rinfflate () methods call the rinfflatechildren () method. The source code of this method is as follows:

final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
    rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}

From the source code, we can know that the rinflatchildren () method actually calls the rInflate() method. So far, we have analyzed the overall execution process of setContentView(). So far, we have completed the creation and setting of ContentView of Activity. Next, we begin to get to the point and analyze the drawing process of View.

3.3.performTraversals

After the association between decorView and ViewRoot is established, the requestLayout() method of ViewRoot class will be called to complete the initial layout of the application user interface. What is actually called is the requestLayout() method of ViewRootImpl class. The source code of this method is as follows:

@Override
public void requestLayout() {
  if (!mHandlingLayoutInLayoutRequest) {
    // Check whether the thread initiating the layout request is the main thread  
    checkThread();
    mLayoutRequested = true;
    scheduleTraversals();
  }
}

In the middle note, the scheduleTraversals() method is called to schedule the completed rendering process. This method sends an "traversal" message to the main thread, which will eventually cause the performTraversals () method of ViewRootImpl to be called. Next, let's start with performTraversals() to analyze the whole drawing process of View.

// =====================ViewRootImpl.java=================
private void performTraversals() {
   ......
   int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
   int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
   ......
   // Ask host how big it wants to be
   performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
   ......
   performLayout(lp, mWidth, mHeight);
   ......
   performDraw();
}

The source code of measure () is as follows:

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
  . . .
  try { 
  //mView is decorview
    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  } finally {
    . . .
  }
}

View.measure

The source code of this method is as follows:

/**
 * Call this method to calculate how big a View should be. The parameter is the constraint information of the parent View on its width and height.
 * The actual measurement is done in the onMeasure() method
 */
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
  . . . 
  // Determine whether to rearrange

  // If mPrivateFlags contains PFLAG_FORCE_LAYOUT tag, then force the re layout
  // For example, call view Requestlayout() will add this tag to mPrivateFlags
  final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
  final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
      || heightMeasureSpec != mOldHeightMeasureSpec;
  final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
      && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
  final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
      && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
  final boolean needsLayout = specChanged
      && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);

  // Need to rearrange  
  if (forceLayout || needsLayout) {
    . . .
    // Try to get from the cache first. If forceLayout is true or does not exist in the cache or
    // If the cache is ignored, onMeasure() is called to perform the measurement again
    int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
    if (cacheIndex < 0 || sIgnoreMeasureCache) {
      // measure ourselves, this should set the measured dimension flag back
      onMeasure(widthMeasureSpec, heightMeasureSpec);
      . . .
    } else {
      // Cache hits can be directly taken from the cache without further measurement
      long value = mMeasureCache.valueAt(cacheIndex);
      // Casting a long to int drops the high 32 bits, no mask needed
      setMeasuredDimensionRaw((int) (value >> 32), (int) value);
      . . .
    }
    . . .
  }
  mOldWidthMeasureSpec = widthMeasureSpec;
  mOldHeightMeasureSpec = heightMeasureSpec;
  mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
      (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
1)This method is called to find out view How old should it be. Parent layout in witdh and height Restriction information is provided in the parameter;
2)One view The actual measurement work is called by this method onMeasure(int,int)Method. So, only onMeasure(int,int)Can and must be overridden by subclasses, ViewGroup Subclasses of must override this method to draw subclasses within this container view. If you are customizing a child control, extends View,Then you don't have to rewrite the method);
3)parameter widthMeasureSpec: Horizontal space requirements added by parent layout;
4)parameter heightMeasureSpec: Vertical space requirements added by the parent layout.
The system defines it as a final Method, it can be seen that the system does not want the whole measurement process framework to be modified.

ViewGroup.onMeasure() method (usually our custom view needs to be rewritten)

For decorView, the onMeasure() method of FrameLayout actually performs measurement. The source code of this method is as follows:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  int count = getChildCount();
  . . .
  int maxHeight = 0;
  int maxWidth = 0;

  int childState = 0;
  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());

      . . .
    }
  }

  // Account for padding too
  maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
  maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

  // Check against our minimum height and width
  maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
  maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

  // Check against our foreground's minimum height and width
  final Drawable drawable = getForeground();
  if (drawable != null) {
    maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
    maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
  }

  setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
        resolveSizeAndState(maxHeight, heightMeasureSpec,
        childState << MEASURED_HEIGHT_STATE_SHIFT));
  . . . 
}

FrameLayout is a subclass of ViewGroup, which has a member variable mChildren of View [] type, representing its child View collection. getChildAt(i) can get the sub views at the specified index, and getChildCount() can get the total number of sub views.
In the above source code, first call the measureChildWithMargins() method to measure all sub views and calculate the maximum width and height of all sub views. Then add padding to the maximum height and width obtained. The padding here includes the padding of the parent View and the padding of the foreground area. Then it will check whether the minimum width and height is set and compare with it, and set the larger of the two as the final maximum width and height. Finally, if the foreground image is set, we also need to check the minimum width and height of the foreground image.
After the above series of steps, we get the final values of maxHeight and maxWidth, indicating that the current container View can normally display all its child views with this size (considering padding and margin at the same time). Then we need to call the resolveSizeAndState() method to obtain the final measured width and height in combination with the passed MeasureSpec, and save it to the member variables of mMeasuredWidth and mMeasuredHeight.
From the execution process of the above code, we can see that the container View can get its own measurement results only after it measures all child views through the measureChildWithMargins() method. In other words, for ViewGroup and its subclasses, the measurement of sub views should be completed first, and then their own measurement (taking into account padding, etc.).

ViewGroup.measureChildWithMargins

Next, let's look at the implementation of measureChildWithMargins() method of ViewGroup:

protected void measureChildWithMargins(View child,
  int parentWidthMeasureSpec, int widthUsed,
  int parentHeightMeasureSpec, int heightUsed) {
  final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
  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);

  child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

For ViewGroup, it calls child Measure() to complete the measurement of the child View. The MeasureSpec passed into ViewGroup is used by its parent View to restrict its measurement, so ViewGroup itself needs to generate a childMeasureSpec to limit the measurement of its child views. This childMeasureSpec is generated by the getChildMeasureSpec() method. Next, let's analyze this method:

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
  // spec is the MeasureSpec of the parent View
  // Padding is the used size of the parent View in the corresponding direction plus the padding of the parent View and the margin of the child View
  // childDimension is the value of LayoutParams of the child View
  int specMode = MeasureSpec.getMode(spec);
  int specSize = MeasureSpec.getSize(spec);

  // Now the value of size is the available size in the corresponding direction of the parent View
  int size = Math.max(0, specSize - padding);

  int resultSize = 0;
  int resultMode = 0;

  switch (specMode) {
    // Parent has imposed an exact size on us
    case MeasureSpec.EXACTLY:
      if (childDimension >= 0) {
        // The LayoutParams representing the child View specifies the specific size value (xx dp)
        resultSize = childDimension;
        resultMode = MeasureSpec.EXACTLY;
      } else if (childDimension == LayoutParams.MATCH_PARENT) {
        // The child View wants to be as big as the parent View
        resultSize = size;
        resultMode = MeasureSpec.EXACTLY;
      } else if (childDimension == LayoutParams.WRAP_CONTENT) {
        // The child View wants to determine its size by itself, but it cannot be larger than the parent View 
        resultSize = size;
        resultMode = MeasureSpec.AT_MOST;
      }
      break;

    // Parent has imposed a maximum size on us
    case MeasureSpec.AT_MOST:
      if (childDimension >= 0) {
        // The child View specifies the specific size
        resultSize = childDimension;
        resultMode = MeasureSpec.EXACTLY;
      } else if (childDimension == LayoutParams.MATCH_PARENT) {
        // The child View wants to be as large as the parent View, but the size of the parent View is not fixed
        // Therefore, the specified constraint child View cannot be larger than the parent View
        resultSize = size;
        resultMode = MeasureSpec.AT_MOST;
      } else if (childDimension == LayoutParams.WRAP_CONTENT) {
        // The child View wants to determine its own size, but it cannot be larger than the parent View
        resultSize = size;
        resultMode = MeasureSpec.AT_MOST;
      }
      break;

      . . .
  }

  //noinspection ResourceType
  return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

The above method shows the process of generating the MeasureSpec of the child View according to the MeasureSpec of the parent View and the LayoutParams of the child View, * * the LayoutParams of the child View represents the expected size of the child View * *. The generated MeasureSpec is used to guide the determination of the measurement results of the sub View itself.
In the measureChildWithMargins() method, after obtaining the MeasureSpec that knows the measurement of the child View, the next step is to call child Measure () method and pass in the obtained childMeasureSpec. At this time, the onMeasure() method will be called again. If the child View at this time is a subclass of ViewGroup, the onMeasure() method of the corresponding container class will be called. The execution process of the onMeasure() method of other container views is similar to that of FrameLayout.

View.onMeasure()

For an ordinary View, the onMeasure() method of the View class will be called for actual measurement. The source code of this method is as follows:

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

For ordinary View (non ViewgGroup), you only need to complete your own measurement. In the above code, the measurement result is set through the setMeasuredDimension() method. Specifically, the return value of the getdefaultsize () method is used as the measurement result. The source code of getDefaultSize() method is as follows:

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;
}

From the above code, we can see that the getDefaultSize() method of View is effective for at_ SpecSize is returned as result in both most and actual cases. Therefore, if our custom View directly inherits the View class, we need to wrap it ourselves_ Content (corresponding to AT_MOST). Otherwise, wrap is specified for the custom View_ Content and match_ The parent effect is the same.

4.layout

The basic idea of the layout stage is to start from the root View and recursively complete the layout of the whole control tree.

View.layout()

We take the call to the layout() method of decorView as the starting point for the layout of the whole control tree. In fact, we call the layout() method of View class. The source code is as follows:

public void layout(int l, int t, int r, int b) {
    // l is the distance between the left edge of this View and the left edge of the parent View
    // t is the distance between the upper edge of this View and the upper edge of the parent View
    // r is the distance between the right edge of this View and the left edge of the parent View
    // b is the distance between the lower edge of this View and the upper edge of the parent View
    . . .
    boolean changed = isLayoutModeOptical(mParent) ?            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
        . . .
            
    }
    . . .
}

This method will call the setFrame() method to set the four parameters mLeft, mTop, mRight and mBottom of the View. These four parameters describe the position of the View relative to its parent View (assigned as l, t, R and B respectively). In the setFrame() method, it will judge whether the position of the View has changed. If it has changed, it is necessary to rearrange the child View. The layout of the child View is realized through the onlayout () method. Since the normal View (non ViewGroup) does not contain child views, the onLayout() method of the View class is empty. So next, let's look at the implementation of the onlayout () method of the ViewGroup class.
ViewGroup.onLayout()
In fact, the onLayout() method of the ViewGroup class is abstract because different layout managers have different layout methods.
Here, we take decorView, the onLayout() method of FrameLayout, as an example to analyze the layout process of ViewGroup:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}

void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
  final int count = getChildCount();
  final int parentLeft = getPaddingLeftWithForeground();
  final int parentRight = right - left - getPaddingRightWithForeground();
  final int parentTop = getPaddingTopWithForeground();
  final int parentBottom = bottom - top - getPaddingBottomWithForeground();

  for (int i = 0; i < count; i++) {
    final View child = getChildAt(i);
    if (child.getVisibility() != GONE) {
      final LayoutParams lp = (LayoutParams) child.getLayoutParams();
      final int width = child.getMeasuredWidth();
      final int height = child.getMeasuredHeight();
      int childLeft;
      int childTop;
      int gravity = lp.gravity;

      if (gravity == -1) {
        gravity = DEFAULT_CHILD_GRAVITY;
      }

      final int layoutDirection = getLayoutDirection();
      final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
      final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

      switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
        case Gravity.CENTER_HORIZONTAL:
          childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
          break;

        case Gravity.RIGHT:
          if (!forceLeftGravity) {
            childLeft = parentRight - width - lp.rightMargin;
            break;
          }

        case Gravity.LEFT:
        default:
          childLeft = parentLeft + lp.leftMargin;

      }

      switch (verticalGravity) {
        case Gravity.TOP:
          childTop = parentTop + lp.topMargin;
          break;

        case Gravity.CENTER_VERTICAL:
          childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
          break;

        case Gravity.BOTTOM:
          childTop = parentBottom - height - lp.bottomMargin;
          break;

        default:
          childTop = parentTop + lp.topMargin;
      }
      child.layout(childLeft, childTop, childLeft + width, childTop + height);
    }
  }
}

In the above method, parentLeft represents a left boundary specified by the current View for its child View display area, that is, the distance from the left edge of the child View display area to the left edge of the parent View. parentRight, parentTop and parentBottom have the same meaning. After determining the display area of the sub View, next, a for loop is used to complete the layout of the sub View.
Only when you ensure that the visibility of the child View is not GONE can you layout it. First, a series of parameters such as LayoutParams and layoutDirection of the child View will be obtained. childLeft in the above code represents the distance between the left edge of the final child View and the left edge of the parent View, and childTop represents the distance between the upper edge of the child View and the upper edge of the parent View. According to the layout of the sub View_ The value of gravity makes different adjustments to childLeft and childTop. Finally, it calls child The layout () method sets the position parameters of the child View, and then goes to View For the call of layout () method, if the child View is a container View, it will layout its child View recursively.
Here, we have analyzed the general process of the layout stage. This stage is mainly to determine the final display position of the View according to the measured width and height of the View obtained in the previous stage. Obviously, after the measure phase and layout phase, we have determined the size and position of the View, and then we can start drawing the View.

5.draw

For the analysis at this stage, we take decorview Draw () is the starting point of analysis, that is, view The source code of the draw () method is as follows:

public void draw(Canvas canvas) {
  . . . 
  // Draw the background only when dirtyOpaque is false, the same below
  int saveCount;
  if (!dirtyOpaque) {
    drawBackground(canvas);
  }

  . . . 

  // Draw your own content
  if (!dirtyOpaque) onDraw(canvas);

  // Draw subview
  dispatchDraw(canvas);

   . . .
  // Draw scroll bars, etc
  onDrawForeground(canvas);

}

In the dispatchDraw() method of the View Group class, the drawChild() method will be called successively to draw sub views. The source code of the drawChild() method is as follows:

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
  return child.draw(canvas, this, drawingTime);
}

This method calls View draw(Canvas, ViewGroup, long) method to draw sub views. In the draw(Canvas, ViewGroup, long) method, firstly, a series of transformations are carried out on the canvas to transform it to the coordinate system of the View to be drawn. After the transformation of canvas is completed, View. Is called The draw (canvas) method performs the actual drawing work. At this time, the incoming canvas is the transformed canvas in the coordinate system of the View to be drawn.
Enter view After the draw (canvas) method, the following steps will be performed as described earlier:

  • Draw the background;
  • Draw your own content through onDraw();
  • Draw sub views through dispatchDraw();
  • Draw scroll bar

reference

  1. [morning and evening] Android custom View part (I) View drawing process - Song Dynasty is the king - blog Garden
  2. Drawing process of android view_ Android_ Verus blog - CSDN blog_ android view drawing process
  3. Deeply understand the drawing process of Android View

Tags: Android

Posted by neex1233 on Thu, 14 Apr 2022 20:57:25 +0930