关于android:UI绘制流程之Draw绘制流程

6次阅读

共计 21205 个字符,预计需要花费 54 分钟才能阅读完成。

前言

从后面的几篇文章,我门可能的到晓得 ui 的测量和布局,那么这次,咱们首先来关注下我门的 ui 是怎么具体画进去的。那么在这里咱们首先须要理解的是具体绘制的流程以及,paint 和 Canvas 在这两头所表演的角色

绘制流程

在之前的课程里咱们都提到了在 performTraversals 当中一次调用了 performMeasure,performLayout,performDraw 那么前两者咱们当初不关注,当初次要关注 draw 正在具体干嘛,那么咱们看到 ViewRootImpl. performDraw 办法看下他是如何实现具体绘制的在 performTraversals 中是这样

     // Remember if we must report the next draw.
    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) {
            // Try again
            scheduleTraversals();} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {for (int i = 0; i < mPendingTransitions.size(); ++i) {mPendingTransitions.get(i).endChangingAnimations();}
            mPendingTransitions.clear();}
    }

    mIsInTraversal = false;

在这里咱们能够看到一个关键点就是在 isViewVisible = true(也就是 view 为显示状态下,这里会在此发动一次 scheduleTraversals,所以,这也是为什么咱们的 onMeasure 会调用两次的起因)。 接着进入 performDraw 我门具体来探寻他做了什么

    private void performDraw() {if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {return;} else if (mView == null) {return;}

    final boolean fullRedrawNeeded = mFullRedrawNeeded;
    mFullRedrawNeeded = false;

    mIsDrawing = true;
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
    try {draw(fullRedrawNeeded);
    } finally {
        mIsDrawing = false;
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }

    // For whatever reason we didn't create a HardwareRenderer, end any
    // hardware animations that are now dangling
    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 we're using multi-thread renderer, wait for the window frame draws
        if (mWindowDrawCountDown != null) {
            try {mWindowDrawCountDown.await();
            } catch (InterruptedException e) {Log.e(mTag, "Window redraw count down interruped!");
            }
            mWindowDrawCountDown = null;
        }

        if (mAttachInfo.mThreadedRenderer != null) {mAttachInfo.mThreadedRenderer.fence();
            mAttachInfo.mThreadedRenderer.setStopped(mStopped);
        }

        if (LOCAL_LOGV) {Log.v(mTag, "FINISHED DRAWING:" + mWindowAttributes.getTitle());
        }

        if (mSurfaceHolder != null && mSurface.isValid()) {SurfaceCallbackHelper sch = new SurfaceCallbackHelper(this::postDrawFinished);
            SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();

            sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
        } else {pendingDrawFinished();
        }
    }
}

