View drawing series:

  • DecorView and ViewRootImpl for Android View drawing process

  • Android View drawing process Measure process details (1)

  • Android View drawing process Layout and Draw process details (2)

  • Android View event distribution principle analysis

 

DecorView and its subviews are measured in detail. We’ll start with the Layout and Draw processes. Let’s start the analysis:

DecorView Layout stage

In ViewRootImpl, the performLayout method is called to determine the location of the DecorView on the screen. Here’s the code logic:

// ViewRootImpl 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 {// Draw host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()) based on the measurement results; mInLayout = false; int numViewsRequestingLayout = mLayoutRequesters.size(); if (numViewsRequestingLayout > 0) { // requestLayout() was called during layout. // If no layout-request flags are set on the requesting views, there is no problem. // If some requests are still pending, then we need to clear those flags and do // a full request/measure/layout pass to handle this situation. ArrayList<View>  validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, false); if (validLayoutRequesters ! = null) { // Set this flag to indicate that any further requests are happening during // the second pass, which may result in posting those requests to the next // frame instead mHandlingLayoutInLayoutRequest = true; // Process fresh layout requests, then measure and layout int numValidRequests = validLayoutRequesters.size(); for (int i = 0; i < numValidRequests; ++i) { final View view = validLayoutRequesters.get(i); Log.w("View", "requestLayout() improperly called by " + view + " during layout: running second layout pass"); view.requestLayout(); } measureHierarchy(host, lp, mView.getContext().getResources(), desiredWindowWidth, desiredWindowHeight); mInLayout = true; host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); mHandlingLayoutInLayoutRequest = false; // Check the valid requests again, this time without checking/clearing the // layout flags, since requests happening during the second pass get noop'd validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true); if (validLayoutRequesters ! = null) { final ArrayList<View> finalRequesters = validLayoutRequesters; // Post second-pass requests to the next frame getRunQueue().post(new Runnable() { @Override public void run() { int numValidRequests = finalRequesters.size(); for (int i = 0; i < numValidRequests; ++i) { final View view = finalRequesters.get(i); Log.w("View", "requestLayout() improperly called by " + view + " during second layout pass: posting in next frame"); view.requestLayout(); }}}); } } } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } mInLayout = false; }Copy the code

In the process of layout drawing, it will determine which of these requests are valid. If they are valid, it must be processed first. Clear layout-request flags (view.pFLAG_force_layout) and do a complete redraw: remeasure, layout.

In the case of a DecorView, the layout method is called to layout itself, noting that the parameters passed are 0,0, host.getMeasuredWidth, host.getMeasuredHeigh, The top left of the DecorView should be 0, and the width and height should be the measured width and height of the View.

// ViewGroup public final void layout(int l, int t, int r, int b) { if (! mSuppressLayout && (mTransition == null || ! mTransition.isChangingLayout())) { if (mTransition ! = null) { mTransition.layoutChange(this); } super.layout(l, t, r, b); } else { // record the fact that we noop'd it; request layout when transition finishes mLayoutCalledWhileSuppressed = true; }}Copy the code

Since the ViewGroup layout method is final, the subclass cannot be overwritten. We call the View#layout method of the parent class.

// View 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); // The layout has changed or needs to be rearranged. If so will enter onLayout logic (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; } // Clear the request layout flag 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 this case, 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

Call the setFrame method and pass in four position information. This method is used to determine the position of the four vertices of the View.

protected boolean setFrame(int left, int top, int right, int bottom) { boolean changed = false; if (DBG) { Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + "," + right + "," + bottom + ")"); } // If (mLeft! = left || mRight ! = right || mTop ! = top || mBottom ! = bottom) { changed = true; // Remember our drawn bit int drawn = mPrivateFlags & PFLAG_DRAWN; int oldWidth = mRight - mLeft; int oldHeight = mBottom - mTop; int newWidth = right - left; int newHeight = bottom - top; boolean sizeChanged = (newWidth ! = oldWidth) || (newHeight ! = oldHeight); // Invalidate our old position invalidate(sizeChanged); mLeft = left; mTop = top; mRight = right; mBottom = bottom; mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); / / marking mPrivateFlags | = PFLAG_HAS_BOUNDS; // size change callback if (sizeChanged) {sizeChange(newWidth, newHeight, oldWidth, oldHeight); } if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView ! = null) { // If we are visible, force the DRAWN bit to on so that // this invalidate will go through (at least to our parent). // This is because someone may have invalidated this view // before this call to setFrame came in, thereby clearing // the DRAWN bit. mPrivateFlags |= PFLAG_DRAWN; invalidate(sizeChanged); // parent display list may need to be recreated based on a change in the bounds // of any child invalidateParentCaches(); } // Reset drawn bit to original value (invalidate turns it off) mPrivateFlags |= drawn; mBackgroundSizeChanged = true; mDefaultFocusHighlightSizeChanged = true; if (mForegroundInfo ! = null) { mForegroundInfo.mBoundsChanged = true; } notifySubtreeAccessibilityStateChangedIfNeeded(); } return changed; }Copy the code

