A bad pen is better than a good memory. Taking notes in your life is not only convenient for yourself, but also for others.

(Most of the source code below is from API 28)

Following the previous article to review the principle of View rendering (1), continue to see the principle of View rendering.

1. View drawing process

Views are drawn from the performTraversals() method of the root ViewRoot, traversing the tree from top to bottom. Each view controller is responsible for drawing itself, and the ViewGroup is responsible for telling its children to draw. The process of view operation can be divided into three steps, namely Measure, Layout and Draw. PerformTraversals method in viewRoot implementation class ViewRootImpl:

  • The measure method measures the width and height of a View
  • The Layout method is used to determine the position of the View in the parent container
  • The draw method is responsible for drawing the View on the screen

View drawing flow chart:

PerformTraversals traversals

  private void performTraversals() {... int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); . // Measure performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); . // performLayout(lp, mWidth, mHeight); . / / draw performDraw (); . }Copy the code

Call performMeasure, performLayout and performDraw successively to complete measure, Layout and draw processes of top-level View. PerformMeasure will call the measure method, which in turn calls the onMeasure method. In the onMeasure method, the child elements will be measured. In this way, the whole View tree will be traversed.

PerformLayout, performDraw pass process is very similar, but performDraw is implemented in the draw method via dispatchDraw method.

The Measure procedure determines the width and height of the View, the Layout method determines the coordinates of the four vertices and the actual width and height (usually equal to the width and height calculated in measure), and the DRAW method determines the display of the View. Only when the draw method is complete will it appear correctly on the screen.

2. MeasureSpec

MeasureSpec is an important parameter of measure. MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec MeasureSpec is a static inner class of the View class that specifies how the View should be measured. MeasureSpec has three modes:

  • UNSPECIFIED: The measurement mode is not specified. The parent view has no limitations on the size of its child views, which can be of any size as desired. The child views are used internally and are rarely used in application development.

  • EXACTLY: Precise measurement mode. This mode takes effect when layout_width or layout_height of the View is specified as a specific value or match_parent, indicating that the parent View has determined the exact size of the child View. In this mode, the measured value of the View is the value of SpecSize.

  • AT_MOST: maximum mode. This mode takes effect when the layout_width or layout_height of the current view is specified as wrap_content. In this case, the size of the child view can be any size that does not exceed the maximum size of the parent view.

MeasureSpec = MeasureSpec;

childLayoutParams/parentSpecParams EXACTLY AT_MOST UNSPECIFIED
dp/px EXACTLY childSIze EXACTLY childSIze EXACTLY childSIze
match_parent EXACTLY parentSize AT_MOST parentSize UNSPECIFIED 0
wrap_content AT_MOST parentSize AT_MOST parentSize UNSPECIFIED 0

3. Measure

To draw a View, start with a measure. Look at the performMeasure () method:

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}Copy the code

Specific operations are assigned to the ViewGroup, which passes them to child Views in its measureChild method. A ViewGroup iterates through all its sub-views and calls the measure method of each sub-view to measure operations.

3.1 Measure process of View

The measure process of a View is completed by the measure method. The measure method is a final method and cannot be overwritten. It will call the onMeasure method of the View. The getDefaultSize method is called from the onMeasure method, and the getDefault method is called from the getSuggestedWidth and getSuggestedHeight methods.

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
Copy the code
      /**
     * Utility to return a default size. Uses the supplied size if the
     * MeasureSpec imposed no constraints. Will get larger if allowed
     * by the MeasureSpec.
     *
     * @param size Default size for this view
     * @param measureSpec Constraints imposed by the parent
     * @return The size this view should be.
     */
    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;
    }
Copy the code

The getDefaultSize method returns the measured View size.

Then look at the getSuggestedWidth and getSuggestedHeight methods:

    /**
     * Returns the suggested minimum width that the view should use. This
     * returns the maximum of the view's minimum width * and the background's minimum width
     *  ({@link android.graphics.drawable.Drawable#getMinimumWidth()}).
     * <p>
     * When being used in {@link #onMeasure(int, int)}, the caller should still
     * ensure the returned width is within the requirements of the parent.
     *
     * @return The suggested minimum width of the view.
     */
    protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

    protected int getSuggestedMinimumHeight() {
        return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());

    }
Copy the code

It returns the value of minSize with no background specified, and the getMinimumWidth/getMinimumHeight method of background drawable with background specified

Both methods return the original width if Drawable has the original width, and 0 otherwise

As you can see from the getDefaultSize method, the width and height of the View is determined by specSize.

3.2 Measure Process of a ViewGroup

In addition to completing its own measure process, a ViewGroup will iterate over the measure method that calls the child element, and then the child element executes recursively again. The ViewGroup is an abstract class, so it does not override the View’s onMeasure method. But it offers a measureChildren approach, as follows:

    /**
     * Ask all of the children of this view to measure themselves, taking into
     * account both the MeasureSpec requirements for this view and its padding.
     * We skip children that are in the GONE state The heavy lifting is done in
     * getChildMeasureSpec.
     *
     * @param widthMeasureSpec The width requirements for this view
     * @param heightMeasureSpec The height requirements for this view
     */
    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if((child.mViewFlags & VISIBILITY_MASK) ! = GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } } /** * Ask one of the children of this view to measure itself, taking into * account both the MeasureSpec requirementsfor this view and its padding.
     * The heavy lifting is done in getChildMeasureSpec.
     *
     * @param child The child to measure
     * @param parentWidthMeasureSpec The width requirements for this view
     * @param parentHeightMeasureSpec The height requirements for this view
     */
    protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
