The performTravel method goes through the onMeasure and onLayout processes to the following code section.

if (mFirst) { if (sAlwaysAssignFocus || ! isInTouchMode()) { if (mView ! = null) { if (! mView.hasFocus()) { mView.restoreDefaultFocus(); } else { ... } } } else { View focused = mView.findFocus(); if (focused instanceof ViewGroup && ((ViewGroup) focused).getDescendantFocusability() == ViewGroup.FOCUS_AFTER_DESCENDANTS) { focused.restoreDefaultFocus(); } } } final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible; final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible; final boolean regainedFocus = hasWindowFocus && mLostWindowFocus; if (regainedFocus) { mLostWindowFocus = false; } else if (! hasWindowFocus && mHadWindowFocus) { mLostWindowFocus = true; } if (changedVisibility || regainedFocus) { boolean isToast = (mWindowAttributes == null) ? false : (mWindowAttributes.type == WindowManager.LayoutParams.TYPE_TOAST); . } mFirst = false; mWillDrawSoon = false; mNewSurfaceNeeded = false; mActivityRelaunched = false; mViewVisibility = viewVisibility; mHadWindowFocus = hasWindowFocus; if (hasWindowFocus && ! isInLocalFocusMode()) { final boolean imTarget = WindowManager.LayoutParams .mayUseInputMethod(mWindowAttributes.flags);  if (imTarget ! = mLastWasImTarget) { mLastWasImTarget = imTarget; InputMethodManager imm = InputMethodManager.peekInstance(); if (imm ! = null && imTarget) { imm.onPreWindowFocus(mView, hasWindowFocus); imm.onPostWindowFocus(mView, mView.findFocus(), mWindowAttributes.softInputMode, ! mHasHadWindowFocus, mWindowAttributes.flags); }}}Copy the code

Before entering the onDraw process, focus is dealt with. This process can be divided into two steps:

  • 1. If it is the first time to render, the width and height are all 0.
private boolean canTakeFocus() { return ((mViewFlags & VISIBILITY_MASK) == VISIBLE) && ((mViewFlags & FOCUSABLE) == FOCUSABLE) && ((mViewFlags & ENABLED_MASK) == ENABLED) && (sCanFocusZeroSized || ! isLayoutValid() || hasSize()); }Copy the code

Before each onMeasure, a focus traversal will be attempted. The requestFocusNoSearch method returns false if it has not been measured. This is because every change of focus or focus can be accompanied by a switch such as background drawable, statelistDrawable, etc. There’s no point in doing this without measuring it.

Therefore, in order to make up for the previous rejection of focus, the restoreDefaultFocus behavior is reperformed for requestFocus processing.

  • 2. If there is a window focus and the FLAG_LOCAL_FOCUS_MODE flag is not turned on (this is a special case and is typically only available in startingWindow snapshots).

The onPostWindowFocus method of InputMethodManager is called to start the soft keyboard service with android.view.inputMethod action.

Ontouch process

if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) ! = 0) { reportNextDraw(); } boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || ! isViewVisible; if (! cancelDraw && ! newSurface) { if (mPendingTransitions ! = null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).startChangingAnimations(); } mPendingTransitions.clear(); } performDraw(); } else { if (isViewVisible) { scheduleTraversals(); } else if (mPendingTransitions ! = null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).endChangingAnimations(); } mPendingTransitions.clear(); } } mIsInTraversal = false;Copy the code
  • 1. Determine that if the draw method is called for the first time, reportNextDraw is called.
    private void reportNextDraw() {
        if (mReportNextDraw == false) {
            drawPending();
        }
        mReportNextDraw = true;
    }

    void drawPending() {
        mDrawsNeededToReport++;
    }
Copy the code

You can see that you’re actually setting mReportNextDraw to true. Let’s review that the first two processes, mReportNextDraw, participate in the determination of flag bits. To execute onMeasure and onLayout, mStop is false and mReportNextDraw is true. As long as one is satisfied, it will be executed.

The purpose of doing this is to ensure that the onDraw method is called once. Why is that? PerformDraw is the entry point to the entire Draw process. In this entry, however, cancelDraw must be false and newSurface must be false.

Note that newSurface is true for the first rendering because a newSurface is being added. If the serial port is visible, scheduleTraversals is called to execute the next Loop. Otherwise, determine if there is a LayoutTransitions Layout animation that needs to be executed.

So the first Looper does not go to onDraw, and the View draws onDraw after the second Looper.

ViewRootImpl performDraw