Here we see that it reassigns the values mLeft, mTop, mRight, and mBottom. For each View, including the ViewGroup, the above four values hold the position of Viwe, so these four values are the final width and height, i.e., GetLeft (), getTop() and other methods should be called after the layout method is finished to get the final width and height. If the corresponding methods are called before then, the result will be 0. When initialization is complete, the layout flow of the ViewGroup is complete.

SizeChange () is called if the size changes, and onSizeChanged is called back to indicate that the size of the view has changed, as well as the first time laout is called. When customizing a view, you can get the width and height of the control here.

private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) { onSizeChanged(newWidth, newHeight, OldWidth, oldHeight); . }Copy the code

Child View Layout process

Once the DecorView has determined its position, it calls onLayout to determine the position of the child view. For the onLayout method, the View and ViewGroup classes are empty implementations. Let’s look at FrameLayout:

// FrameLayout 
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }
Copy the code

As you can see, this method calls layoutChildren to determine the location of the child View.

// FrameLaout void layoutChildren(int left, int top, int right, int bottom, Boolean forceLeftGravity) {final int count = getChildCount(); / / get DecoverView remaining space final int parentLeft = getPaddingLeftWithForeground (); final int parentRight = right - left - getPaddingRightWithForeground(); final int parentTop = getPaddingTopWithForeground(); final int parentBottom = bottom - top - getPaddingBottomWithForeground(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() ! = GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final int width = child.getMeasuredWidth(); final int height = child.getMeasuredHeight(); int childLeft; int childTop;Copy the code
/ / DEFAULT_CHILD_GRAVITY = Gravity. TOP | Gravity. START default is in the upper left cornerCopy the code
int gravity = lp.gravity; if (gravity == -1) { gravity = DEFAULT_CHILD_GRAVITY; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); Final int verticalGravity = gravity & gravity_mask; Switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {case Gravity.CENTER_HORIZONTAL: childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: if (! forceLeftGravity) { childLeft = parentRight - width - lp.rightMargin; break; } case Gravity.LEFT: default: childLeft = parentLeft + lp.leftMargin; } switch (verticalGravity) {case Gravity.TOP: childTop = parentTop + lp.topmargin; break; case Gravity.CENTER_VERTICAL: childTop = parentTop + (parentBottom - parentTop - height) / 2 + lp.topMargin - lp.bottomMargin; break; case Gravity.BOTTOM: childTop = parentBottom - height - lp.bottomMargin; break; default: childTop = parentTop + lp.topMargin; } child.layout(childLeft, childTop, childLeft + width, childTop + height); }}}Copy the code

Let’s review the above logic:

  • We first get the padding value of the parent container to get the display space of the DecorView.
  • Then, each child View is traversed, and the coordinates of the upper left corner of the child View are determined according to the layout_gravity property of the child View, the measuring width and height of the child View, and the padding value of the parent container
  • We then call the Child.layout method, which combines the upper-left coordinates with its width and height, to determine the position of the child View.

The final call to layout is View#layout, which has been analyzed before.

If the child View is a ViewGroup, then repeat the above steps. If it is a View, then call the View#layout method directly. According to the above analysis, inside this method will set the View’s four layout parameters. Then call the onLayout method:

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

}
Copy the code

This method is an empty method, which means that subclasses are required to implement this method. Different views implement this method differently, so I won’t analyze it here.

The basic idea of layout stage is to start from the root View and recursively complete the layout of the whole control tree.

For the acquisition of width and height, here is the summary:

 

 

It is important to note that in some unusual cases, you can override the View layout() to force the layout to be set. In this case, the measured value is different from the final value.

Layout Overall flow chart

The above explains the Layout process from the perspective of View and ViewGroup respectively. Here we summarize the whole Layout process in the form of flow chart to deepen memory:

 

DecorView the Draw process

}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}

