Three major processes of Android View drawing

Three major processes of Android View drawing

introduce

The workflow of View mainly refers to the three major processes of measure, layout and draw, i.e., measure determines the measured width and height of View, layout determines the positions of the four vertices of View in its parent View according to the measured width and height, and draw draws View to the screen, so that through the recursive traversal of ViewGroup, a View tree is displayed on the screen. It is simple to say, let's analyze it step by step from the source code:

Android's View is a tree structure:

Basic Concepts

Before introducing the three major processes of View, we must first introduce some basic concepts to better understand the whole process.

The concept of Window

Window represents the concept of a window, which is an abstract concept from the perspective of WindowManagerService. All views in Android are presented through Windows, whether it is Activity, Dialog or Toast, as long as there is a View, there must be a Window.

It should be noted here that this abstract Window concept is not the same as the PhoneWindow class. PhoneWindow represents the abstraction of the mobile phone screen. It acts as a medium between Activity and DecorView. Even if there is no PhoneWindow, View can be displayed.

Putting everything aside, from the perspective of WindowManagerService alone, the Android interface is displayed by stacking windows, and Window is an abstract concept. It does not actually exist. It exists in the form of View, and this View is DecorView.

Regarding Window, let's first understand a general

Concept of DecorView

DecorView is the top-level View of the entire Window interface. The measurement, layout, drawing, and event distribution of the View are all done by DecorView traversing the View tree downwards. As the top-level View, DecorView generally contains a vertical LinearLayout inside. There are two parts in this LinearLayout (the specific situation depends on the Android version and theme), the upper part is the [title bar], and the lower part is the [content bar]. In the Activity, the layout file we set through setContentView is actually loaded into the [content bar], and the id of the content bar is content, so the method to specify the layout is called setContent().

The concept of ViewRoot

ViewRoot corresponds to the ViewRootImpl class, which is the link between WindowManager and DecorView. The three major processes of View are all completed through ViewRoot. In ActivityThread, when the Activity object is created, DecorView will be added to Window, and the corresponding ViewRootImpl will be created at the same time, and the ViewRootImpl and DecorView will be associated and saved in the WindowManagerGlobal object.

  1. WindowManagerGlobal.java
  2.  
  3. root = new ViewRootImpl( view .getContext(), display);
  4. root.setView( view , wparams, panelParentView);

Java

The drawing process of View starts from the performTraversals method of ViewRoot. It goes through three processes: measure, layout, and draw to finally draw a View. The general process is as follows:

Measure

In order to better understand the measurement process of View, we also need to understand MeasureSpec, which is an internal class of View and represents the measurement specifications of View. MeasureSpec represents a 32-bit int value, the upper 2 bits represent SpecMode (measurement mode), and the lower 30 bits represent SpecSize (measurement size). Let's take a look at its specific implementation:

  1. MeasureSpec.java
  2.  
  3. public   static class MeasureSpec {
  4. private static final int MODE_SHIFT = 30;
  5. private static final int MODE_MASK = 0x3 << MODE_SHIFT;
  6.  
  7. /**
  8. * UNSPECIFIED mode:
  9. * The parent View does not have any restrictions on the child View , the child View can be as large as needed
  10. */
  11. public   static final int UNSPECIFIED = 0 << MODE_SHIFT;
  12.  
  13. /**
  14. * EXACTYLY mode:
  15. * The parent View has measured the exact size required by the child View, and the final size of the View is now
  16. * is the value specified by SpecSize. Corresponds to the two modes of match_parent and exact value
  17. */
  18. public   static final int EXACTLY = 1 << MODE_SHIFT;
  19.  
  20. /**
  21. * AT_MOST mode:
  22. * The final size of the child View is the SpecSize value specified by the parent View , and the size of the child View cannot be larger than this value.
  23. * This corresponds to the wrap_content mode
  24. */
  25. public   static final int AT_MOST = 2 << MODE_SHIFT;
  26.  
  27. //Pack size and mode into a 32-bit int value
  28. //The upper 2 bits represent SpecMode, the measurement mode, and the lower 30 bits represent SpecSize, the specification size under a certain measurement mode
  29. public   static   int makeMeasureSpec( int   size , int mode) {
  30. if (sUseBrokenMakeMeasureSpec) {
  31. return   size + mode;
  32. } else {
  33. return ( size & ~MODE_MASK) | (mode & MODE_MASK);
  34. }
  35. }
  36.  
  37. // Unpack the 32-bit MeasureSpec and return SpecMode, measurement mode
  38. public   static   int getMode( int measureSpec) {
  39. return (measureSpec & MODE_MASK);
  40. }
  41.  
  42. // Unpack the 32-bit MeasureSpec and return SpecSize, the specification size in a certain measurement mode
  43. public   static   int getSize( int measureSpec) {
  44. return (measureSpec & ~MODE_MASK);
  45. }
  46. //...
  47. }