private void performDraw() { if (mAttachInfo.mDisplayState == Display.STATE_OFF && ! mReportNextDraw) { return; } else if (mView == null) { return; } final boolean fullRedrawNeeded = mFullRedrawNeeded || mReportNextDraw; mFullRedrawNeeded = false; mIsDrawing = true; Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw"); boolean usingAsyncReport = false; if (mReportNextDraw && mAttachInfo.mThreadedRenderer ! = null && mAttachInfo.mThreadedRenderer.isEnabled()) { usingAsyncReport = true; mAttachInfo.mThreadedRenderer.setFrameCompleteCallback((long frameNr) -> { pendingDrawFinished(); }); } try { boolean canUseAsync = draw(fullRedrawNeeded); if (usingAsyncReport && ! canUseAsync) { mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null); usingAsyncReport = false; } } finally { mIsDrawing = false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } if (mAttachInfo.mPendingAnimatingRenderNodes ! = null) { final int count = mAttachInfo.mPendingAnimatingRenderNodes.size(); for (int i = 0; i < count; i++) { mAttachInfo.mPendingAnimatingRenderNodes.get(i).endAllAnimators(); } mAttachInfo.mPendingAnimatingRenderNodes.clear(); } if (mReportNextDraw) { mReportNextDraw = false; if (mWindowDrawCountDown ! = null) { try { mWindowDrawCountDown.await(); } catch (InterruptedException e) { Log.e(mTag, "Window redraw count down interrupted!" ); } mWindowDrawCountDown = null; } if (mAttachInfo.mThreadedRenderer ! = null) { mAttachInfo.mThreadedRenderer.setStopped(mStopped); } if (mSurfaceHolder ! = null && mSurface.isValid()) { SurfaceCallbackHelper sch = new SurfaceCallbackHelper(this::postDrawFinished); SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks); } else if (! usingAsyncReport) { if (mAttachInfo.mThreadedRenderer ! = null) { mAttachInfo.mThreadedRenderer.fence(); } pendingDrawFinished(); }}}Copy the code

The whole process can be abstracted as follows: for software rendering:

  • 1. Call draw to traverse the View hierarchy.
  • 2. Call pendingDrawFinished in the surfaceRedrawNeededAsync Callback of SurfaceHolder.Callback if Surface is valid.
  • 3. If synchronous rendering is forced, pendingDrawFinished is called directly.

For hardware rendering:

  • 1. Call draw to traverse the View hierarchy.
  • 2. Execute the pendingDrawFinished method by listening to the setFrameCompleteCallback callback of the mThreadedRenderer.

Let’s focus first on the software rendering process. So draw and pendingDrawFinished.

ViewRootImpl draw

private boolean draw(boolean fullRedrawNeeded) { Surface surface = mSurface; if (! surface.isValid()) { return false; } if (! sFirstDrawComplete) { synchronized (sFirstDrawHandlers) { sFirstDrawComplete = true; final int count = sFirstDrawHandlers.size(); for (int i = 0; i< count; i++) { mHandler.post(sFirstDrawHandlers.get(i)); } } } scrollToRectOrFocus(null, false); if (mAttachInfo.mViewScrollChanged) { mAttachInfo.mViewScrollChanged = false; mAttachInfo.mTreeObserver.dispatchOnScrollChanged(); } boolean animating = mScroller ! = null && mScroller.computeScrollOffset(); final int curScrollY; if (animating) { curScrollY = mScroller.getCurrY(); } else { curScrollY = mScrollY; } if (mCurScrollY ! = curScrollY) { mCurScrollY = curScrollY; fullRedrawNeeded = true; if (mView instanceof RootViewSurfaceTaker) { ((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY); } } final float appScale = mAttachInfo.mApplicationScale; final boolean scalingRequired = mAttachInfo.mScalingRequired; final Rect dirty = mDirty; if (mSurfaceHolder ! = null) { dirty.setEmpty(); if (animating && mScroller ! = null) { mScroller.abortAnimation(); } return false; } if (fullRedrawNeeded) { mAttachInfo.mIgnoreDirtyState = true; Dirty. Set (0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); } mAttachInfo.mTreeObserver.dispatchOnDraw(); int xOffset = -mCanvasOffsetX; int yOffset = -mCanvasOffsetY + curScrollY; final WindowManager.LayoutParams params = mWindowAttributes; final Rect surfaceInsets = params ! = null ? params.surfaceInsets : null; if (surfaceInsets ! = null) { xOffset -= surfaceInsets.left; yOffset -= surfaceInsets.top; dirty.offset(surfaceInsets.left, surfaceInsets.right); }... mAttachInfo.mDrawingTime = mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS; boolean useAsyncReport = false; if (! dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) { if (mAttachInfo.mThreadedRenderer ! = null && mAttachInfo.mThreadedRenderer.isEnabled()) { boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested; mInvalidateRootRequested = false; mIsAnimating = false; if (mHardwareYOffset ! = yOffset || mHardwareXOffset ! = xOffset) { mHardwareYOffset = yOffset; mHardwareXOffset = xOffset; invalidateRoot = true; } if (invalidateRoot) { mAttachInfo.mThreadedRenderer.invalidateRoot(); } dirty.setEmpty(); final boolean updated = updateContentDrawBounds(); if (mReportNextDraw) { mAttachInfo.mThreadedRenderer.setStopped(false); } if (updated) { requestDrawWindow(); } useAsyncReport = true; final FrameDrawingCallback callback = mNextRtFrameCallback; mNextRtFrameCallback = null; mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback); } else { if (mAttachInfo.mThreadedRenderer ! = null && ! mAttachInfo.mThreadedRenderer.isEnabled() && mAttachInfo.mThreadedRenderer.isRequested() && mSurface.isValid()) { try { mAttachInfo.mThreadedRenderer.initializeIfNeeded( mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets); } catch (OutOfResourcesException e) { handleOutOfResourcesException(e); return false; } mFullRedrawNeeded = true; scheduleTraversals(); return false; } if (! drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets)) { return false; } } } if (animating) { mFullRedrawNeeded = true; scheduleTraversals(); } return useAsyncReport; }Copy the code

The following process has been roughly completed:

  • 1. If the surface is invalid, return directly
    1. SFirstDrawHandlers this stores the runnable static object. The attach method is actually called after the ActivityThread has started and is added by addFirstDrawHandler just to start jit mode.
  • 3. ScrollToRectOrFocus handles the sliding area or focus area. If there is a sliding is callback TreeObserver. DispatchOnScrollChanged. Then the global mScroller computeScrollOffset is used to determine whether sliding animation is required. If animation needs to be executed, the onRootViewScrollYChanged of DeocView is called for animation execution on the Y-axis.
  • 4. Use the dispatchOnDraw of the ViewTreeObserver to start distributing the listener that Draw starts drawing.
  • 5. Determine whether there is offset on the surface, correct the dirty area once, and add the offset.

Next comes the hardware rendering and software rendering branches. But the process of calling Draw further requires several conditions: the dirty area is not empty, the animation needs to be performed, and the secondary service has a focus change

  • 6. If the ThreadedRenderer is not empty and available. ThreadedRenderer calls back to ViewRootImpl via onPreDraw to update mHardwareYOffset, mHardwareXOffset. If these two parameters are changed, then the whole hardware rendering area changes have taken place, need to traverse from scratch once all of the areas is set to null and void area, mThreadedRenderer. InvalidateRoot.

Finally, the threadedrenderer.draw method is called to perform the hardware rendering. And set the callback set in via registerRtFrameCallback to the ThreadedRenderer.

  • 7. If the ThreadedRenderer is unavailable but not empty, the ThreadedRenderer needs to be initialized, and scheduleTraversals need to be invoked for hardware rendering in the next rendering process.
  • 8. If none of the above conditions is met, it indicates that software rendering is performed, and drawSoftware is called for software rendering.
  • 9. If the draw method is not allowed to traverse the global View tree, then determine whether sliding animation is required and call scheduleTraversals to proceed to the next drawing.

This article will leave hardware rendering behind and take a look at what is done in software rendering drawSoftware. And what does scrollToRectOrFocus do in the slide?

ViewRootImpl scrollToRectOrFocus