Copy the code

As you can see, when a ViewGroup executes a measure, the measureChild method is called to measure the child elements.

In the measureChild method, take the child’s LayoutParams, create the child’s MeasureSpec with the getChildMeasureSpec method, and pass it to the View’s measure method for measurement.

The ViewGroup does not define the measurement process because it is an abstract class. The onMeasure method of the specific measurement process needs to be implemented by subclasses. Since the characteristics of its subclasses may be very different, it cannot be unified (such as LinearLayout and RelativeLayout).

4. Layout

The Layout process is used to determine the position of child elements in the ViewGroup. When the ViewGroup is identified, the onLayout method is called and the onLayout method is called in the onLayout method. The Layout method determines the position of the View, while the onLayout method determines the position of all child elements.

PerformLayout for ViewRootImpl looks like this:

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        mLayoutRequested = false;
        mScrollMayChange = true;
        mInLayout = true;

        final View host = mView;
        if (host == null) {
            return;
        }
        if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
            Log.v(mTag, "Laying out " + host + " to (" +
                    host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
        }

        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout"); try { host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); . } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } mInLayout =false;
    }
Copy the code

Let’s look at the View layout method:

    public void layout(int l, int t, int r, int b) {
        if((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) ! = 0) { onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; 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);

            if (shouldDrawRoundScrollbar()) {
                if(mRoundScrollbarRenderer == null) { mRoundScrollbarRenderer = new RoundScrollbarRenderer(this); }}else {
                mRoundScrollbarRenderer = null;
            }

            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo li = mListenerInfo;
            if(li ! = null && li.mOnLayoutChangeListeners ! = null) { ArrayList<OnLayoutChangeListener> listenersCopy = (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone(); int numListeners = listenersCopy.size();for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

        final boolean wasLayoutValid = isLayoutValid();

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;

        if(! wasLayoutValid && isFocused()) { mPrivateFlags &= ~PFLAG_WANTS_FOCUS;if (canTakeFocus()) {
                // We have a robust focus, so parents should no longer be wanting focus.
                clearParentsWantFocus();
            } else if(getViewRootImpl() == null || ! getViewRootImpl().isInLayout()) { // This is a weird case. Most-likely the user, rather than ViewRootImpl, called // layout. In thiscase, there's no guarantee that parent layouts will be evaluated // and thus the safest action is to clear focus here. clearFocusInternal(null, /* propagate */ true, /* refocus */ false); clearParentsWantFocus(); } else if (! hasParentWantsFocus()) { // original requestFocus was likely on this view directly, so just clear focus clearFocusInternal(null, /* propagate */ true, /* refocus */ false); } // otherwise, we let parents handle re-assigning focus during their layout passes. } else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) ! = 0) { mPrivateFlags &= ~PFLAG_WANTS_FOCUS; View focused = findFocus(); if (focused ! = null) { // Try to restore focus as close as possible to our starting focus. if (! restoreDefaultFocus() && ! hasParentWantsFocus()) { // Give up and clear focus once we've reached the top-most parent which wants
                    // focus.
                    focused.clearFocusInternal(null, /* propagate */ true, /* refocus */ false); }}}if((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) ! = 0) { mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT; notifyEnterOrExitForAutoFillIfNeeded(true); }}Copy the code

First, use the setFrame method to set the position of the View’s four vertices (mLeft, mRight, mTop, mBottom). Once the four vertices are determined, their positions in the parent container are determined, and the onLayout method is called to let the parent determine the position of the children. OnLayout is also specific to the layout, so neither View nor ViewGroup implements onLayout.

5. Draw

The Draw process draws the View onto the screen. Let’s start with the performDraw method:

    private void performDraw() {... try { boolean canUseAsync = draw(fullRedrawNeeded); . } finally { mIsDrawing =false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); }... }Copy the code
    private boolean draw(boolean fullRedrawNeeded) {
    ...
    if(! drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets)) {return false; } } private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty, Rect surfaceInsets) { ... mView.draw(canvas); . }Copy the code

Finally, the draw method of each View is called to draw each specific View. Drawing can be basically divided into six steps:

   public void draw(Canvas canvas) {
    ...
    // Step 1, draw the background, if needed
    if (!dirtyOpaque) {
      drawBackground(canvas);
    }
    ...
    // Step 2, save the canvas' layers saveCount = canvas.getSaveCount(); . // Step 3, draw the content if (! dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 5, draw the fade effect and restore layers canvas.drawRect(left, top, right, top + length, p); . canvas.restoreToCount(saveCount); . // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); }Copy the code

The View drawing process is passed through dispatchDraw. DispatchDraw traverses the draw method that calls all child elements. Thus draw events are passed down one layer at a time.

It has a special setWillNotDraw method. If a View doesn’t need to draw anything, after we set this flag to true, the system will optimize it accordingly. Normally views do not have this flag bit enabled. But viewGroups are enabled by default.

Its significance for practical development lies in: when our custom control inherits from the ViewGroup and does not have the drawing function, we can turn on this marker bit to facilitate the system for subsequent optimization.

6. Conclusion

The principle of view drawing is also very much on the Internet, it is easy to forget for a long time, look at others, it is better to record them by the way, for the convenience of their future review.

Important reference:

  • Blog.csdn.net/qq_21556263…

  • www.jianshu.com/p/c151efe22…