Java

MeasureSpec avoids excessive object memory allocation by packing SpecMode and SpecSize into an int value and provides packing and unpacking methods.

There are three types of SpecMode, each with a special meaning:

UNSPECIFIED

The parent container does not impose any restrictions on the View. It can be as large as required. This situation is generally used within the system to indicate a measurement state.

EXACTLY

The parent container has detected the exact size required by the View, and the final size of the View is the value specified by SpecSize. It corresponds to the two modes of match_parent and specific values ​​in LayoutParams.

AT_MOST

The parent container specifies an available size, SpecSize, and the size of the View cannot be larger than this value. The specific value depends on the specific implementation of different Views. It corresponds to wrap_content in LayoutParams.

The MeasureSpec of a View is determined by the MeasureSpec of its parent container and its own LayoutParams, but it is a little different for DecorView because it has no parent class. The following code in the measureHierarchy method in ViewRootImpl shows the creation process of DecorView's MeasureSpec, where desiredWindowWidth and desireWindowHeight are the screen sizes:

ViewGroup measure

  1. childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
  2.  
  3. childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
  4.  
  5. performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

Java

Let's take a look at the getRootMeasureSpec method:

  1. private static   int getRootMeasureSpec( int windowSize, int rootDimension) {
  2. int measureSpec;
  3. switch (rootDimension) {
  4.  
  5. case ViewGroup.LayoutParams.MATCH_PARENT:
  6. // Window can't resize. Force root view   to be windowSize.
  7. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
  8. break;
  9. case ViewGroup.LayoutParams.WRAP_CONTENT:
  10. // Window can resize. Set   max   size   for root view .
  11. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
  12. break;
  13. default :
  14. // Window wants to be an exact size . Force root view   to be that size .
  15. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
  16. break;
  17. }
  18. return measureSpec;
  19. }

Java

Through the above code, the generation process of DecorView's MeasureSpec is very clear, because DecorView is a subclass of FrameLyaout and belongs to ViewGroup. For ViewGroup, in addition to completing its own measure process, it will also traverse and call the measure method of all child elements, and each child element will recursively execute this process. Unlike View, ViewGroup is an abstract class, and it does not override View's onMeasure method. This is easy to understand, because each specific ViewGroup implementation class has different functions, and how to measure should be decided by itself, such as LinearLayout and RelativeLayout.

Therefore, it is necessary to traverse and measure the child View in the specific ViewGroup. Here we look at the measureChildWithMargins method provided in ViewGroup to measure the child View:

  1. protected void measureChildWithMargins( View child,
  2. int parentWidthMeasureSpec, int widthUsed,
  3. int parentHeightMeasureSpec, int heightUsed) {
  4. final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
  5.  
  6. final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
  7. mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
  8. + widthUsed, lp.width);
  9. final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
  10. mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
  11. + heightUsed, lp.height);
  12.  
  13. child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  14. }

Java