boolean scrollToRectOrFocus(Rect rectangle, boolean immediate) { final Rect ci = mAttachInfo.mContentInsets; final Rect vi = mAttachInfo.mVisibleInsets; int scrollY = 0; boolean handled = false; if (vi.left > ci.left || vi.top > ci.top || vi.right > ci.right || vi.bottom > ci.bottom) { final View focus = mView.findFocus(); if (focus == null) { return false; } View lastScrolledFocus = (mLastScrolledFocus ! = null) ? mLastScrolledFocus.get() : null; if (focus ! = lastScrolledFocus) { rectangle = null; } if (focus == lastScrolledFocus && ! mScrollMayChange && rectangle == null) { } else { mLastScrolledFocus = new WeakReference<View>(focus); mScrollMayChange = false; if (focus.getGlobalVisibleRect(mVisRect, null)) { if (rectangle == null) { focus.getFocusedRect(mTempRect); if (mView instanceof ViewGroup) { ((ViewGroup) mView).offsetDescendantRectToMyCoords( focus, mTempRect); } } else { mTempRect.set(rectangle); } if (mTempRect.intersect(mVisRect)) { if (mTempRect.height() > (mView.getHeight()-vi.top-vi.bottom)) { } else if (mTempRect.top < vi.top) { scrollY = mTempRect.top - vi.top; } else if (mTempRect.bottom > (mView.getHeight()-vi.bottom)) { scrollY = mTempRect.bottom - (mView.getHeight()-vi.bottom); } else { scrollY = 0; } handled = true; } } } } if (scrollY ! = mScrollY) { if (! immediate) { if (mScroller == null) { mScroller = new Scroller(mView.getContext()); } mScroller.startScroll(0, mScrollY, 0, scrollY-mScrollY); } else if (mScroller ! = null) { mScroller.abortAnimation(); } mScrollY = scrollY; } return handled; }Copy the code

You can see that in this process you’re actually dealing with two areas: the mVisibleInsets visual area and the mContentInsets content area.

In fact, the process is to start from the root node to find the focus, and then the entire image freezes at the focus. Because mVisibleInsets are generally the size of the outgoing scanning area on the screen, but the content area is not necessarily the same. The content may exceed the screen size, so it will be positioned by mScroller sliding.

The calculation principle is as follows, divided into two cases:

  • 1. The top of the viewable area is lower than the top of the view with focus, indicating that the view is out of the screen and needs to be swiped down:

scrollY = mTempRect.top – vi.top;

  • 2. If the bottom of the focus view is lower than that of the viewable area, it means that you need to slide up. Note that after sliding, you need to display the view, so the sliding distance should be subtracted from the height of the view:

scrollY = mTempRect.bottom – (mView.getHeight()-vi.bottom); ScrollY = mtemprect.bottom +vi. Bottom-mview.getheight (); scrollY = mtemprect.bottom +vi. Bottom-mview.getheight ();

