View 的 draw 流程-再总结

看图说话


流程图

借用该链接:凶残的程序员-View 的工作流程 的两张图,来表示大致的工作流程。

时序图

ViewRootImpl Surface DecorView View ViewGroup 0,performTraversals 1,performDraw 1.1,draw 1.2,drawSoftware canvas = mSurface.lockCanvas(dirty) 2,mView.draw(canvas) 没有重写,直调父 3,super.draw(canvas) drawBackground(canvas) // 背景绘制 4,onDraw(canvas) mBackgroundFallback.draw(...) 5,dispatchDraw(canvas) 6,drawChild(canvas, child, drawingTime) 7,child.draw(canvas, this, drawingTime) draw(canvas) loop [ 遍历绘制子 view ] 可能会 invalidate(true) 重绘 surface.unlockCanvasAndPost(canvas) ViewRootImpl Surface DecorView View ViewGroup

表格

drawonDrawdrawonDraw
ViewGroupnull, 调用View#drawnullViewdraw( 三参数 ) 先被父容器调用, 若没有缓存, 再调用 draw(一参数): 在其内部调用 onDraw 绘制自身内容, 再调用 dispatchDraw (ViewGroup实现) 绘制子 View。null
ViewGroup子类null绘制子 View 之间的分割线View子类null操作 canvas 绘制自己

简单源码梳理

第0步,performTraversals

  • 0,ViewRootImpl#performTraversals
// ViewRootImpl 类

  boolean newSurface = false;
  
  // 。。。

    private void performTraversals() {

	// 。。。

        boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;

        if (!cancelDraw && !newSurface) {
	    // 。。。

            performDraw();
        } 

若没有取消绘制,且不是 newSurface,就调用 performDraw

第1步,performDraw

  • 1,ViewRootImpl#performDraw
// ViewRootImpl 类

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

        try {
	     // 接着下一步
            boolean canUseAsync = draw(fullRedrawNeeded);
            // 。。。
        } 
	// 。。。
    }

  • 1.1 ViewRootImpl#draw
    private boolean draw(boolean fullRedrawNeeded) {
        Surface surface = mSurface;
        if (!surface.isValid()) {
            return false;
        }
	// 。。。
        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
            if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
		  // 。。。
	     }else{
		 // 。。。
                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)) {
                    return false;
                }
		 // 。。。
	    }

  • 1.2,ViewRootImpl#drawSoftware
// ViewRootImpl 类

    public final Surface mSurface = new Surface();

    /**
     * @return true if drawing was successful, false if an error occurred
     */
    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {

        // Draw with software renderer.
        final Canvas canvas;

        // 脏区域的处理
        // We already have the offset of surfaceInsets in xoff, yoff and dirty region,
        // therefore we need to add it back when moving the dirty region.
        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);
            
            ...
            
        }finally {
            dirty.offset(dirtyXOffset, dirtyYOffset);  // Reset to the original value.
        }

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

                // 绘制前的工作...
                
                // 执行 draw 流程,自顶而下
                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;
            }

            ...
        }
        return true;
    }

第2步,DecorView#draw

  • 2,DecorView#draw

DecorView 没有实现 draw 函数,直接调用 View#draw 函数…


第3步,View#draw

  • 3,View#draw

    /**
     * Manually render this view (and all of its children) to the given Canvas.
     * The view must have already done a full layout before this function is
     * called.  When implementing a view, implement
     * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
     * If you do need to override this method, call the superclass version.
     *
     * @param canvas The Canvas to which the View is rendered.
     */
    @CallSuper
    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);
        }

        // 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;
        }

	// 。。。
    }

在注释 Step 3 处,调用了 onDraw 去画出自身内容; 在注释 Step 4 处,调用了 dispatchDraw 去画出子view 的内容;

第4步,onDraw

  • 4,DecorView#onDraw
// DecorView 类

    @Override
    public void onDraw(Canvas c) {
        super.onDraw(c); // FrameLayout 类中没有 onDraw 函数

	// 绘制背景?
        mBackgroundFallback.draw(this, mContentRoot, c, mWindow.mContentParent,
                mStatusColorViewState.view, mNavigationColorViewState.view);
    }

第5步,dispatchDraw

  • 5,ViewGroup#dispatchDraw