The above method will measure the child element. Before calling the child element's measure method, the child element's MeasureSpec will be obtained through the getChildMeasureSpec method. From the code, the creation of the child element's MeasureSpec is related to the parent container's MeasureSpec and its own LayoutParams, as well as the View's margin and the parent class's padding. Now let's take a look at the specific implementation of getChildMeasureSpec:

  1. ViewGroup.java
  2.  
  3. public   static   int getChildMeasureSpec( int spec, int padding, int childDimension) {
  4. int specMode = MeasureSpec.getMode(spec);
  5. int specSize = MeasureSpec.getSize(spec);
  6.  
  7. int   size = Math. max (0, specSize - padding);
  8.  
  9. int resultSize = 0;
  10. int resultMode = 0;
  11.  
  12. switch (specMode) {
  13. // Parent has imposed an exact size   on us
  14. case MeasureSpec.EXACTLY:
  15. if (childDimension >= 0) {
  16. resultSize = childDimension;
  17. resultMode = MeasureSpec.EXACTLY;
  18. } else if (childDimension == LayoutParams.MATCH_PARENT) {
  19. // Child wants to be our size . So be it.
  20. resultSize = size ;
  21. resultMode = MeasureSpec.EXACTLY;
  22. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  23. // Child wants to determine its own size . It can't be
  24. // bigger than us.
  25. resultSize = size ;
  26. resultMode = MeasureSpec.AT_MOST;
  27. }
  28. break;
  29.  
  30. // Parent has imposed a maximum size   on us
  31. case MeasureSpec.AT_MOST:
  32. if (childDimension >= 0) {
  33. // Child wants a specific size ... so be it
  34. resultSize = childDimension;
  35. resultMode = MeasureSpec.EXACTLY;
  36. } else if (childDimension == LayoutParams.MATCH_PARENT) {
  37. // Child wants to be our size , but our size   is   not fixed.
  38. // Constrain child to   not be bigger than us.
  39. resultSize = size ;
  40. resultMode = MeasureSpec.AT_MOST;
  41. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  42. // Child wants to determine its own size . It can't be
  43. // bigger than us.
  44. resultSize = size ;
  45. resultMode = MeasureSpec.AT_MOST;
  46. }
  47. break;
  48.  
  49. // Parent asked to see how big we want to be
  50. case MeasureSpec.UNSPECIFIED:
  51. if (childDimension >= 0) {
  52. // Child wants a specific size ... let him have it
  53. resultSize = childDimension;
  54. resultMode = MeasureSpec.EXACTLY;
  55. } else if (childDimension == LayoutParams.MATCH_PARENT) {
  56. // Child wants to be our size ... find out how big it should
  57. // be
  58. resultSize = View .sUseZeroUnspecifiedMeasureSpec ? 0 : size ;
  59. resultMode = MeasureSpec.UNSPECIFIED;
  60. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  61. // Child wants to determine its own size .... find out how
  62. // big it should be
  63. resultSize = View .sUseZeroUnspecifiedMeasureSpec ? 0 : size ;
  64. resultMode = MeasureSpec.UNSPECIFIED;
  65. }
  66. break;
  67. }
  68. //noinspection ResourceType
  69. return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
  70. }

Java

The above code creates the MeasureSpec of the child element based on the MeasureSpec of the parent class and its own LayoutParams. The specific process is analyzed by the students themselves. The final creation rules are as follows:

After traversing the child Views, ViewGroup needs to determine its final measured size based on the measurement results of the child elements and call the setMeasuredDimension method to save the measured width and height values.

  1. setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),heightSizeAndState);

Java

resolveSizeAndState is called here to determine the final size, mainly to ensure that the measured size does not exceed the maximum remaining space maxWidth of the parent container. Here we look at its implementation:

  1. public   static   int resolveSizeAndState( int   size , int measureSpec, int childMeasuredState) {
  2. final int specMode = MeasureSpec.getMode(measureSpec);
  3. final int specSize = MeasureSpec.getSize(measureSpec);
  4. final int result;
  5. switch (specMode) {
  6. case MeasureSpec.AT_MOST:
  7. if (specSize < size ) {
  8. result = specSize | MEASURED_STATE_TOO_SMALL;
  9. } else {
  10. result = size ;
  11. }
  12. break;
  13. case MeasureSpec.EXACTLY:
  14. result = specSize;
  15. break;
  16. case MeasureSpec.UNSPECIFIED:
  17. default :
  18. result = size ;
  19. }
  20. return result | (childMeasuredState & MEASURED_STATE_MASK);
  21. }

Java

The onMeasure process of specific ViewGroup is not analyzed here. Since the measurement method of each layout is different, it is impossible to analyze them one by one, but the steps in their onMeasure are regular:

1. Traverse the Children elements according to their respective measurement rules and call the getChildMeasureSpec method to get the measureSpec of the Child;

2. Call Child's measure method;

3. Call setMeasuredDimension to determine the final size.

View measure

The measure process of View is completed by its measure method. The measure method is a final type method, which means that subclasses cannot override this method. The onMeasure method will be called in the measure method of View. Here we only need to look at the implementation of onMeasure, as follows:

  1. View.java
  2.  
  3. protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) {
  4. setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
  5. getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
  6. }

Java

The code is very simple. Let's continue to look at the implementation of the getDefaultSize method:

  1. View.java
  2.  
  3. public   static   int getDefaultSize( int   size , int measureSpec) {
  4. int result = size ;
  5. int specMode = MeasureSpec.getMode(measureSpec);
  6. int specSize = MeasureSpec.getSize(measureSpec);
  7.  
  8. switch (specMode) {
  9. case MeasureSpec.UNSPECIFIED:
  10. result = size ;
  11. break;
  12. case MeasureSpec.AT_MOST:
  13. case MeasureSpec.EXACTLY:
  14. result = specSize;
  15. break;
  16. }
  17. return result;
  18. }

Java

From the above code, we can conclude that the width/height of View is determined by specSize. Custom controls that directly inherit View need to override the onMeasure method and set their own size when wrap_content is used. Otherwise, using wrap_content in the layout is equivalent to using match_parent.

The above is the general process of View measurement. After the measurement is completed, the measured width and height can be obtained through the getMeasuredWidth/Height method. This width and height is generally equal to the final width and height of the View, because the width and height of the View are set according to measureWidth/Height when the View is laid out, unless the measure value is modified in the layout.

Layout

The role of Layout is that ViewGroup is used to determine the position of child elements. When the position of ViewGroup is determined, it will traverse all child elements in onLayout and call their layout methods. In simple terms, the layout method determines the position of the View itself, while the onLayout method determines the positions of all child elements.

Let's first look at the layout method of View:

  1. public void layout( int l, int t, int r, int b) {
  2. if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
  3. onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
  4. mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
  5. }
  6.  
  7. int oldL = mLeft;
  8. int oldT = mTop;
  9. int oldB = mBottom;
  10. int oldR = mRight;
  11.  
  12. boolean changed = isLayoutModeOptical(mParent) ?
  13. setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
  14.  
  15. if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
  16. onLayout(changed, l, t, r, b);
  17.  
  18. if (shouldDrawRoundScrollbar()) {
  19. if(mRoundScrollbarRenderer == null ) {
  20. mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
  21. }
  22. } else {
  23. mRoundScrollbarRenderer = null ;
  24. }
  25.  
  26. mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
  27.  
  28. ListenerInfo li = mListenerInfo;
  29. if (li != null && li.mOnLayoutChangeListeners != null ) {
  30. ArrayList<OnLayoutChangeListener> listenersCopy =
  31. (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
  32. int numListeners = listenersCopy. size ();
  33. for ( int i = 0; i < numListeners; ++i) {
  34. listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
  35. }
  36. }
  37. }
  38.  
  39. mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
  40. mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
  41. }

Due to WeChat word limit, please click the original link to view the full content

Summarize

At this point, the three major processes of View measure, layout, and draw have been discussed. Here is a summary:

  • If it is a custom ViewGroup, you need to rewrite the onMeasure method, traverse and measure the child elements in the onMeasure method, and the same is true for the onLayout method. Finally, implement the onDraw method to draw yourself;
  • If you customize the View, you need to write the onMeasure method to handle the wrap_content situation, and you don't need to handle onLayout. Finally, implement the onDraw method to draw yourself.

<<:  Why are you forced to insert ads? Talking about HTTPS connections

>>:  Apple's 3D recognition is two years ahead of Qualcomm, but Android manufacturers are not interested in mobile phone 3D

Recommend

Zhihu's "Wilson Formula" recommendation algorithm!

If operators or creators want their content to at...

Domestic companies fined for infringing BMW trademark rights

According to a recent report by Shanghai Daily, t...

What can UCloud do to accelerate Chinese companies’ overseas expansion?

The original intention of globalization strategy ...

Is God a programmer? Humans just live in code

"Mental patients are all different and come ...

A 56% plunge in China's mobile phone market in February

In response to the epidemic, the Chinese mobile p...

The top ten marketing failures in 2019!

Think back, you must still remember several parti...

How is the "big belly" of Tianzhou-6 different from the previous ones?

At 9:00 a.m. on May 30, the Shenzhou 16 manned sp...

How should an APP build a user growth system? Share 6 points!

Just as KOL corresponds to the herd effect in psy...