ViewRootImpl drawSoftware

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty, Rect surfaceInsets) { final Canvas canvas; int dirtyXOffset = xoff; int dirtyYOffset = yoff; if (surfaceInsets ! = null) { dirtyXOffset += surfaceInsets.left; dirtyYOffset += surfaceInsets.top; } try { dirty.offset(-dirtyXOffset, -dirtyYOffset); final int left = dirty.left; final int top = dirty.top; final int right = dirty.right; final int bottom = dirty.bottom; canvas = mSurface.lockCanvas(dirty); if (left ! = dirty.left || top ! = dirty.top || right ! = dirty.right || bottom ! = dirty.bottom) { attachInfo.mIgnoreDirtyState = true; } canvas.setDensity(mDensity); } catch (Surface.OutOfResourcesException e) { handleOutOfResourcesException(e); return false; } catch (IllegalArgumentException e) { mLayoutRequested = true; // ask wm for a new surface next time. return false; } finally { dirty.offset(dirtyXOffset, dirtyYOffset); // Reset to the original value. } try { if (! canvas.isOpaque() || yoff ! = 0 || xoff ! = 0) { canvas.drawColor(0, PorterDuff.Mode.CLEAR); } dirty.setEmpty(); mIsAnimating = false; mView.mPrivateFlags |= View.PFLAG_DRAWN; try { canvas.translate(-xoff, -yoff); if (mTranslator ! = null) { mTranslator.translateCanvas(canvas); } canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0); attachInfo.mSetIgnoreDirtyState = false; mView.draw(canvas); drawAccessibilityFocusedDrawableIfNeeded(canvas); } finally { if (! attachInfo.mSetIgnoreDirtyState) { attachInfo.mIgnoreDirtyState = false; } } } finally { try { surface.unlockCanvasAndPost(canvas); } catch (IllegalArgumentException e) { Log.e(mTag, "Could not unlock surface", e); mLayoutRequested = true; // ask wm for a new surface next time. //noinspection ReturnInsideFinallyBlock return false; } } return true; }Copy the code

The logic is similar to the hardware rendering logic above:

  • 1. The entire dirty area is also offset based on the global surface offset

  • 2. Map a Canvas object through the surface. lockCanvas method, and all subsequent drawing actions will be performed on this Canvas object.

  • 3. After obtaining Canvas, as it is an offset of the entire surface, Canvas, as the drawing object mapped from the surface, also needs to be offset once.

  • 4. Call the draw method of the DecorView and start traversing the entire View tree.

  • 5. Through the whole tree View, will explain all of the information has been know to Canvas, can through the surface. The unlockCanvasAndPost, with record in the SkCanvas pixel data sent to SF, render into the screen.

DecorView draw
    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);

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

You can see that the entire DecorView is actually drawing the menu bar’s drawable onto the Canvas by calling the parent’s Draw method.

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; int saveCount; if (! dirtyOpaque) { drawBackground(canvas); } // skip step 2 & 5 if possible (common case) final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) ! = 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) ! = 0; if (! verticalEdges && ! horizontalEdges) { if (! dirtyOpaque) onDraw(canvas); dispatchDraw(canvas); drawAutofilledHighlight(canvas); // Overlay is part of the content and draws beneath Foreground if (mOverlay ! = null && ! mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } onDrawForeground(canvas); drawDefaultFocusHighlight(canvas); return; } boolean drawTop = false; boolean drawBottom = false; boolean drawLeft = false; boolean drawRight = false; Float topFadeStrength = 0.0 f; Float bottomFadeStrength = 0.0 f; Float leftFadeStrength = 0.0 f; Float rightFadeStrength = 0.0 f; 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) { if (drawTop) { canvas.saveUnclippedLayer(left, top, right, top + length); } if (drawBottom) { canvas.saveUnclippedLayer(left, bottom - length, right, bottom); } if (drawLeft) { canvas.saveUnclippedLayer(left, top, left + length, bottom); } if (drawRight) { canvas.saveUnclippedLayer(right - length, top, right, bottom); } } 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); drawAutofilledHighlight(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); }Copy the code

Roughly speaking, the DRAW method is divided into the following steps:

  • 1. First check the flag bits opened in mPrivateFlags in the draw method. Remember from the previous article that the PFLAG_DIRTY_MASK mask actually controls the dirty and transparent flags.
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && (mAttachInfo == null || ! mAttachInfo.mIgnoreDirtyState); mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;Copy the code

Check whether PFLAG_DIRTY_OPAQUE is enabled. If dirtyOpaque is enabled, it is true. Turning on the PFLAG_DRAWN flag indicates that the View has already called the draw method.

  • 2. If dirtyOpaque is false, call drawBackground to draw the background.

  • 3. Verify whether there are horizontal and vertical edge shadows to be drawn. If not, perform the following three processes:

    • 1. Execute the onDraw process rewritten by the View to draw
    • 2. DispatchDraw dispatches drawing actions to child views
    • 3. Check if there are overlays, then draw dispatchDraw for each View’s float layer
    • 4. OnDrawForeground drawable
    • 5. DrawDefaultFocusHighlight draw focus highlighted by default.
  • 4. To draw pulleys in the four directions of up, down, left and right, perform the following steps:

    • 1. Calculate the upper, lower, and left four directions of the pulley, and calculate its length according to whether it is horizontal or vertical
    • 2. Draw this piece of content as the unclipped area of the Canvas to the outside area
    • 3. Perform steps 3.1 and 3.2
    • 4. Rotate the pulley according to the four directions drawn up, down and left
    • 5. Perform steps 3.3 to 3.5

We focus on what is accomplished in the core actions 3.1onDraw and 3.2dispatchDraw and drawBackground.

View drawBackground
private void drawBackground(Canvas canvas) { final Drawable background = mBackground; if (background == null) { return; } setBackgroundBounds(); // Attempt to use a display list if requested. if (canvas.isHardwareAccelerated() && mAttachInfo ! = null && mAttachInfo.mThreadedRenderer ! = null) { mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode); final RenderNode renderNode = mBackgroundRenderNode; if (renderNode ! = null && renderNode.isValid()) { setBackgroundRenderNodeProperties(renderNode); ((DisplayListCanvas) canvas).drawRenderNode(renderNode); return; } } final int scrollX = mScrollX; final int scrollY = mScrollY; if ((scrollX | scrollY) == 0) { background.draw(canvas); } else { canvas.translate(scrollX, scrollY); background.draw(canvas); canvas.translate(-scrollX, -scrollY); }}Copy the code

You can see that the logic is divided into hardware rendering and software rendering:

  • Hardware rendering first uses getDrawableRenderNode to get a Drawable renderNode and then calls Canvas drawRenderNode. As you can see from the TextureView article I analyzed earlier on hardware rendering, Canvas is essentially DisplayListCanvas.
  • Software rendering calls the draw method of drawble to draw pixels to canvas.
getDrawableRenderNode
    private RenderNode getDrawableRenderNode(Drawable drawable, RenderNode renderNode) {
        if (renderNode == null) {
            renderNode = RenderNode.create(drawable.getClass().getName(), this);
        }

        final Rect bounds = drawable.getBounds();
        final int width = bounds.width();
        final int height = bounds.height();
        final DisplayListCanvas canvas = renderNode.start(width, height);

        canvas.translate(-bounds.left, -bounds.top);

        try {
            drawable.draw(canvas);
        } finally {
            renderNode.end(canvas);
        }

        renderNode.setLeftTopRightBottom(bounds.left, bounds.top, bounds.right, bounds.bottom);
        renderNode.setProjectBackwards(drawable.isProjected());
        renderNode.setProjectionReceiver(true);
        renderNode.setClipToBounds(false);
        return renderNode;
    }
Copy the code

We can see that when drawing a hardware-rendered drawable object, it becomes a RenderNode. After calling start, we get the drawable object corresponding to the DisplayListCanvas. The information is drawn to the DisplayListCanvas and renderNode is returned.

Add the drawable’s renderNode to the Canvas of the current View.

ViewGroup dispatchDraw

View defaults to leaving an empty method onDraw. Let’s see what we do in dispatchDraw.

protected void dispatchDraw(Canvas canvas) { boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode); final int childrenCount = mChildrenCount; final View[] children = mChildren; int flags = mGroupFlags; if ((flags & FLAG_RUN_ANIMATION) ! = 0 && canAnimate()) { final boolean buildCache = ! isHardwareAccelerated(); for (int i = 0; i < childrenCount; i++) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { final LayoutParams params = child.getLayoutParams(); attachLayoutAnimationParameters(child, params, i, childrenCount); bindLayoutAnimation(child); } } final LayoutAnimationController controller = mLayoutAnimationController; if (controller.willOverlap()) { mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE; } controller.start(); mGroupFlags &= ~FLAG_RUN_ANIMATION; mGroupFlags &= ~FLAG_ANIMATION_DONE; if (mAnimationListener ! = null) { mAnimationListener.onAnimationStart(controller.getAnimation()); } } int clipSaveCount = 0; final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK; if (clipToPadding) { clipSaveCount = canvas.save(Canvas.CLIP_SAVE_FLAG); canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop, mScrollX + mRight - mLeft - mPaddingRight, mScrollY + mBottom - mTop - mPaddingBottom); } // We will draw our child's animation, let's reset the flag mPrivateFlags &= ~PFLAG_DRAW_ANIMATION; mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED; boolean more = false; final long drawingTime = getDrawingTime(); if (usingRenderNodeProperties) canvas.insertReorderBarrier(); final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size(); int transientIndex = transientCount ! = 0? 0:1; final ArrayList<View> preorderedList = usingRenderNodeProperties ? null : buildOrderedChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); for (int i = 0; i < childrenCount; i++) { 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; } } final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() ! = null) { more |= drawChild(canvas, child, drawingTime); } } while (transientIndex >= 0) { final View transientChild = mTransientViews.get(transientIndex); if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || transientChild.getAnimation() ! = null) { more |= drawChild(canvas, transientChild, drawingTime); } transientIndex++; if (transientIndex >= transientCount) { break; } } if (preorderedList ! = null) preorderedList.clear(); // Draw any disappearing views that have animations if (mDisappearingChildren ! = null) { final ArrayList<View> disappearingChildren = mDisappearingChildren; final int disappearingCount = disappearingChildren.size() - 1; // Go backwards -- we may delete as animations finish for (int i = disappearingCount; i >= 0; i--) { final View child = disappearingChildren.get(i); more |= drawChild(canvas, child, drawingTime); } } if (usingRenderNodeProperties) canvas.insertInorderBarrier(); if (clipToPadding) { canvas.restoreToCount(clipSaveCount); } flags = mGroupFlags; if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) { invalidate(true); } if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 && mLayoutAnimationController.isDone() && ! more) { mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER; final Runnable end = new Runnable() { @Override public void run() { notifyAnimationListener(); }}; post(end); }}Copy the code

This process does several things:

  • 1. Check whether the FLAG_RUN_ANIMATION flag is enabled and Layout is allowed. BindLayoutAnimation sets the Layout animation for each child View by iterating through all visible child views in the viewGroup.
    private void bindLayoutAnimation(View child) {
        Animation a = mLayoutAnimationController.getAnimationForView(child);
        child.setAnimation(a);
    }
