Android源码进阶之深入理解View的绘制流程(Draw)机制

开发 前端
三大工作流程始于ViewRootImpl#performTraversals,在这个方法内部会分别调用performMeasure,performLayout,performDraw三个方法来分别完成测量,布局,绘制流程。那么我们现在先从performDraw方法看起。

[[426758]]

前言

前几篇文章,讲述了measure,layout流程等,接下来将详细分析绘制流程。

测量流程决定了View的大小,布局流程决定了View的位置,那么绘制流程将决定View的样子,一个View该显示什么由绘制流程完成;

那我们就开始开车了;

一、performDraw

三大工作流程始于ViewRootImpl#performTraversals,在这个方法内部会分别调用performMeasure,performLayout,performDraw三个方法来分别完成测量,布局,绘制流程。那么我们现在先从performDraw方法看起;

performDraw

private void performDraw() { 
    //... 
    final boolean fullRedrawNeeded = mFullRedrawNeeded; 
    try { 
        draw(fullRedrawNeeded); 
    } finally { 
        mIsDrawing = false
        Trace.traceEnd(Trace.TRACE_TAG_VIEW); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

里面又调用了ViewRootImpl#draw方法,我们来看看ViewRootImpl#draw:

private void draw(boolean fullRedrawNeeded) { 
    ... 
    //获取mDirty,该值表示需要重绘的区域 
    final Rect dirty = mDirty; 
    if (mSurfaceHolder != null) { 
        // The app owns the surface, we won't draw. 
        dirty.setEmpty(); 
        if (animating) { 
            if (mScroller != null) { 
                mScroller.abortAnimation(); 
            } 
            disposeResizeBuffer(); 
        } 
        return
    } 
    //如果fullRedrawNeeded为真,则把dirty区域置为整个屏幕,表示整个视图都需要绘制 
    //第一次绘制流程,需要绘制所有视图 
    if (fullRedrawNeeded) { 
        mAttachInfo.mIgnoreDirtyState = true
        dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); 
    } 
    //... 
    if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { 
                return
        } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.

根据fullRedrawNeeded来判断是否需要重置dirty区域,最后调用了ViewRootImpl#drawSoftware方法,并把相关参数传递进去,包括dirty区域,我们接着看该方法的源码;

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
        } 
        canvas.setDensity(mDensity); 
    }  
    try { 
        if (!canvas.isOpaque() || yoff != 0 || xoff != 0) { 
            canvas.drawColor(0, PorterDuff.Mode.CLEAR); 
        } 
        dirty.setEmpty(); 
        mIsAnimating = false
        attachInfo.mDrawingTime = SystemClock.uptimeMillis(); 
        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); 
        } 
    }  
    return true

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.

实例化了Canvas对象,然后锁定该canvas的区域,由dirty区域决定,接着对canvas进行一系列的属性赋值,最后调用了mView.draw(canvas)方法,

mView就是DecorView,也就是说从DecorView开始绘制;

二、draw源码详解

由于ViewGroup没有重写draw方法,因此所有的View都是调用View#draw方法,因此,我们直接看它的源码