这里会看到调用了 draw 办法而该参数由 mFullRedrawNeeded 成员变量获取,它的作用是判断是否须要从新绘制全副视图,如果是第一次绘制视图,那么显然应该绘制所以的视图, 如果因为某些起因,导致了视图重绘,那么就没有必要绘制所有视图

   private void draw(boolean fullRedrawNeeded) {
    Surface surface = mSurface;
    if (!surface.isValid()) {return;}

    if (DEBUG_FPS) {trackFPS();
    }

    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;

    int resizeAlpha = 0;
    // 获取 mDirty,该值示意须要重绘的区域,就是之前咱们最先做的那个定位
    final Rect dirty = mDirty;
    if (mSurfaceHolder != null) {
        // The app owns the surface, we won't draw.
        dirty.setEmpty();
        if (animating && mScroller != null) {mScroller.abortAnimation();
        }
        return;
    }
    // 如果 fullRedrawNeeded 为真,则把 dirty 区域置为整个屏幕,示意整个视图都须要绘制
    // 第一次绘制流程,须要绘制所有视图
    if (fullRedrawNeeded) {
        mAttachInfo.mIgnoreDirtyState = true;
        dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
    }

    if (DEBUG_ORIENTATION || DEBUG_DRAW) {
        Log.v(mTag, "Draw" + mView + "/"
                + mWindowAttributes.getTitle()
                + ": dirty={" + dirty.left + "," + dirty.top
                + "," + dirty.right + "," + dirty.bottom + "} surface="
                + surface + "surface.isValid()=" + surface.isValid() + ", appScale:" +
                appScale + ", width=" + mWidth + ", height=" + mHeight);
    }

    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;

        // Offset dirty rect for surface insets.
        dirty.offset(surfaceInsets.left, surfaceInsets.right);
    }

    boolean accessibilityFocusDirty = false;
    final Drawable drawable = mAttachInfo.mAccessibilityFocusDrawable;
    if (drawable != null) {
        final Rect bounds = mAttachInfo.mTmpInvalRect;
        final boolean hasFocus = getAccessibilityFocusedRect(bounds);
        if (!hasFocus) {bounds.setEmpty();
        }
        if (!bounds.equals(drawable.getBounds())) {accessibilityFocusDirty = true;}
    }

    mAttachInfo.mDrawingTime =
            mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;

    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
            // If accessibility focus moved, always invalidate the root.
            boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested;
            mInvalidateRootRequested = false;

            // Draw with hardware renderer.
            mIsAnimating = false;

            if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {
                mHardwareYOffset = yOffset;
                mHardwareXOffset = xOffset;
                invalidateRoot = true;
            }

            if (invalidateRoot) {mAttachInfo.mThreadedRenderer.invalidateRoot();
            }

            dirty.setEmpty();

            // Stage the content drawn size now. It will be transferred to the renderer
            // shortly before the draw commands get send to the renderer.
            final boolean updated = updateContentDrawBounds();

            if (mReportNextDraw) {// report next draw overrides setStopped()
                // This value is re-sync'd to the value of mStopped
                // in the handling of mReportNextDraw post-draw.
                mAttachInfo.mThreadedRenderer.setStopped(false);
            }

            if (updated) {requestDrawWindow();
            }

            mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
        } else {
            // If we get here with a disabled & requested hardware renderer, something went
            // wrong (an invalidate posted right before we destroyed the hardware surface
            // for instance) so we should just bail out. Locking the surface with software
            // rendering at this point would lock it forever and prevent hardware renderer
            // from doing its job when it comes back.
            // Before we request a new frame we must however attempt to reinitiliaze the
            // hardware renderer if it's in requested state. This would happen after an
            // eglTerminate() for instance.
            if (mAttachInfo.mThreadedRenderer != null &&
                    !mAttachInfo.mThreadedRenderer.isEnabled() &&
                    mAttachInfo.mThreadedRenderer.isRequested()) {

                try {
                    mAttachInfo.mThreadedRenderer.initializeIfNeeded(mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
                } catch (OutOfResourcesException e) {handleOutOfResourcesException(e);
                    return;
                }

                mFullRedrawNeeded = true;
                scheduleTraversals();
                return;
            }

            if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {return;}
        }
    }

    if (animating) {
        mFullRedrawNeeded = true;
        scheduleTraversals();}
}