Copy the code
    1. LayoutAnimationController control the Layout of the animation controller calls to start to start the animation, and callback to monitor.
  • 3. Check whether the content area is trimmed by padding, which is enabled by default. In this case, the slide area, the padding area, and the viewgroup area will be clipped, rather than all drawn on the Canvas.

            canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
                    mScrollX + mRight - mLeft - mPaddingRight,
                    mScrollY + mBottom - mTop - mPaddingBottom);
Copy the code
  • 4. Check whether hardware acceleration is enabled. If not, buildOrderedChildList inserts all child views under the current ViewGroup on the Z-axis to see who will be drawn further up. In this process, the smaller the value on the Z-axis, the more the drawChild method is called first to draw to the canvas, that is, the lower the level, which is overlaid by other child views. The temporary View added via addTransientView is processed before processing the drawChild method corresponding to each child. This method is very rare. You can view it as a temporary animation effect without participating in the onMeasure and onLayout of the view, but it will be drawn. You need to remove it manually.
  • 5. Draw the disappearing temporary View added via addDisappearingView.
  • 6. FLAG_NOTIFY_ANIMATION_LISTENER, FLAG_ANIMATION_DONE are off, Layout animation is complete, and all drawchildren return false. NotifyAnimationListener is called at the start of the next Looper to notify the listener that the animation is complete.

The whole core is the drawChild method, so what does drawChild do?

drawChild

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