private void performDraw() { ... //fullRedrawNeeded, which determines whether to redraw all views draw(fullRedrawNeeded); . }Copy the code

It then executes to view script #draw:

private void draw(boolean fullRedrawNeeded) { ... Final Rect dirty = mDirty; // Get mDirty. Final Rect dirty = mDirty; if (mSurfaceHolder ! = null) { // The app owns the surface, we won't draw. dirty.setEmpty(); if (animating) { if (mScroller ! = null) { mScroller.abortAnimation(); } disposeResizeBuffer(); } return; } // If fullRedrawNeeded is true, the dirty area is set to the entire screen, indicating that the entire view needs to be drawn. Need to draw all views the if (fullRedrawNeeded) {mAttachInfo. MIgnoreDirtyState = true; Dirty. Set (0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); }... if (! drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { return; }}Copy the code

It then executes to view otiml #drawSoftware, and then executes to mview.draw (canvas) in view otiml #drawSoftware.

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { final Canvas canvas; // This canvas is the canvas on which we want to draw things. Canvas = msurface.lockCanvas (dirty); . // Canvas supports bitmap density, which is related to phone resolution canvas.setDensity(mDensity); . if (! canvas.isOpaque() || yoff ! = 0 || xoff ! = 0) { canvas.drawColor(0, PorterDuff.Mode.CLEAR); }... canvas.translate(-xoff, -yoff); . // Start drawing mview.draw (canvas); . / / submitted need to draw something surface. UnlockCanvasAndPost (canvas); }Copy the code

Mview.draw (canvas) begins the actual drawing. Draw: mView = ‘mView’;

  public void draw(Canvas canvas) {
        super.draw(canvas);

        if (mMenuBackground != null) {
            mMenuBackground.draw(canvas);
        }
    }
Copy the code

It’s going to call View#draw:

    public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

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

        // Step 1, draw the background, if needed
        int saveCount;

        //绘制背景
        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        // 如果可以跳过2和5步
        final int viewFlags = mViewFlags;
      //判断是否有绘制衰退边缘的标示
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
     // 如果没有绘制衰退边缘只需要3,4,6步
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

            // we're done...
            return;
        }

        /*
         * Here we do the full fledged routine...
         * (this is an uncommon case where speed matters less,
         * this is why we repeat some of the tests that have been
         * done above)
         */

        boolean drawTop = false;
        boolean drawBottom = false;
        boolean drawLeft = false;
        boolean drawRight = false;

        float topFadeStrength = 0.0f;
        float bottomFadeStrength = 0.0f;
        float leftFadeStrength = 0.0f;
        float rightFadeStrength = 0.0f;

        // Step 2, save the canvas' layers
        int paddingLeft = mPaddingLeft;

        final boolean offsetRequired = isPaddingOffsetRequired();
        if (offsetRequired) {
            paddingLeft += getLeftPaddingOffset();
        }

        int left = mScrollX + paddingLeft;
        int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
        int top = mScrollY + getFadeTop(offsetRequired);
        int bottom = top + getFadeHeight(offsetRequired);

        if (offsetRequired) {
            right += getRightPaddingOffset();
            bottom += getBottomPaddingOffset();
        }

        final ScrollabilityCache scrollabilityCache = mScrollCache;
        final float fadeHeight = scrollabilityCache.fadingEdgeLength;
        int length = (int) fadeHeight;

        // clip the fade length if top and bottom fades overlap
        // overlapping fades produce odd-looking artifacts
        if (verticalEdges && (top + length > bottom - length)) {
            length = (bottom - top) / 2;
        }

        // also clip horizontal fades if necessary
        if (horizontalEdges && (left + length > right - length)) {
            length = (right - left) / 2;
        }

        if (verticalEdges) {
            topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
            drawTop = topFadeStrength * fadeHeight > 1.0f;
            bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
            drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
        }

        if (horizontalEdges) {
            leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
            drawLeft = leftFadeStrength * fadeHeight > 1.0f;
            rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
            drawRight = rightFadeStrength * fadeHeight > 1.0f;
        }

        saveCount = canvas.getSaveCount();

        int solidColor = getSolidColor();
        if (solidColor == 0) {
            final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;

            if (drawTop) {
                canvas.saveLayer(left, top, right, top + length, null, flags);
            }

            if (drawBottom) {
                canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
            }

            if (drawLeft) {
                canvas.saveLayer(left, top, left + length, bottom, null, flags);
            }

            if (drawRight) {
                canvas.saveLayer(right - length, top, right, bottom, null, flags);
            }
        } else {
            scrollabilityCache.setFadeColor(solidColor);
        }

        // 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
        final Paint p = scrollabilityCache.paint;
        final Matrix matrix = scrollabilityCache.matrix;
        final Shader fade = scrollabilityCache.shader;

        if (drawTop) {
            matrix.setScale(1, fadeHeight * topFadeStrength);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left, top, right, top + length, p);
        }

        if (drawBottom) {
            matrix.setScale(1, fadeHeight * bottomFadeStrength);
            matrix.postRotate(180);
            matrix.postTranslate(left, bottom);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left, bottom - length, right, bottom, p);
        }

        if (drawLeft) {
            matrix.setScale(1, fadeHeight * leftFadeStrength);
            matrix.postRotate(-90);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left, top, left + length, bottom, p);
        }

        if (drawRight) {
            matrix.setScale(1, fadeHeight * rightFadeStrength);
            matrix.postRotate(90);
            matrix.postTranslate(right, top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(right - length, top, right, bottom, p);
        }

        canvas.restoreToCount(saveCount);

        // Overlay is part of the content and draws beneath Foreground
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }

        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);
    }