首先是先获取了 mDirty 值,这里保留了须要重绘的区域的信息,。接着依据 fullRedrawNeeded 来判断是否须要重置 dirty 区域,最初调用了 ViewRootImpl#drawSoftware 办法,

    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
        boolean scalingRequired, Rect dirty) {

    // Draw with software renderer.
    final Canvas canvas;
    try {
        final int left = dirty.left;
        final int top = dirty.top;
        final int right = dirty.right;
        final int bottom = dirty.bottom;
        // 锁定 canvas 区域,由 dirty 区域决定
        canvas = mSurface.lockCanvas(dirty);

        // The dirty rectangle can be modified by Surface.lockCanvas()( 这个巨型区域被锁定了批改)
        //noinspection ConstantConditions(没有查看恒定条件)if (left != dirty.left || top != dirty.top || right != dirty.right
                || bottom != dirty.bottom) {attachInfo.mIgnoreDirtyState = true;}

        // TODO: Do this in native(在本地设置密度)canvas.setDensity(mDensity);
    } catch (Surface.OutOfResourcesException e) {handleOutOfResourcesException(e);
        return false;
    } catch (IllegalArgumentException e) {Log.e(mTag, "Could not lock surface", e);
        // Don't assume this is due to out of memory, it could be
        // something else, and if it is something else then we could
        // kill stuff (or ourself) for no reason.
        mLayoutRequested = true;    // ask wm for a new surface next time.
        return false;
    }

    try {if (DEBUG_ORIENTATION || DEBUG_DRAW) {
            Log.v(mTag, "Surface" + surface + "drawing to bitmap w="
                    + canvas.getWidth() + ", h=" + canvas.getHeight());
            //canvas.drawARGB(255, 255, 0, 0);
        }

        // If this bitmap's format includes an alpha channel, we
        // need to clear it before drawing so that the child will
        // properly re-composite its drawing on a transparent
        // background. This automatically respects the clip/dirty region
        // or
        // If we are applying an offset, we need to clear the area
        // where the offset doesn't appear to avoid having garbage
        // left in the blank areas.
      对于下面的翻译是:如果位图的格局蕴含 alpha 通道,咱们
      须要在画之前革除它,以便子控件重新组合其图纸上的通明背景
      这将主动地对剪辑区域进行关联。或
      如果咱们申请抵销,咱们须要清理这个地区。在没有呈现偏移的状况下防止垃圾
      留在空白区域。if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {canvas.drawColor(0, PorterDuff.Mode.CLEAR);
        }

        dirty.setEmpty();
        mIsAnimating = false;
        mView.mPrivateFlags |= View.PFLAG_DRAWN;

        if (DEBUG_DRAW) {Context cxt = mView.getContext();
            Log.i(mTag, "Drawing: package:" + cxt.getPackageName() +
                    ", metrics=" + cxt.getResources().getDisplayMetrics() +
                    ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
        }
        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) {// Only clear the flag if it was not set during the mView.draw() call
                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;
        }

        if (LOCAL_LOGV) {Log.v(mTag, "Surface" + surface + "unlockCanvasAndPost");
        }
    }
    return true;
}

能够看出,首先是实例化了 Canvas 对象,而后锁定该 canvas 的区域,由 dirty 区域决定, 接着对 canvas 进行一系列的属性赋值,最初调用了 mView.draw(canvas) 办法,那么之前就讲过这里的 mView 就是咱们的 DectorView 所以是从 DectorView 顶层开始绘制 那么之前的一切都是在进行筹备一块画板具体的绘制切实 mView.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(画子 view)*      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);
    }

    // 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) {
        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);

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

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

        // Step 7, draw the default focus highlight
        drawDefaultFocusHighlight(canvas);

        if (debugDraw()) {debugDrawFocus(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);

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

    if (debugDraw()) {debugDrawFocus(canvas);
    }
}

能够看到,draw 过程比较复杂,然而逻辑非常清晰, 而官网正文也分明地阐明了每一步的做法。咱们首先来看一开始的标记位 dirtyOpaque,该标记位的作用是判断以后 View 是否是通明的,如果 View 是通明的,那么依据上面的逻辑能够看出,将不会执行一些步骤, 比方绘制背景、绘制内容等。 这样很容易了解,因为一个 View 既然是通明的,那就没必要绘制它了。 接着是绘制流程的六个步骤,这里先小结这六个步骤别离是什么,而后再开展来讲。

绘制流程的六个步骤:

  1. 对 View 的背景进行绘制
  2. 保留以后的图层信息
  3. 绘制 View 的内容
  4. 对 View 的子 View 进行绘制 (如果有子 View)
  5. 绘制 View 的褪色的边缘,相似于暗影成果
  6. 绘制 View 的装璜