The core calls the View’s draw method. But notice that this draw method is not quite the same as the draw method above.

    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();

        boolean drawingWithRenderNode = mAttachInfo != null
                && mAttachInfo.mHardwareAccelerated
                && hardwareAcceleratedCanvas;

        boolean more = false;
        final boolean childHasIdentityMatrix = hasIdentityMatrix();
        final int parentFlags = parent.mGroupFlags;

        if ((parentFlags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) != 0) {
            parent.getChildTransformation().clear();
            parent.mGroupFlags &= ~ViewGroup.FLAG_CLEAR_TRANSFORMATION;
        }

        Transformation transformToApply = null;
        boolean concatMatrix = false;
        final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
        final Animation a = getAnimation();
        if (a != null) {
            more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
            concatMatrix = a.willChangeTransformationMatrix();
            if (concatMatrix) {
                mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
            }
            transformToApply = parent.getChildTransformation();
        } else {
            if ((mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_TRANSFORM) != 0) {
                // No longer animating: clear out old animation matrix
                mRenderNode.setAnimationMatrix(null);
                mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
            }
            if (!drawingWithRenderNode
                    && (parentFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
                final Transformation t = parent.getChildTransformation();
                final boolean hasTransform = parent.getChildStaticTransformation(this, t);
                if (hasTransform) {
                    final int transformType = t.getTransformationType();
                    transformToApply = transformType != Transformation.TYPE_IDENTITY ? t : null;
                    concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0;
                }
            }
        }

        concatMatrix |= !childHasIdentityMatrix;

        // Sets the flag as early as possible to allow draw() implementations
        // to call invalidate() successfully when doing animations
        mPrivateFlags |= PFLAG_DRAWN;

        if (!concatMatrix &&
                (parentFlags & (ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS |
                        ViewGroup.FLAG_CLIP_CHILDREN)) == ViewGroup.FLAG_CLIP_CHILDREN &&
                canvas.quickReject(mLeft, mTop, mRight, mBottom, Canvas.EdgeType.BW) &&
                (mPrivateFlags & PFLAG_DRAW_ANIMATION) == 0) {
            mPrivateFlags2 |= PFLAG2_VIEW_QUICK_REJECTED;
            return more;
        }
        mPrivateFlags2 &= ~PFLAG2_VIEW_QUICK_REJECTED;

        if (hardwareAcceleratedCanvas) {
            // Clear INVALIDATED flag to allow invalidation to occur during rendering, but
            // retain the flag's value temporarily in the mRecreateDisplayList flag
            mRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) != 0;
            mPrivateFlags &= ~PFLAG_INVALIDATED;
        }

        RenderNode renderNode = null;
        Bitmap cache = null;
        int layerType = getLayerType(); // TODO: signify cache state with just 'cache' local
        if (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) {
             if (layerType != LAYER_TYPE_NONE) {
                 // If not drawing with RenderNode, treat HW layers as SW
                 layerType = LAYER_TYPE_SOFTWARE;
                 buildDrawingCache(true);
            }
            cache = getDrawingCache(true);
        }

        if (drawingWithRenderNode) {
            renderNode = updateDisplayListIfDirty();
            if (!renderNode.isValid()) {
                renderNode = null;
                drawingWithRenderNode = false;
            }
        }

        int sx = 0;
        int sy = 0;
        if (!drawingWithRenderNode) {
            computeScroll();
            sx = mScrollX;
            sy = mScrollY;
        }

        final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;
        final boolean offsetForScroll = cache == null && !drawingWithRenderNode;

        int restoreTo = -1;
        if (!drawingWithRenderNode || transformToApply != null) {
            restoreTo = canvas.save();
        }
        if (offsetForScroll) {
            canvas.translate(mLeft - sx, mTop - sy);
        } else {
            if (!drawingWithRenderNode) {
                canvas.translate(mLeft, mTop);
            }
            if (scalingRequired) {
                if (drawingWithRenderNode) {
                    // TODO: Might not need this if we put everything inside the DL
                    restoreTo = canvas.save();
                }
                // mAttachInfo cannot be null, otherwise scalingRequired == false
                final float scale = 1.0f / mAttachInfo.mApplicationScale;
                canvas.scale(scale, scale);
            }
        }

        float alpha = drawingWithRenderNode ? 1 : (getAlpha() * getTransitionAlpha());
        if (transformToApply != null
                || alpha < 1
                || !hasIdentityMatrix()
                || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
            if (transformToApply != null || !childHasIdentityMatrix) {
                int transX = 0;
                int transY = 0;

                if (offsetForScroll) {
                    transX = -sx;
                    transY = -sy;
                }

                if (transformToApply != null) {
                    if (concatMatrix) {
                        if (drawingWithRenderNode) {
                            renderNode.setAnimationMatrix(transformToApply.getMatrix());
                        } else {

                            canvas.translate(-transX, -transY);
                            canvas.concat(transformToApply.getMatrix());
                            canvas.translate(transX, transY);
                        }
                        parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
                    }

                    float transformAlpha = transformToApply.getAlpha();
                    if (transformAlpha < 1) {
                        alpha *= transformAlpha;
                        parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
                    }
                }

                if (!childHasIdentityMatrix && !drawingWithRenderNode) {
                    canvas.translate(-transX, -transY);
                    canvas.concat(getMatrix());
                    canvas.translate(transX, transY);
                }
            }

            if (alpha < 1 || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
                if (alpha < 1) {
                    mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_ALPHA;
                } else {
                    mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_ALPHA;
                }
                parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
                if (!drawingWithDrawingCache) {
                    final int multipliedAlpha = (int) (255 * alpha);
                    if (!onSetAlpha(multipliedAlpha)) {
                        if (drawingWithRenderNode) {
                            renderNode.setAlpha(alpha * getAlpha() * getTransitionAlpha());
                        } else if (layerType == LAYER_TYPE_NONE) {
                            canvas.saveLayerAlpha(sx, sy, sx + getWidth(), sy + getHeight(),
                                    multipliedAlpha);
                        }
                    } else {
                        mPrivateFlags |= PFLAG_ALPHA_SET;
                    }
                }
            }
        } else if ((mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {
            onSetAlpha(255);
            mPrivateFlags &= ~PFLAG_ALPHA_SET;
        }

        if (!drawingWithRenderNode) {
            if ((parentFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0 && cache == null) {
                if (offsetForScroll) {
                    canvas.clipRect(sx, sy, sx + getWidth(), sy + getHeight());
                } else {
                    if (!scalingRequired || cache == null) {
                        canvas.clipRect(0, 0, getWidth(), getHeight());
                    } else {
                        canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());
                    }
                }
            }

            if (mClipBounds != null) {
                // clip bounds ignore scroll
                canvas.clipRect(mClipBounds);
            }
        }

        if (!drawingWithDrawingCache) {
            if (drawingWithRenderNode) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
            } else {
                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 || mLayerPaint == null) {
                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.0f, 0.0f, cachePaint);
            } else {
                int layerPaintAlpha = mLayerPaint.getAlpha();
                if (alpha < 1) {
                    mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
                }
                canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
                if (alpha < 1) {
                    mLayerPaint.setAlpha(layerPaintAlpha);
                }
            }
        }

        if (restoreTo >= 0) {
            canvas.restoreToCount(restoreTo);
        }

        if (a != null && !more) {
            if (!hardwareAcceleratedCanvas && !a.getFillAfter()) {
                onSetAlpha(255);
            }
            parent.finishAnimatingView(this, a);
        }

        if (more && hardwareAcceleratedCanvas) {
            if (a.hasAlpha() && (mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {
                invalidate(true);
            }
        }

        mRecreateDisplayList = false;

        return more;
    }
Copy the code

The above method does one very important thing, which is to draw the cache of each View. There are two important flag bits in this process:

boolean drawingWithRenderNode = mAttachInfo ! = null && mAttachInfo.mHardwareAccelerated && hardwareAcceleratedCanvas;Copy the code

DrawingWithRenderNode Determines whether the RenderNode needs to be drawn by hardware.

final boolean drawingWithDrawingCache = cache ! = null && ! drawingWithRenderNode;Copy the code

Drawingdeposite cache Drawing a cache in a View is decided by turning hardware rendering off and a cached bitmap not being empty.

Here’s what it does:

  • 1. First check whether setAnimation sets a Transformation matrix to transform the animation into the View. If it does, call applyLegacyAnimation to make sure that the entire change animation refreshes within the scope of the View. TransformToApply sets Transformation to the parent container. If Animation is not set, check whether hardware rendering is turned off and FLAG_support_STATIC_doubled is enabled. That is, if the parent of the View has a static transformation matrix, it is updated to transformToApply.
  • 2. If mLayerType is LAYER_TYPE_SOFTWARE or hardware rendering is turned off, it is a software rendering, and buildDrawingCache is called to build a drawn cache. Get the cache bitmap into the cache with getDrawingCache.
  • 3. If drawingWithRenderNode is true, hardware rendering is being used. Call updateDisplayListIfDirty to update the dirty areas in the DisplayList.
  • 4. If drawingWithRenderNode is false, call computeScroll to calculate the sliding region and assign values to SX and SY.
  • 5. If there is a transformation matrix or hardware rendering is closed, the canvas save method is called to save the current state. If offsetForScroll is true(indicating software rendering and no cache), the translation distance will be calculated by calling the fourth step of canvas Translate method parameter.

Horizontal translation: mleft-sx Vertical translation: mtop-SY

This moves the origin of the drawing to a shifted, shifted position, and all subsequent drawing is based on that point

  • 6. If offsetForScroll is false, rendering cache or hardware rendering may be required, and only two things will be done: moving the origin of the current canvas, or scaling the entire canvas if necessary.
  • 7. If transformToApply is not null, the premise is. Found that opened the hardware rendering, call the RenderNode. SetAnimationMatrix matrix method to set animation. If closed, the Canvas origin after translation will be rolled back first, and the animation matrix will be merged before sliding.
  • 8. If alpha is less than 1 and a drawingcache is closed. Indicates that transparency processing can be performed in the current drawing result. If onSetAlpha is false, call RenderNode. setAlpha for hardware rendering or Canvas. saveLayerAlpha for software rendering. If draw cache is turned on. OnSetAlpha true indicates that the entire transparency is determined by the child View, so turn on the PFLAG_ALPHA_SET flag and wait for further processing.
  • 9. If the parent container has the FLAG_CLIP_CHILDREN flag on and the current View is not cached. The result of the current View needs to be clipped by the parent container: If a slide is done, the clipping area is as follows:

canvas.clipRect(sx, sy, sx + getWidth(), sy + getHeight()); ClipRect (0, 0, getWidth(), getHeight())); Caches are available: Canvas. ClipRect (0, 0, cache.getwidth (), cache.getheight ()); If you need to trim by edge: Canvas.cliprect (mClipBounds);

  • 10. DrawingWithDrawingCache if it is closed, and drawingWithRenderNode is on, call the DisplayListCanvas. DrawRenderNode (renderNode) method. The renderNode parameter is the updateDisplayListIfDirty method that generates a new DisplayListCanvas. Draw the result to the parent container’s DisplayListCanvas using the drawRenderNode.
  • 11. DrawingWithDrawingCache closed, drawingWithRenderNode is closed. In this case, draw directly. If the PFLAG_SKIP_DRAW flag is turned on, you need to drop the current View directly and call dispatchDraw to distribute the View drawing command. If not, the draw method is called. I’m going to call onDraw and dispatchDraw and distribute the View drawing method.
  • 12 drawingWithDrawingCache is open, and at the same time the cache buffer is not null, then the cache the results in the paint to the Canvas.
  • 13. When all subviews are drawn, the canvas. RestoreToCount method is called to restore the drawing state layer by layer. Mainly restore the origin of the drawing. Call finishAnimatingView of the parent container to clear all Animation and disappearingAnimation and call onAnimationEnd.