// ViewGroup 类

    @Override
    protected void dispatchDraw(Canvas canvas) {
        boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
        final int childrenCount = mChildrenCount;
        final View[] children = mChildren;
        int flags = mGroupFlags;

	// 。。。

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

    	...
    	
	   // mGroupFlags might have been updated by drawChild()
        flags = mGroupFlags;

        if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
            invalidate(true);//触发重绘
        }
        ...
    }

第6步,drawChild

  • 6,ViewGroup#drawChild
// ViewGroup 类

    /**
     * Draw one child of this View Group. This method is responsible for getting
     * the canvas in the right state. This includes clipping, translating so
     * that the child's scrolled origin is at 0, 0, and applying any animation
     * transformations.
     *
     * @param canvas The canvas on which to draw the child
     * @param child Who to draw
     * @param drawingTime The time at which draw is occurring
     * @return True if an invalidate() was issued
     */
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }

注意,此处调用的 child 的 draw 函数是有三个参数的 (canvas, this, drawingTime),这意味着只有 DecorView 是被调用 draw( 一个参数) 来触发绘制流程,普通ViewGroup的子类 和 普通View的子类,都是被调用 draw( 三个参数) 来触发绘制流程的。

第7步,child.draw(三参数)

  • 7,child.draw(canvas, this, drawingTime)

从这一步开始,就开始了 DecorView 的 子View 的绘制流程…

// View 类

    /**
     * This method is called by ViewGroup.drawChild() to have each child view draw itself.
     *
     * This is where the View specializes rendering behavior based on layer type,
     * and hardware acceleration.
     */
    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {

        if (!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); // 没有缓存,就调用一个参数的同名函数,同时序图中的第2步
                }
            }
        } else if (cache != null) { // 有缓存就直接画出来
		// 。。。

                canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
            }
        }

总结

从 draw 这个流程看下来,发现它是一个先绘制自己,再绘制 子view 的流程;

从第7步开始的 child.draw,child 也就是 DecorView 的第一个 子View,即系统布局 R.layout.screen_title 或 R.layout.screen_simple 或 其他类似的,统一放在源码工程的 frameworks/base/core/res/res/layout/ 路径下;

R.layout.screen_title 源码R.layout.screen_simple 源码

因为 R.layout.screen_title 这类的布局文件是 ViewGroup 类型的,所以第7步 child 就是它们的 根view:LinearLayout

其实,这整个流程看下来,只是对 draw 流程有一个大概的了解,具体的View 子类和 ViewGroup 子类的 onDraw 函数都有自己的实现,需要去具体分析…


补充

参考:Android Render系列规划篇

硬件加速绘制的 draw 流程:

更多源码分析参考:Android Render(二)7.1源码硬件加速下draw绘制流程分析


链接汇总

R.layout.screen_title 源码

R.layout.screen_simple 源码

ViewRootImpl 源码

DecorView 源码

FrameLayout 源码

View 源码

ViewGroup 源码

ViewStub extends View

LinearLayout 源码

TextView 源码

查源码的链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android的视图绘制是Android应用程序中的重要部分,它涉及到将用户界面元素绘制到屏幕上。以下是Android视图绘制的基本流程: 1. 触发绘制:当应用程序启动、布局发生变化或者手动调用 `invalidate()` 方法时,会触发视图绘制。 2. 测量布局:在绘制之前,Android会测量每个视图的大小。这个过程称为“测量布局”。测量布局是为了确定每个视图在屏幕上的位置和大小。 3. 布局:一旦测量完成,Android会根据视图的测量结果进行布局,确定每个视图在屏幕上的位置。 4. 绘制:布局完成后,Android会调用每个视图的 `draw()` 方法进行绘制。在 `draw()` 方法中,视图会绘制自己的内容,包括背景、文字、图片等。 5. 绘制层次:视图的绘制按照层次结构进行,即从父视图到子视图的顺序。父视图会先绘制自己,然后再绘制子视图。 6. 递归绘制:当父视图绘制完成后,它会递归地调用子视图的 `draw()` 方法,依次完成整个视图树的绘制过程。 7. 绘制缓存:为了提高绘制性能,Android使用了绘制缓存。绘制缓存可以将视图的绘制结果保存起来,在下次绘制时直接使用缓存,而不需要重新执行绘制操作。 总结来说,Android的视图绘制过程包括测量布局、布局、绘制和绘制缓存。通过这个过程,Android应用程序可以将用户界面元素绘制到屏幕上,实现丰富多样的交互效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值