Copy the code

As you can see, the draw process is complex, but the logic is clear, and the official notes clearly explain how to do each step. The dirtyOpaque bit is used to determine whether the current View is transparent. If the View is transparent, some steps such as background drawing and content drawing will not be performed according to the following logic. This is easy to understand, since a View is transparent, there is no need to draw it. Then there are the six steps of the drawing process. Here we summarize what these six steps are respectively and then expand them. The six steps of the drawing process:

  1. Draw the background of the View
  2. Save the current layer information (skip)
  3. Draws the contents of the View
  4. Draw a child View of a View (if any)
  5. Draw the faded edges of the View, similar to the shadow effect (skip)
  6. Draw the View’s decorations (e.g. scroll bars)

Step 2 and step 5 can be skipped, we will not do the analysis here, we will focus on the other steps.

ViewGroup subclasses do not execute onDraw methods by default. InitViewGroup () ¶

private void initViewGroup() { // ViewGroup doesn't draw by default if (! debugDraw()) { setFlags(WILL_NOT_DRAW, DRAW_MASK); }... }Copy the code

As you can see from the second line, ViewGroup does not draw by default. On line 4, call setFlags to set the WILL_NOT_DRAW flag.

1 final int privateFlags = mPrivateFlags; 2 final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && 3 (mAttachInfo == null || ! mAttachInfo.mIgnoreDirtyState);Copy the code

 

The setFlags method is to change the value of mPrivateFlags in the View. We set WILL_NOT_DRAW so that dirtyOpaque will be true, so if (! DirtyOpaque does not hold, and the onDraw method is not executed.

1. Paint the background

View#drawBackground

Private void Drawable (Canvas Canvas) {// Get final Drawable background = mBackground; if (background == null) { return; } // Determine background Drawable bounds setBackgroundBounds(); . Final int scrollX = mScrollX; final int scrollX = mScrollX; final int scrollY = mScrollY; if ((scrollX | scrollY) == 0) { background.draw(canvas); } else { canvas.translate(scrollX, scrollY); Draw background.draw(canvas); Canvas. Translate (-scrollx, -scrolly); drawable (-scrollx, -scrolly); }}Copy the code

3. Draw the View contents

Skip step 2 first because not all views need to draw faded edges. DecorView# ontouch:

public void onDraw(Canvas c) { super.onDraw(c); mBackgroundFallback.draw(this, mContentRoot, c, mWindow.mContentParent, mStatusColorViewState.view, mNavigationColorViewState.view); }Copy the code

View#onDraw

  protected void onDraw(Canvas canvas) {

  }
Copy the code

OnDraw is an empty implementation that requires the subview to draw itself. There is usually nothing in a DecorView except the background color and so on, so there is nothing to draw.

4. Draw subviews

DecorView #dispatchDraw:

protected void dispatchDraw(Canvas canvas) { boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode); final int childrenCount = mChildrenCount; final View[] children = mChildren; int flags = mGroupFlags; // Does the ViewGroup have a child View entry animation, if there is bound to the View // start animation controller... Int clipSaveCount = 0; Final Boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK; // Remove the padding if (clipToPadding) {clipSaveCount = canvas.save(); canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop, mScrollX + mRight - mLeft - mPaddingRight, mScrollY + mBottom - mTop - mPaddingBottom); }... for (int i = 0; i < childrenCount; I++) {// the View from mTransientViews is added by addTransientView. They are just one item rendered by the container while (transientIndex >= 0 && mTransientIndices. Get (transientIndex) == I) {Final View transientChild = mTransientViews.get(transientIndex); if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || transientChild.getAnimation() ! = null) { more |= drawChild(canvas, transientChild, drawingTime); } transientIndex++; if (transientIndex >= transientCount) { transientIndex = -1; } } int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() ! = null) { more |= drawChild(canvas, child, drawingTime); }}... }Copy the code

ViewGroup#dispatchDraw starts the first animation added to the layout, then determines the drawing area, traverses the drawing View, and then draws the rendering mTransientViews first. Draw a View call to ViewGroup#drawChild:

protected boolean drawChild(Canvas canvas, View child, DrawingTime (canvas, this, drawingTime) {return child. Draw (canvas, this, drawingTime); }Copy the code

View# the draw (canvas, this drawingTime)

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { boolean drawingWithRenderNode = mAttachInfo ! = null && mAttachInfo.mHardwareAccelerated && hardwareAcceleratedCanvas; . Draw (canvas); draw(canvas); draw(canvas); draw(canvas); drawingWithDrawingCache) { if (drawingWithRenderNode) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; ((DisplayListCanvas) canvas).drawRenderNode(renderNode); } else { // Fast path for layouts with no backgrounds if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; dispatchDraw(canvas); } else { draw(canvas); } } } else if (cache ! = null) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; if (layerType == LAYER_TYPE_NONE) { // no layer paint, use temporary paint to draw bitmap Paint cachePaint = parent.mCachePaint; if (cachePaint == null) { cachePaint = new Paint(); cachePaint.setDither(false); parent.mCachePaint = cachePaint; } cachePaint.setAlpha((int) (alpha * 255)); Canvas. DrawBitmap (cache, 0.0 f, 0.0 f, cachePaint); } else { // use layer paint to draw the bitmap, merging the two alphas, but also restore int layerPaintAlpha = mLayerPaint.getAlpha(); mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha)); Canvas. DrawBitmap (cache, 0.0 f, 0.0 f, mLayerPaint); mLayerPaint.setAlpha(layerPaintAlpha); }}}Copy the code

The first step is to determine whether there is already a cache, i.e. whether it has been drawn once before. If not, the draw(canvas) method will be called and normal drawing will begin, i.e. the six steps mentioned above. Otherwise, the display will be drawn using cache.

This step can also be summarized as the ViewGroup drawing process, it has been drawn on the sub-view, and the sub-view will call its own draw method to draw itself, so constantly traversing the sub-view and sub-view of its own drawing, so that the View tree to complete the drawing.

For custom views, if you need to draw something, just re-draw onDraw.

6. Paint decorations

Public void onDrawForeground(Canvas) {onDrawScrollIndicators(Canvas); // DrawScrollBar onDrawScrollBars(canvas); Drawable foreground = mForegroundInfo! Drawable foreground = mForegroundInfo! = null ? mForegroundInfo.mDrawable : null; if (foreground ! = null) { if (mForegroundInfo.mBoundsChanged) { mForegroundInfo.mBoundsChanged = false; final Rect selfBounds = mForegroundInfo.mSelfBounds; final Rect overlayBounds = mForegroundInfo.mOverlayBounds; if (mForegroundInfo.mInsidePadding) { selfBounds.set(0, 0, getWidth(), getHeight()); } else { selfBounds.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), getHeight() - getPaddingBottom()); } final int ld = getLayoutDirection(); Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(), foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld); foreground.setBounds(overlayBounds); } foreground.draw(canvas); }}Copy the code

2 and 5. Draw the faded edges of the View

When the horizontalEdges or verticalEdges have a true, the faded edges of the View need to be drawn:

boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) ! = 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) ! = 0;Copy the code

At this time, first calculate whether to draw the top, bottom, left and right faded edges and its parameters, and then save the view layer:

int paddingLeft = mPaddingLeft; final boolean offsetRequired = isPaddingOffsetRequired(); if (offsetRequired) { paddingLeft += getLeftPaddingOffset(); } int left = mScrollX + paddingLeft; int right = left + mRight - mLeft - mPaddingRight - paddingLeft; int top = mScrollY + getFadeTop(offsetRequired); int bottom = top + getFadeHeight(offsetRequired); if (offsetRequired) { right += getRightPaddingOffset(); bottom += getBottomPaddingOffset(); } final ScrollabilityCache scrollabilityCache = mScrollCache; final float fadeHeight = scrollabilityCache.fadingEdgeLength; int length = (int) fadeHeight; if (verticalEdges && (top + length > bottom - length)) { length = (bottom - top) / 2; } if (horizontalEdges && (left + length > right - length)) { length = (right - left) / 2; } if (verticalEdges) {topFadeStrength = math.max (0.0f, math.min (1.0f, getTopFadingEdgeStrength())); DrawTop = topFadeStrength * fadeHeight > 1.0f; BottomFadeStrength = Math. Max (0.0 f, Math. Min (1.0 f, getBottomFadingEdgeStrength ())); DrawBottom = bottomFadeStrength * fadeHeight > 1.0f; {} the if (horizontalEdges) leftFadeStrength = Math. Max (0.0 f, Math. Min (1.0 f, getLeftFadingEdgeStrength ())); DrawLeft = leftfadeheight > 1.0f; RightFadeStrength = Math. Max (0.0 f, Math. Min (1.0 f, getRightFadingEdgeStrength ())); DrawRight = rightFadeStrength * fadeHeight > 1.0f; } saveCount = canvas.getSaveCount(); int solidColor = getSolidColor(); if (solidColor == 0) { final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG; if (drawTop) { canvas.saveLayer(left, top, right, top + length, null, flags); } if (drawBottom) { canvas.saveLayer(left, bottom - length, right, bottom, null, flags); } if (drawLeft) { canvas.saveLayer(left, top, left + length, bottom, null, flags); } if (drawRight) { canvas.saveLayer(right - length, top, right, bottom, null, flags); } } else { scrollabilityCache.setFadeColor(solidColor); }Copy the code

Draw faded edges and restore view layer:

        final Paint p = scrollabilityCache.paint;
        final Matrix matrix = scrollabilityCache.matrix;
        final Shader fade = scrollabilityCache.shader;
        if (drawTop) {
            matrix.setScale(1, fadeHeight * topFadeStrength);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left, top, right, top + length, p);
        }
        if (drawBottom) {
            matrix.setScale(1, fadeHeight * bottomFadeStrength);
            matrix.postRotate(180);
            matrix.postTranslate(left, bottom);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left, bottom - length, right, bottom, p);
        }
        if (drawLeft) {
            matrix.setScale(1, fadeHeight * leftFadeStrength);
            matrix.postRotate(-90);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left, top, left + length, bottom, p);
        }
        if (drawRight) {
            matrix.setScale(1, fadeHeight * rightFadeStrength);
            matrix.postRotate(90);
            matrix.postTranslate(right, top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(right - length, top, right, bottom, p);
        }
        canvas.restoreToCount(saveCount);
Copy the code

The so-called drawing decoration refers to the rest of the View except the background, content, sub-view, such as the scroll bar, etc.

Finally, attach the draw process of View:

 

 

At this point, the View drawing process is finished, the next article will talk about custom View.

Summary:

  • The view keeps looking for the parent and finds its way to the DecorView. The DecorView is supposed to be the vertex, but the DecorView also has a virtual parent, ViewRootImpl. The View View pl is not a View or a ViewGroup, it has a member. The mView is a DecorView, and all operations are distributed from the View View PL down
  • View’s invalidate does not cause the view’s invalidate to be called. Instead, it recursively calls the parent view’s invalidateChildInParent. Until the ViewRootImpl invalidateChildInParent, then peformTraversals causes the current view to be repainted, since mLayoutRequested is false, OnMeasure and onLayout will not be called, while OnDraw will be called
  • A view’s invalidate sets its PFLAG_INVALIDATED to 1, and its and parent viewgroup’s PFLAG_DRAWING_CACHE_VALID to 0
  • RequestLayout directly recursively calls the requestLayout of the parent window until ViewRootImpl, which then triggers peformTraversals, and since mLayoutRequested is true, OnMeasure and onLayout will be called. It doesn’t have to trigger OnDraw
  • RequestLayout will trigger onDraw because it will invalidate l, T, r, and B in the layout process. MDirty can also be non-empty for other reasons (such as running animations)
  • RequestLayout causes its own and parent view’s PFLAG_FORCE_LAYOUT and PFLAG_INVALIDATED flags to be set.
  • In general, invalidate is called whenever a refresh is required, requestLayout is called when a new measure is required, followed by an invalidate.