In addition to the Canvas operation (which I will discuss in a special Skia source code parsing section), there are several important methods:

  • BuildDrawingCache Builds a draw cache object
  • 2. Update the dirty area updateDisplayListIfDirty hardware rendering
buildDrawingCache
    public void buildDrawingCache(boolean autoScale) {
        if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?
                mDrawingCache == null : mUnscaledDrawingCache == null)) {
            try {
                buildDrawingCacheImpl(autoScale);
            } finally {
            }
        }
    }
Copy the code

You can see that’s actually calling buildDrawingCacheImpl.

buildDrawingCacheImpl
private void buildDrawingCacheImpl(boolean autoScale) { mCachingFailed = false; int width = mRight - mLeft; int height = mBottom - mTop; final AttachInfo attachInfo = mAttachInfo; final boolean scalingRequired = attachInfo ! = null && attachInfo.mScalingRequired; If (autoScale && scalingRequired) {width = (int) ((width * attachInfo mApplicationScale) + 0.5 f); Height = (int) ((height * attachInfo mApplicationScale) + 0.5 f); } final int drawingCacheBackgroundColor = mDrawingCacheBackgroundColor; final boolean opaque = drawingCacheBackgroundColor ! = 0 || isOpaque(); final boolean use32BitCache = attachInfo ! = null && attachInfo.mUse32BitDrawingCache; final long projectedBitmapSize = width * height * (opaque && ! use32BitCache ? 2, 4); final long drawingCacheSize = ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize(); if (width <= 0 || height <= 0 || projectedBitmapSize > drawingCacheSize) { if (width > 0 && height > 0) { Log.w(VIEW_LOG_TAG, getClass().getSimpleName() + " not displayed because it is" + " too large to fit into a software layer (or drawing cache), needs " + projectedBitmapSize + " bytes, only " + drawingCacheSize + " available"); } destroyDrawingCache(); mCachingFailed = true; return; } boolean clear = true; Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache; if (bitmap == null || bitmap.getWidth() ! = width || bitmap.getHeight() ! = height) { Bitmap.Config quality; if (! opaque) { // Never pick ARGB_4444 because it looks awful // Keep the DRAWING_CACHE_QUALITY_LOW flag just in case switch (mViewFlags & DRAWING_CACHE_QUALITY_MASK) { case DRAWING_CACHE_QUALITY_AUTO: case DRAWING_CACHE_QUALITY_LOW: case DRAWING_CACHE_QUALITY_HIGH: default: quality = Bitmap.Config.ARGB_8888; break; } } else { quality = use32BitCache ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; } if (bitmap ! = null) bitmap.recycle(); try { bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(), width, height, quality); bitmap.setDensity(getResources().getDisplayMetrics().densityDpi); if (autoScale) { mDrawingCache = bitmap; } else { mUnscaledDrawingCache = bitmap; } if (opaque && use32BitCache) bitmap.setHasAlpha(false); } catch (OutOfMemoryError e) { if (autoScale) { mDrawingCache = null; } else { mUnscaledDrawingCache = null; } mCachingFailed = true; return; } clear = drawingCacheBackgroundColor ! = 0; } Canvas canvas; if (attachInfo ! = null) { canvas = attachInfo.mCanvas; if (canvas == null) { canvas = new Canvas(); } canvas.setBitmap(bitmap); attachInfo.mCanvas = null; } else { canvas = new Canvas(bitmap); } if (clear) { bitmap.eraseColor(drawingCacheBackgroundColor); } computeScroll(); final int restoreCount = canvas.save(); if (autoScale && scalingRequired) { final float scale = attachInfo.mApplicationScale; canvas.scale(scale, scale); } canvas.translate(-mScrollX, -mScrollY); mPrivateFlags |= PFLAG_DRAWN; if (mAttachInfo == null || ! mAttachInfo.mHardwareAccelerated || mLayerType ! = LAYER_TYPE_NONE) { mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID; } if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; dispatchDraw(canvas); drawAutofilledHighlight(canvas); if (mOverlay ! = null && ! mOverlay.isEmpty()) { mOverlay.getOverlayView().draw(canvas); } } else { draw(canvas); } canvas.restoreToCount(restoreCount); canvas.setBitmap(null); if (attachInfo ! = null) { attachInfo.mCanvas = canvas; }}Copy the code
  • 1. Before creating a drawn cache bitmap, if one of the width and height of the current View is less than or equal to 0, or if the current View requires more memory than the maximum allowed cache View size. In this process, the cache calculation method after View drawing is as follows:

projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4);

You can see this process to determine if you need to be transparent and turn off the cache of 32, 2 bits per pixel, otherwise 4 bits.

If the calculated result is greater than MAXIMUM_DRAWING_CACHE_SIZE, the draw cache is destroyed.

private static final int MAXIMUM_DRAWING_CACHE_SIZE = 480 * 800 * 4; // ARGB8888
Copy the code

You can see that each View can only be 480 wide, 800 high and ARGB8888 mode memory size.

  • 2. If the View size changes, the bitmap.createBitmap method is called to create a Bitmap corresponding to the View size.
  • 3. If attachInfo is not empty, determine whether there is a global Canvas. If not, create a new Canvas and set the bitmap to the Canvas.
  • 4. Scale the cache bitmap if necessary. If you need to slide, move the origin of the entire Canvas.
  • 5. If PFLAG_SKIP_DRAW is turned on, dispatchDraw is called and the drawing process continues. If closed, the draw method is called, onDraw followed by dispatchDraw.

You can see that the processes skipped in the draw method above because they detect the presence of a draw cache are processed in this method.

updateDisplayListIfDirty
public RenderNode updateDisplayListIfDirty() { final RenderNode renderNode = mRenderNode; if (! canHaveDisplayList()) { return renderNode; } if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || ! renderNode.isValid() || (mRecreateDisplayList)) { if (renderNode.isValid() && ! mRecreateDisplayList) { mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; mPrivateFlags &= ~PFLAG_DIRTY_MASK; dispatchGetDisplayList(); return renderNode; // no work needed } mRecreateDisplayList = true; int width = mRight - mLeft; int height = mBottom - mTop; int layerType = getLayerType(); final DisplayListCanvas canvas = renderNode.start(width, height); try { if (layerType == LAYER_TYPE_SOFTWARE) { buildDrawingCache(true); Bitmap cache = getDrawingCache(true); if (cache ! = null) { canvas.drawBitmap(cache, 0, 0, mLayerPaint); } } else { computeScroll(); canvas.translate(-mScrollX, -mScrollY); mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; mPrivateFlags &= ~PFLAG_DIRTY_MASK; // Fast path for layouts with no backgrounds if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { dispatchDraw(canvas); drawAutofilledHighlight(canvas); if (mOverlay ! = null && ! mOverlay.isEmpty()) { mOverlay.getOverlayView().draw(canvas); } if (debugDraw()) { debugDrawFocus(canvas); } } else { draw(canvas); } } } finally { renderNode.end(canvas); setDisplayListProperties(renderNode); } } else { mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; mPrivateFlags &= ~PFLAG_DIRTY_MASK; } return renderNode; }Copy the code
  • 1. If canHaveDisplayList is false (that is, mThreadedRenderer is null), this function is returned.

If PFLAG_DRAWING_CACHE_VALID is off, or renderNode is invalid, or mRecreateDisplayList is false. The RenderNode Canvas is drawn.

  • 2. If renderNode is valid and there is no need to recreate the DisplayList of the entire hardware rendering (mRecreateDisplayList is false), it is not the first time that the renderNode has been drawn. DispatchGetDisplayList is called.

  • 3. The following scenario shows that it is the first time that renderNode is still in an invalid state. The initialization process is as follows:

    • Rendernode. start(width, height) Calls start to create a new DisplayListCanvas
    • 2. If LayerType is LAYER_TYPE_SOFTWARE, even if the Layer is in software render mode, if hardware render mode is enabled, It’s still going to set the current View’s drawing cache bitmap to the DisplayListCanvas using setBitmap.
    • 3. If it is not LAYER_TYPE_SOFTWARE, call computeScroll after sliding calculation. Return the entire RenderNode origin to its pre-slide state and flag PFLAG_DRAWN and PFLAG_DRAWING_CACHE_VALID.
    • 4. If PFLAG_SKIP_DRAW is turned on, dispatchDraw is called to distribute the draw process.
    • If PFLAG_SKIP_DRAW is not enabled, call draw, call onDraw, then call dispatchDraw.
  • 4. Call RenderNode. end(canvas) to finish drawing renderNode.

So what does dispatchGetDisplayList do when it’s not drawn the first time?

ViewGroup dispatchGetDisplayList
protected void dispatchGetDisplayList() { final int count = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < count; i++) { final View child = children[i]; if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() ! = null)) { recreateChildDisplayList(child); } } final int transientCount = mTransientViews == null ? 0 : mTransientIndices.size(); for (int i = 0; i < transientCount; ++i) { View child = mTransientViews.get(i); if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() ! = null)) { recreateChildDisplayList(child); } } if (mOverlay ! = null) { View overlayView = mOverlay.getOverlayView(); recreateChildDisplayList(overlayView); } if (mDisappearingChildren ! = null) { final ArrayList<View> disappearingChildren = mDisappearingChildren; final int disappearingCount = disappearingChildren.size(); for (int i = 0; i < disappearingCount; ++i) { final View child = disappearingChildren.get(i); recreateChildDisplayList(child); }}}Copy the code

