“This is the 16th day of my participation in the First Challenge 2022. For details: First Challenge 2022”

Related articles: DecorView Android View DecorView Android View DecorView View rendering process (3) -Activity View -WindowManager Android View rendering process (4) -Activity View -ViewRootImpl Android View rendering process (5) -Measure Android View View drawing process (six) -Layout Android View drawing process (seven) -Draw

Last article the View drawing process Layout (Layout) logic through the analysis of the main content of the source code is introduced, the main method call is onLayout method and layoutChildren, we introduce the View drawing process in the last step Draw Draw.

Draw

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

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

Then execute the draw() method:

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

The above method finally calls drawSoftware, which then executes to mView.draw(canvas) after 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) to start the actual drawing:

 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

Draw process is more complex, but the logic is still very clear, focus on the above at the beginning of the marker: DirtyOpaque, the meaning of the flag bit is to judge whether the current view is transparent, if it is to execute the next step is not transparent, simple, transparent can see drawing, then the drawing process is mainly divided into six steps, but the main is a step, the next will be to: 1) : the background of the map view (2) : Save layer information (not important) 3) : Draw the content of the View 4) : Draw the sub-view (same process as measure, layout) 5) : Draw shadows (not important) 6) : Draw other parts of the View \

InitViewGroup (); / / initViewGroup(); / / initViewGroup(); / / 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 comment, 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.

Below for the above steps, focus on the source code

Draw the background

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

Draw the content

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

Super is also an empty implementation that needs to be implemented by itself, depending on how the different components are implemented

Draw the son View

The dispatchDraw process of the ViewGroup is to start the animation that was first added to the layout, then determine the drawing area, and iterate over the drawing View. DrawChild (ViewGroup drawChild, ViewGroup drawChild, ViewGroup drawChild, ViewGroup drawChild)

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

Draw a View:

protected boolean drawChild(Canvas canvas, View child, DrawingTime (canvas, this, drawingTime) {return child. Draw (canvas, this, drawingTime); }Copy the code
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 main content of the above source code and the previous analysis of measure and layout, first determine whether there is a cache, that is, whether it has been drawn once before, if not, will call draw(canvas) method, start normal drawing, otherwise use cache to display. Code Chinese Academy of Sciences see, 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 their own drawing, so that the View tree to complete the drawing.

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

Draw other parts

This is the part of the View except for the background, content, and child views.

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

I won’t go into details about 2 and 5, but mostly draw things like shadows and edge colors

In fact, the theme of the idea and measure and layout, familiar with custom View students, for drawing Draw method is not unfamiliar, all kinds of Draw type method, shape and so on, these source code roughly understand the process, the actual development process, Still need practice, more operation will make perfect, written is the real thing.

The next article summarizes the View drawing process, the above has introduced the wrong place also please correct, welcome to leave a message