public void draw(Canvas canvas) { 
    ....  
    // 1. 绘制本身View背景 
    if (!dirtyOpaque) { 
        drawBackground(canvas); 
    } 
    if (!verticalEdges && !horizontalEdges) { 
        // Step 3, draw the content 
        // 2. 绘制内容,默认空实现 需复写 
        if (!dirtyOpaque) onDraw(canvas); 
        // 3. 绘制 children  
        dispatchDraw(canvas); 
        drawAutofilledHighlight(canvas); 
        // 4. 分发Draw (单一View空实现,ViewGroup见下面分析) 
        if (mOverlay != null && !mOverlay.isEmpty()) { 
            mOverlay.getOverlayView().dispatchDraw(canvas); 
        } 
        // 5. 绘制装饰 (前景色,滚动条) 
        onDrawForeground(canvas); 
        return
    } 
    .... 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

可以看到,draw过程比较复杂,但是逻辑十分清晰。首先来看一开始的标记位dirtyOpaque,

该标记位的作用是判断当前View是否是透明的,如果View是透明的,那么根据下面的逻辑可以看出,将不会执行一些步骤,比如绘制背景、绘制内容等;

绘制流程的五个步骤:

  • 对View的背景进行绘制;
  • 绘制View的内容;
  • 对View的子View进行绘制(如果有子View);
  • 分发Draw;

绘制View的装饰(例如:前景色,滚动条);

1、绘制背景

//绘制背景 
private void drawBackground(Canvas canvas) { 
    final Drawable background = mBackground; 
    if (background == null) { 
        return
    } 
    // 根据在 layout 过程中获取的 View 的位置参数,来设置背景的边界 
    setBackgroundBounds(); 
    // 先尝试用HWUI绘制 
    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) { 
        //调用 Drawable 的 draw 方法来进行背景的绘制 
        background.draw(canvas); 
    } else { 
        // 若 mScrollX 和 mScrollY 有值,则对 canvas 的坐标进行偏移平移画布 
        canvas.translate(scrollX, scrollY); 
        //调用 Drawable 的 draw 方法来进行背景的绘制 
        background.draw(canvas); 
        canvas.translate(-scrollX, -scrollY); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.

2、绘制View的内容

// 绘制View本身内容,空实现,子类必须复写 
protected void onDraw(Canvas canvas) { 

  • 1.
  • 2.
  • 3.

这里调用了View#onDraw方法,View中该方法是一个空实现,因为不同的View有着不同的内容,这需要我们自己去实现,即在自定义View中重写该方法来实现;

3、子View进行绘制

当前的View是一个ViewGroup类型,那么就需要绘制它的子View,这里调用了dispatchDraw,而View中该方法是空实现,实际是ViewGroup重写了这个方法,那么我们来看看;

@Override 
protected void dispatchDraw(Canvas canvas) { 
    ... 
    //  遍历子View 
    final int childrenCount = mChildrenCount; 
    ... 
    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) { 
            // 调用 drawChild 方法,进行子元素绘制 
            more |= drawChild(canvas, child, drawingTime); 
        } 
    } 
    .... 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.

4、分发Draw

@Override 
protected void dispatchDraw(Canvas canvas) { 
    ... 
    // 1. 遍历子View 
    final int childrenCount = mChildrenCount; 
    ... 
    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) { 
            // 调用 drawChild 方法,进行子元素绘制 
            more |= drawChild(canvas, child, drawingTime); 
        } 
    } 
    .... 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.

5、绘制View

所谓的绘制装饰,就是指View除了背景、内容、子View的其余部分,例如滚动条等,我们看View#onDrawForeground

public void onDrawForeground(Canvas canvas) { 
    //绘制指示器 
    onDrawScrollIndicators(canvas); 
    //绘制滚动条 
    onDrawScrollBars(canvas); 
    final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null
    if (foreground != null) { 
        if (mForegroundInfo.mBoundsChanged) { 
            mForegroundInfo.mBoundsChanged = false
            final Rect selfBounds = mForegroundInfo.mSelfBounds; 
            final Rect overlayBounds = mForegroundInfo.mOverlayBounds; 
            if (mForegroundInfo.mInsidePadding) { 
                selfBounds.set(0, 0, getWidth(), getHeight()); 
            } else { 
                selfBounds.set(getPaddingLeft(), getPaddingTop(), 
                        getWidth() - getPaddingRight(), getHeight() - getPaddingBottom()); 
            } 
            final int ld = getLayoutDirection(); 
            Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(), 
                    foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld); 
            foreground.setBounds(overlayBounds); 
        } 
        //调用 Drawable 的 draw 方法,绘制前景色 
        foreground.draw(canvas); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.

到目前为止,View的绘制流程也讲述完毕了;

总结

其实绘制这块还是很重要的,下次还是要继续讲解下;

学如逆水行舟,不进则退;心似平原走马,易放难收;

一起加油老铁们

本文转载自微信公众号「Android开发编程」

【编辑推荐】

 

责任编辑:姜华 来源: Android开发编程
相关推荐

2021-09-16 06:44:04

Android进阶流程

2021-10-15 09:19:17

AndroidSharedPrefe分析源码

2021-09-17 06:55:50

AndroidLayoutView

2021-09-08 06:51:52

AndroidRetrofit原理

2021-08-24 07:53:28

AndroidActivity生命周期

2021-09-15 07:31:33

Android窗口管理

2021-09-24 08:10:40

Java 语言 Java 基础

2021-09-10 07:31:54

AndroidAppStartup原理

2021-09-18 06:56:01

JavaCAS机制

2017-05-03 17:00:16

Android渲染机制

2022-10-11 07:43:34

AndroidSyncGradle 构建

2021-08-17 13:41:11

AndroidView事件

2024-12-30 08:02:40

2014-07-15 17:17:31

AdapterAndroid

2017-08-08 09:15:41

前端JavaScript页面渲染

2017-01-13 22:42:15

iosswift

2021-02-17 11:25:33

前端JavaScriptthis

2017-07-12 14:58:21

AndroidInstant Run

2016-10-26 20:49:24

ReactJavascript前端

2024-06-06 09:58:13

点赞
收藏

51CTO技术栈公众号