And you can see that it’s actually quite simple, we’re recreateChildDisplayList for the 4 views.

  • 1. All visible child views or animated child views
  • 2. MTransientViews Temporary View added by using addTransientView
  • The floating layer of each View
  • 4. MDisappearingChildren is added via addDisappearingView to animate the View that is required when the View is removed.
ViewGroup recreateChildDisplayList
private void recreateChildDisplayList(View child) { child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) ! = 0; child.mPrivateFlags &= ~PFLAG_INVALIDATED; child.updateDisplayListIfDirty(); child.mRecreateDisplayList = false; }Copy the code

You can see that this is actually calling the updateDisplayListIfDirty method of the child View.

pendingDrawFinished

When everything has been processed, pendingDrawFinished is called. If the mdrawdedToReport count is 0, all commands that need to be drawn are completed. Finally, call reportDrawFinished.

void pendingDrawFinished() { if (mDrawsNeededToReport == 0) { throw new RuntimeException("Unbalanced drawPending/pendingDrawFinished calls"); } mDrawsNeededToReport--; if (mDrawsNeededToReport == 0) { reportDrawFinished(); }}Copy the code

reportDrawFinished

private void reportDrawFinished() { try { mDrawsNeededToReport = 0; mWindowSession.finishDrawing(mWindow); } catch (RemoteException e) { // Have fun! }}Copy the code

As you can see, reportDrawFinished is called to inform WindowSession that finishDrawing has been completed.

    public void finishDrawing(IWindow window) {
        if (WindowManagerService.localLOGV) Slog.v(
            TAG_WM, "IWindow finishDrawing called for " + window);
        mService.finishDrawingWindow(this, window);
    }
Copy the code

WMS finishDrawingWindow

void finishDrawingWindow(Session session, IWindow client) { final long origId = Binder.clearCallingIdentity(); try { synchronized (mWindowMap) { WindowState win = windowForClientLocked(session, client, false); if (win ! = null && win.mWinAnimator.finishDrawingLocked()) { if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) ! = 0) { win.getDisplayContent().pendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; } win.setDisplayLayoutNeeded(); mWindowPlacerLocked.requestTraversal(); } } } finally { Binder.restoreCallingIdentity(origId); }}Copy the code

SetDisplayLayoutNeeded for WindowState is called. Set mLayoutNeeded for DisplayContent to true.

Call the requestTraversal of the WindowSurfacePlacer. This method calls performSurfacePlacement in the handler of the AnimationHandler form animation.

conclusion

The entire onDraw entry executes the following methods:

  • 1. Perform the background drawing of the View
  • 2. Execute the onDraw process rewritten by the View to draw
  • 3. DispatchDraw dispatches drawing actions to child views
  • 4. Check if there are overlays, if there are, draw dispatchDraw of each View’s float layer
  • 5. OnDrawForeground drawable
  • 6. DrawDefaultFocusHighlight draw focus highlighted by default.

If the View is transparent, the onDraw method of the View will not be called.

After dispatchDraw dispatches the draw behavior, the drawChild method will be called for each child View’s draw method. The buildOrderedChildList insertion sort is also used to calculate the order of draws on the Z-axis, ensuring that drawChild is called on the Z-axis in order of size.

In this process, software rendering is accompanied by drawing a Bitmap cache. The maximum cache value that each View can request is:

(WIDTH/height)480 * (width/height)800 * 4(ARGB8888)

If you exceed this value, the cache will not appear, or will be destroyed until OOM passes.

When the execution is complete, WMS’s finishDrawingWindow must be called to tell WMS that the ViewrotimPL is finished drawing. This method sets DisplayContent’s mLayoutNeeded to true. This tells THE WMS that the next round of relayoutWindow of the WMS will allow the DisplayContent window to be traversed.

Author: yjy239 links: www.jianshu.com/p/a4fb6a02a… The copyright of the book belongs to the author. Commercial reprint please contact the author for authorization, non-commercial reprint please indicate the source.