1. 画背景

  private void drawBackground(Canvas canvas) {
    //mBackground 是该 View 的背景参数,比方背景色彩这里我门不去探索从何而来
    final Drawable background = mBackground;
    if (background == null) {return;}
    // 依据 View 四个布局参数来确定背景的边界
    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;
        }
    }
    // 获取以后 View 的 mScrollX 和 mScrollY 值
    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);
    }
}

  void setBackgroundBounds() {if (mBackgroundSizeChanged && mBackground != null) {
        // 设置背景四个参数
        mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
        mBackgroundSizeChanged = false;
        rebuildOutline();}
}

2. 第二步突变图像保留和第五步突变图像复原,明天不做解说。临时跳过,咱们前面文章 Canvas 外面细说,那么在这里看到第三步调用了 onDraw,View 中该办法是一个空实现,这里同理于之前的 onMeasure 和 onLayout 因为不同的 View 有着不同的内容,这须要咱们本人去实现,即在自定义 View 中重写该办法来实现

3. 在第四步的 dispatchDraw(canvas); 当中, 这个办法咱们会发现他在始终迭代子 view,这里我门是以 ViewGroup 为例(这个办法也一样,是由子 View 重写)

  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;
    // Only use the preordered list if not HW accelerated, since the HW pipeline will do the
    // draw reordering internally
    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) {
        // there may be additional transient views after the normal views
        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 (debugDraw()) {onDebugDraw(canvas);
    }

    if (clipToPadding) {canvas.restoreToCount(clipSaveCount);
    }

    // mGroupFlags might have been updated by drawChild()
    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) {
        // We want to erase the drawing cache and notify the listener after the
        // next frame is drawn because one extra invalidate() is caused by
        // drawChild() after the animation is over
        mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
        final Runnable end = new Runnable() {
           @Override
           public void run() {notifyAnimationListener();
           }
        };
        post(end);
    }
}

源码很长,这里简略讲下,外面次要遍历了所以子 View,每个子 View 都调用了 drawChild 这个办法,咱们找到这个办法

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

这里开始调用了子 view 的 draw,同样开始向下遍历。那么此时,其实同理于我门之前的测量和布局,父亲获得所有子控件开始遍历,调用子控件让子控件本人调用本人的 draw 开始绘制本人。逻辑很清晰,都是先设定绘制区域,而后利用 canvas 进行绘制。

总结

那么,到目前为止,View 的绘制流程也讲述结束了

我在解说这个 UI 绘制流程时次要目标是从源码剖析出原理,流程,透过流程晓得咱们能干嘛,咱们在对于自定义控件开发的时候,因为他顶层源码的流程,原理,失去咱们本人可能在这个体系当中表演什么角色。

从之前的几篇文章,咱们可能明确,最终,其实测量,布局,和绘制这三个流程最终都是调用到 onMeasure,onLayout,onDraw 让控件本人去实现的,只不过零碎组件他实现帮我门曾经依照它们本人的规定去实现了本人想要实现的成果,那么咱们业同样是依据依据程序,原理,去施加本人的业务,实现本人想要的自定义控件。
那么在这里 UI 绘制流程临时告一段落,对于图形是如何出现在我门的 UI 上,以及我门如何制作一些比拟丑陋的特效其实实际上是依赖于 Paint 组件和 Canvas,那么敬请期待下一篇 …

看完三件事❤️


如果你感觉这篇内容对你还蛮有帮忙,我想邀请你帮我三个小忙:

  1. 点赞,转发,有你们的『点赞和评论』,才是我发明的能源。
  2. 关注公众号 『小新聊 Android』,不定期分享原创常识。
  3. 同时能够期待后续文章 ing🚀
  4. 关注后回复【666】扫码即可获取 Android 进阶学习材料包
正文完
 0