Detailed Explanation of Android UI Drawing Process and Principle

  • 2021-11-10 10:56:02
  • OfStack

1. Draw the process source code path

1. Activity loads ViewRootImpl


ActivityThread.handleResumeActivity() 
--> WindowManagerImpl.addView(decorView, layoutParams) 
--> WindowManagerGlobal.addView()

2. ViewRootImpl starts the traversal of View tree


ViewRootImpl.setView(decorView, layoutParams, parentView)
-->ViewRootImpl.requestLayout()
-->scheduleTraversals()
-->TraversalRunnable.run()
-->doTraversal()
-->performTraversals() ( performMeasure , performLayout , performDraw ) 

2. View drawing process

1. measure

(1) What is MeasureSpec?

As you know from overriding the onMeasure () method, measurement requires the MeasureSpec class to obtain the measurement mode and size of View, so how does this class store these two information?

If you look closely, 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 two bits are mode, and the last 30 bits are size.

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


private static final int MODE_SHIFT = 30;
// 0x3 Convert to 2 The binary system is: 11
//  Left shift 30 Bit after: 11000000 00000000 00000000 00000000
private static final int MODE_MASK = 0x3 << MODE_SHIFT;

public static int getMode(int measureSpec) {
 //  And MODE_MASK After bitwise AND operation, it is about to be low 30 Bit is cleared, and the result is mode Left shift 30 The value after the bit 
 return (measureSpec & MODE_MASK);
}

The same applies to the 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, explain 1, the result of clearing the high 2 bits of size on the left side by bit, and the result of clearing the low 30 bits of mode on the right side. The results of bitwise OR operation of both are just the high 2 bits mode and the low 30 bits size, for example:


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

For binary calculation rules, please refer to: https://www.ofstack.com/article/166892. htm

== > 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 impose any restrictions on View and is used internally.

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

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

(2) Measurement flow of ViewGroup

Back to the performMeasure method of ViewRootImpl, the parameter passed in here is the measurement specification of the top-level DecorView, and its measurement mode is:


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

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

match_parent and the 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 DecorView inherits from FrameLayout. You can see that the measureChildWithMargins method is called in the onMeasure method of FL, and your own measurement specification is passed in:


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

That is, measure the size of child controls. For details of measurement rules, see 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

Back to the onMeasure method, after measuring the child control, ViewGroup will go through 1 calculation to get its own size:


//  Plus padding
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

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

//  Check Drawable Minimum height and width of 
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));

To sum up, the measurement of ViewGroup needs to measure the size of sub-View first, and then calculate its own size in combination with padding and other attributes.

(3) Measurement flow of View


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

You can see the setMeasuredDimensionRaw () method:


ViewRootImpl.setView(decorView, layoutParams, parentView)
-->ViewRootImpl.requestLayout()
-->scheduleTraversals()
-->TraversalRunnable.run()
-->doTraversal()
-->performTraversals() ( performMeasure , performLayout , performDraw ) 
0

View does not need to consider the size of sub-View, but can measure its own size according to the content.

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


ViewRootImpl.setView(decorView, layoutParams, parentView)
-->ViewRootImpl.requestLayout()
-->scheduleTraversals()
-->TraversalRunnable.run()
-->doTraversal()
-->performTraversals() ( performMeasure , performLayout , performDraw ) 
1

We see the exact mode and the maximum mode here, and the final measurement results are the size of the parent container, that is, wrap_content, match_parent and numerical size effects in the layout are all the same, which is why custom View1 must override onMeasure method.

2. layout

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


ViewRootImpl.setView(decorView, layoutParams, parentView)
-->ViewRootImpl.requestLayout()
-->scheduleTraversals()
-->TraversalRunnable.run()
-->doTraversal()
-->performTraversals() ( performMeasure , performLayout , performDraw ) 
2

Enter the layout method and find that l, t, r and b are passed into the setFrame method and set to the member variables:


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

Therefore, the layout is actually to call the layout method of View and set 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 was called, and when you enter it, you find that it is an empty method. So look at the onLayout method of FrameLayout:


ViewRootImpl.setView(decorView, layoutParams, parentView)
-->ViewRootImpl.requestLayout()
-->scheduleTraversals()
-->TraversalRunnable.run()
-->doTraversal()
-->performTraversals() ( performMeasure , performLayout , performDraw ) 
4

As you can see, after a series of calculations, the layout method of child is called to lay out the child controls, and at the same time, the child controls will continue to lay out their own child controls, thus realizing traversal.

To sum up, the layout actually calls layout method to set View position, while ViewGroup needs to implement onLayout method to place child controls.

3. draw

(1) Drawing process entry


ViewRootImpl.setView(decorView, layoutParams, parentView)
-->ViewRootImpl.requestLayout()
-->scheduleTraversals()
-->TraversalRunnable.run()
-->doTraversal()
-->performTraversals() ( performMeasure , performLayout , performDraw ) 
5

(2) Drawing steps

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


ViewRootImpl.setView(decorView, layoutParams, parentView)
-->ViewRootImpl.requestLayout()
-->scheduleTraversals()
-->TraversalRunnable.run()
-->doTraversal()
-->performTraversals() ( performMeasure , performLayout , performDraw ) 
6

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

== > Drawing Background: drawBackground (canvas) == > Draw Yourself: onDraw (canvas) == > Render view: dispatchDraw (canvas) == > Draw scroll bars, foreground and other decorations: onDrawForeground (canvas)

Thank you for reading and supporting this site.


Related articles: