The Drawing Process and Principle of Android UI

Keywords: PHP less

Drawing process source path

1. Activity loads ViewRootImpl

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

2. ViewRootImpl starts traversal of View tree

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

II. View Drawing Process

1,measure

(1) What is MeasureSpec?

Rewriting the onMeasure() method knows that measurement requires the MeasureSpec class to obtain the measurement mode and size of View, so how does this class store the two information?

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

00 000000 00000000 00000000 00000000

Its 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;
// Converting 0x3 to binary is: 11
// 30 places left: 110 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 00
private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

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

The getSize() method is the same.

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

Explain here that the results of bit-by-bit or high 2-bit zeroing with size on the left and low 30-bit zeroing with mode on the right are exactly the results of bit-by-bit or operation for high 2-bit mode and low 30-bit size, for example:

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

Binary Computing Rules are available for reference: https://www.cnblogs.com/joahyau/p/6420619.html

==> 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 View and is used internally.

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

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

(2) Measuring process of ViewGroup

Back to the performance measure method of ViewRootImpl, where the parameters passed in are the measurement specifications of the top DecorView, which are measured by:

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

The match_parent and the exact value are EXACTLY mode and wrap_content is AT_MOST mode.

Moving down, the performance measure method calls DecorView's onMeasure method, which inherits from FrameLayout. You can see that FL's onMeasure method calls the measureChildWithMargins method and passes in its own measurement specification:

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, to measure the size of the child controls, the details of the measurement rules can be seen in the getChildMeasureSpec method, which can be 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 testing the child controls, the ViewGroup will do some calculations to get its 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 the minimum height and width of Drawable
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));

In summary, the measurement of ViewGroup needs to measure the size of sub-View first, and then calculate its own size by combining with attributes such as padding.

(3) Measuring process 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:

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

    // Setting up the marker for the completion of measurement
    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

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:

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.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        // The final measurements were the size of the parent container.
        result = specSize;
        break;
    }
    return result;
}

Here you see the exact and maximum modes, and the final measurement results are the size of the parent container, that is, wrap_content, match_parent and numerical size effects are the same in the layout, which is why the onMeasure method must be rewritten for custom View.

2,layout

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

// In fact, DecorView's left, top, right, bottom four information
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

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

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

Therefore, the layout actually calls the layout method of View and sets its own l, t, r and b values. In addition, as you go down the layout method, you can see that the onLayout method is called and found to be empty. So look at 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();

    // ellipsis

    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            // ellipsis

            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}

As you can see, after a series of calculations, the child's layout method is invoked to lay out the child control, while the child control will continue to lay out its own child control, thus achieving traversal.

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

3,draw

(1) Drawing process entry
ViewRootImpl.performDraw()
-->ViewRootImpl.draw()
-->ViewRootImpl.drawSoftware()
-->View.draw()
(2) Drawing steps

Entering the draw method of 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)
 */

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

==> Drawing Background (canvas)

==> Draw yourself: onDraw(canvas)

==> Draw sub view: dispatchDraw(canvas)

==> Draw scrollbars, foreground decorations, etc: onDraw Foreground (canvas)

Posted by BoltZ on Sat, 03 Aug 2019 00:56:15 -0700