SoFunction
Updated on 2025-04-07

Detailed explanation of Android UI drawing process and principles

1. Draw the source code path of the process

1. Activity loads ViewRootImpl

() 
--> (decorView, layoutParams) 
--> ()

2. ViewRootImpl starts the traversal of the View tree

(decorView, layoutParams, parentView)
-->()
-->scheduleTraversals()
-->()
-->doTraversal()
-->performTraversals()(performMeasure、performLayout、performDraw)

2. View drawing process

1、measure

(1) What is MeasureSpec?

After rewriting the onMeasure() method, you know that measurement requires the MeasureSpec class to obtain the measurement mode and size of the View. So how does this class store these two information?

If you observe carefully, you will find that the two parameters of the onMeasure method are actually 32-bit int type data, namely:

00 000000 00000000 00000000 00000000

The structure is mode + size , the first 2 bits are mode, and the last 30 bits are size.

==> getMode() method (measureSpec --> mode):

private static final int MODE_SHIFT = 30;
// Convert 0x3 to binary is: 11// After shifting left by 30 bits: 11000000000000000000000000000000000000000private static final int MODE_MASK = 0x3 << MODE_SHIFT;

public static int getMode(int measureSpec) {
 // After bitwise operation with MODE_MASK, the lower 30 bits will be cleared, and the result is the value after the mode is shifted left by 30 bits. return (measureSpec & MODE_MASK);
}

The same applies to getSize() method.

==> makeMeasureSpec() method (mode + size --> measureSpec):

public static int makeMeasureSpec(
 @IntRange(from = 0, 
  to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size, 
 @MeasureSpecMode int mode) {
 if (sUseBrokenMakeMeasureSpec) {
  return size + mode;
 } else {
  return (size & ~MODE_MASK) | (mode & MODE_MASK);
 }
}

Here we explain the result of clearing the high 2 bits on the bitwise or the left side is the size, and the lower 30 bits on the right side is the mode. The result of the bitwise or operation of the two is exactly the high 2 bit mode and the lower 30 bits size. Example:

01000000 00000000 00000000 00000000 | 
00001000 00001011 11110101 10101101 =
01001000 00001011 11110101 10101101

Binary calculation rules can be found:https:///article/

==> Measurement mode:

public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY  = 1 << MODE_SHIFT;
public static final int AT_MOST  = 2 << MODE_SHIFT;

UNSPECIFIED: The parent container does not restrict the View, and it is used internally.

EXACTLY: Accurate mode, the parent container detects the View size, that is, SpecSize; corresponding to the match_parent and the specified size in LayoutParams.

AT_MOST: Maximum mode, the parent container specifies the available size, the size of the View cannot exceed this value; corresponding to wrap_content.

(2) Measurement process of ViewGroup

Go back to the performanceMeasure method of ViewRootImpl, the parameters passed here are the measurement specifications of the top-level DecorView, and the measurement method is:

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
 int measureSpec;
 switch (rootDimension) {

 case .MATCH_PARENT:
  measureSpec = (windowSize, );
  break;
 case .WRAP_CONTENT:
  measureSpec = (windowSize, MeasureSpec.AT_MOST);
  break;
 default:
  measureSpec = (rootDimension, );
  break;
 }
 return measureSpec;
}

match_parent and specific values ​​are EXACTLY mode, while wrap_content is AT_MOST mode.

Going down, the onMeasure method of DecorView is called in the performMeasure method, and the DecorView is inherited from FrameLayout. You can see that the measureChildWithMargins method of FL is called and the measurement specifications are passed in:

protected void measureChildWithMargins(View child,
  int parentWidthMeasureSpec, int widthUsed,
  int parentHeightMeasureSpec, int heightUsed) {
 final MarginLayoutParams lp = (MarginLayoutParams) ();

 final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
   mPaddingLeft + mPaddingRight +  + 
     + widthUsed, );
 final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
   mPaddingTop + mPaddingBottom +  + 
     + heightUsed, );

 (childWidthMeasureSpec, childHeightMeasureSpec);
}

That is, the size of the measurement subcontrol. For the measurement rules, please refer to the getChildMeasureSpec method, which is summarized as follows:

childLayoutParams\parentSpecMode EXACTLY AT_MOST UNSPECIFIED
dp EXACTLY/childSize EXACTLY/childSize EXCATLY/childSize
match_parent EXACTLY/parentSize AT_MOST/parentSize UNSPECIFIED/0
wrap_content AT_MOST/parentSize AT_MOST/parentSize UNSPECIFIED/0

Go back to the onMeasure method, after measuring the subcontrol, ViewGroup will use some calculations to obtain its own size:

// Add paddingmaxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

// Check whether it is less than the minimum width and minimum heightmaxHeight = (maxHeight, getSuggestedMinimumHeight());
maxWidth = (maxWidth, getSuggestedMinimumWidth());

// Check the minimum height and width of the Drawablefinal Drawable drawable = getForeground();
if (drawable != null) {
 maxHeight = (maxHeight, ());
 maxWidth = (maxWidth, ());
}

setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
  resolveSizeAndState(maxHeight, heightMeasureSpec,
    childState &lt;&lt; MEASURED_HEIGHT_STATE_SHIFT));

In summary, the measurement of ViewGroup requires first measuring the size of the subview, and then combining padding and other attributes to calculate its own size.

(3) View measurement process

()
-->onMeasure(int widthMeasureSpec, int heightMeasureSpec)
-->setMeasuredDimension(int measuredWidth, int measuredHeight)
-->setMeasuredDimensionRaw(int measuredWidth, int measuredHeight)

You can see the setMeasuredDimensionRaw() method:

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
 //Storing measurement results mMeasuredWidth = measuredWidth;
 mMeasuredHeight = measuredHeight;

 // Set the flag for measurement completion mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

The View does not need to consider the size of the subView, just measure its own size based on the content.

In addition, the getDefaultSize method is called in the onMeasure method in the View:

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

public static int getDefaultSize(int size, int measureSpec) {
 int result = size;
 int specMode = (measureSpec);
 int specSize = (measureSpec);

 switch (specMode) {
 case :
  result = size;
  break;
 case MeasureSpec.AT_MOST:
 case :
  // The final measurement results are all the size of the parent container  result = specSize;
  break;
 }
 return result;
}

Here we see the exact mode and the maximum mode. The final measurement results are the size of the parent container, that is, the wrap_content, match_parent and numerical sizes in the layout are the same. This is why custom View must rewrite the onMeasure method.

2、layout

Layout is much simpler than measurement. Starting from the performLayout method of ViewRootImpl, you can see that the layout method of DecorView is called:

// In fact, it is the four information of DecorView: left, top, right, and bottom(0, 0, (), ());

Entering the layout method, it is found that l, t, r, and b are passed to the setFrame method and set to the member variable:

mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;

Therefore, the layout is actually to call the layout method of the View, setting its own l, t, r, and b values. In addition, if you go down in the layout method, you can see that the onLayout method is called, and after entering, it is found that it is an empty method. So check the onLayout method of FrameLayout:

@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();

 // Omitted
 for (int i = 0; i &lt; count; i++) {
  final View child = getChildAt(i);
  if (() != GONE) {
   final LayoutParams lp = (LayoutParams) ();

   // Omitted
   (childLeft, childTop, childLeft + width, childTop + height);
  }
 }
}

It can be seen that after performing a series of calculations, the layout method of child is called to layout the child controls. At the same time, the child control will continue to layout its own child controls, thereby realizing traversal.

In summary, the layout actually sets the View position for calling the layout method, and the ViewGroup needs to implement the onLayout method to place the child control.

3、draw

(1) The drawing process entrance

()
-->()
-->()
-->()

(2) Drawing steps

Entering the draw method of the View, you can see the following comment:

/*
 * 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)
 */

Combining the source code of the draw method, the key steps of the drawing process are as follows:

  1. ==> Draw the background: drawBackground(canvas)
  2. ==> Draw yourself: onDraw(canvas)
  3. ==> Draw subview: dispatchDraw(canvas)
  4. ==> Draw scrollbars, foreground and other decorations: onDrawForeground(canvas)

Thank you for your reading and support.