View的绘制-draw流程详解

2019-06-06 10:36:05 浏览数 (1)

目录

作用

根据 measure 测量出的宽高,layout 布局的位置,渲染整个 View 树,将界面呈现出来。

具体分析

以下源码基于版本27

DecorView 的draw 流程

在《View的绘制-measure流程详解》中说过,View 的绘制流程是从 ViewRootViewImpl 中的 performMeasure()performLayoutperformDraw 开始的。在执行完 performMeasure()performLayout 后,开始执行 performDraw 方法:(以下源码有所删减)

代码语言:javascript复制
//ViewRootViewImpl 类
 private void performDraw() {
    ....
    draw(fullRedrawNeeded);
    ....
 }
-------------------------------------------------------------------------
//ViewRootViewImpl 类
private void draw(boolean fullRedrawNeeded) {
    ....
    mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
    ....
}

-------------------------------------------------------------------------
//ThreadedRenderer 类
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
    ....
    updateRootDisplayList(view, callbacks);
    ....
}
-------------------------------------------------------------------------
//ThreadedRenderer 类
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
    ....
    updateViewTreeDisplayList(view);
    ....
}
-------------------------------------------------------------------------
//ThreadedRenderer 类
private void updateViewTreeDisplayList(View view) {
    view.mPrivateFlags |= View.PFLAG_DRAWN;
    view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
            == View.PFLAG_INVALIDATED;
    view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
    //这里调用了 View 的 updateDisplayListIfDirty 方法 
    //这个 View 其实就是 DecorView
    view.updateDisplayListIfDirty();
    view.mRecreateDisplayList = false;
}

接下来查看 View 的 updateDisplayListIfDirty 方法:

代码语言:javascript复制
//View 类
 public RenderNode updateDisplayListIfDirty() {
    ....
    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 {
        /*最终调用了 DecorView 的 draw 方法,为什么没有走上面的 dispatchDraw(canvas)
        我也母鸡啊,我是Debug 断点调试晓得走这里的,哈哈*/
        draw(canvas);
    }
    ....
}
-------------------------------------------------------------------------
//DecorView 重写了 draw 方法。所以走到了 DecorView 的 draw 方法
@Override
public void draw(Canvas canvas) {
    //调用父类 (View)的 draw 方法
    super.draw(canvas);

    if (mMenuBackground != null) {
        mMenuBackground.draw(canvas);
    }
}
View 的 draw 流程

就这样, View 的绘制就开始啦。主要有四个步骤:

  • drawBackground 绘制背景色
  • onDraw 绘制内容
  • dispatchDraw 绘制 children
  • onDrawForeground 绘制装饰(前景,滚动条)
代码语言:javascript复制
//View 类
/**
 *手动将此视图(及其所有子项)渲染到给定的Canvas。在调用此函数前,视图必须已经完成了完整布局(layout)。
 *一般我们在自定义控件继承 View 的时候,不要重写 draw 方法,只需重写 onDraw 方法
 */
public void draw(Canvas canvas) {
    ....

    // 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
        //绘制 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;
    }
    ....
}

我们对四个步骤进行分析:

代码语言:javascript复制
//View 类
//绘制背景
private void drawBackground(Canvas canvas) {
    final Drawable background = mBackground;
    //如果没有设置背景,就不进行绘制
    if (background == null) {
        return;
    }
    //如果设置了背景吗,且背景的大小发生了改变,
    //就用 layout 计算出的四个边界值来确定背景的边界
    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) {
        //调用 Drawable 的 draw 方法来进行背景的绘制
        background.draw(canvas);
    } else {
        //平移画布
        canvas.translate(scrollX, scrollY);
        //调用 Drawable 的 draw 方法来进行背景的绘制
        background.draw(canvas);
        canvas.translate(-scrollX, -scrollY);
    }
}
-------------------------------------------------------------------------
//View 类
//绘制内容
protected void onDraw(Canvas canvas) {
    /*View 中的 onDraw 是一个空实现。也不难理解,当我们自定义控件继承 View 
    的时候,需要重写 onDraw 方法,通过 Canvas 和 Paint 来进行内容的绘制*/
}
-------------------------------------------------------------------------
//View 类
//绘制 children
protected void dispatchDraw(Canvas canvas) {
    /*View 中的 dispatchDraw 也是一个空实现。因为单独一个 View 
    本身是没有子元素的,不需要绘制 children */
}

-------------------------------------------------------------------------
//View 类
//绘制装饰
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);
    }
}

以上就是 View 的绘制流程了。ViewGroup 本身是继承 View 的,它的基本绘制流程也是通过父类 View 进行的,只不过它重写了 dispatchDraw 方法,来进行子元素的绘制。下面我们来进行具体分析:

ViewGroup 的绘制 dispatchDraw 流程
代码语言:javascript复制
//ViewGroup 类
@Override
protected void dispatchDraw(Canvas canvas) {
    ....
    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);
        }
    }
    ....
}
-------------------------------------------------------------------------
//ViewGroup 类
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    //调用 View 的 draw 方法,这里要注意,调用的是 View 的三个参数的 draw 方法
    return child.draw(canvas, this, drawingTime);
}

在 View 中还有一个 draw(Canvas canvas) 的重载方法,就是 draw(Canvas canvas, ViewGroup parent, long drawingTime):

代码语言:javascript复制
//View 类
/**
 * ViewGroup.drawChild()调用此方法以使每个子视图自己绘制。
 * 这是View专门根据图层类型和硬件加速来渲染行为的地方。
 */
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    ....
    //是否支持硬件加速
    boolean drawingWithRenderNode = mAttachInfo != null
            && mAttachInfo.mHardwareAccelerated
            && hardwareAcceleratedCanvas;

    if (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) {
         if (layerType != LAYER_TYPE_NONE) {
             //未开启
             //调用 View 的 buildDrawingCache 方法
             buildDrawingCache(true);
        }
        cache = getDrawingCache(true);
    }
    //开启了硬件加速
    if (drawingWithRenderNode) {
        //调用 View 的 updateDisplayListIfDirty 方法
        renderNode = updateDisplayListIfDirty();
        if (!renderNode.isValid()) {
            // Uncommon, but possible. If a view is removed from the hierarchy during the call
            // to getDisplayList(), the display list will be marked invalid and we should not
            // try to use it again.
            renderNode = null;
            drawingWithRenderNode = false;
        }
    }
    ....
}

分别查看 buildDrawingCacheupdateDisplayListIfDirty 方法:

代码语言:javascript复制
//View 类
public void buildDrawingCache(boolean autoScale) {
    ....
    buildDrawingCacheImpl(autoScale);
    ....
}
-------------------------------------------------------------------------
//View 类
private void buildDrawingCacheImpl(boolean autoScale) {
    ....
    // 如果不需要进行自身绘制,就直接调用 dispatchDraw 绘制子 Children
    //否则就直接调用 View 的 draw 方法
    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);
    }
    ....
}
-------------------------------------------------------------------------

//View 类
public RenderNode updateDisplayListIfDirty() {
    ....
    // 如果不需要进行自身绘制,就直接调用 dispatchDraw 绘制子 Children
    //否则就直接调用 View 的 draw 方法
    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);
    }
    ....
}

如此,从顶层 DecorView 的 draw 方法开始,然后调用 dispatchDraw 方法循环遍历绘制子元素,如果子元素是继承了 ViewGroup ,就再次循环调用 dispatchDraw 方法,一层层往下递归调用,直到每一个子元素都被绘制完成,整个 draw 流程也就结束了。

tips: 在我们使用真机进行源码断点调试的时候,可能会出现源码不能打断点的情况,或者断点没有走在该走的地方。这是因为国内手机厂商基本都是定制系统,可能修改了源码。这个时候可以使用模拟器进行断点调试。注意:模拟器版本号要和项目编译版本号一致!

setWillNotDraw 解析

在 View 中有一个方法是 setWillNotDraw:

代码语言:javascript复制
//View 类
/**
 * If this view doesn't do any drawing on its own, set this flag to
 * allow further optimizations. By default, this flag is not set on
 * View, but could be set on some View subclasses such as ViewGroup.
 *
 * Typically, if you override {@link #onDraw(android.graphics.Canvas)}
 * you should clear this flag.
 *
 * @param willNotDraw whether or not this View draw on its own
 */
public void setWillNotDraw(boolean willNotDraw) {
    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}

从注释上看,如果此视图本身不执行任何绘制,就设置为 true,系统会进行一些绘制优化。View 本身是默认设置为 false 的,没有启动这个优化标记(这也不难理解,因为一般我们自定义控件继承 View 的时候,是要重写 onDraw 方法进行绘制的)。ViewGroup 默认是开启这个优化标记的。当然如果明确 ViewGroup 是要通过 onDraw 方法进行绘制的时候,要手动关闭这个标记( setWillNotDraw(false) )。

示例:

我们自定义一个控件,继承 ViewGroup,重写 onDraw 方法。

代码语言:javascript复制
public class MyViewGroup extends ViewGroup {
    public MyViewGroup(Context context) {
        super(context);
        setWillNotDraw(false);
    }
    public MyViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        //这里如果不调用这句话,我们在使用的时候,onDraw 方法不会被调用
        setWillNotDraw(false);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //onLayout 在这里必须重写,因为在 ViewGroup 中 onLayout是一个抽象方法
    }
    //重写 onDraw 方法
    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.BLACK);
    }
}

xml 中使用

代码语言:javascript复制
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.ownchan.miclock.study.MyViewGroup
        android:layout_width="match_parent"
        android:background="@color/black"
        android:layout_height="match_parent"/>
</FrameLayout>

当我们的自定义控件在继承 ViewGroup 的时候,如果需要重写 onDraw 方法进行绘制,需要执行 setWillNotDraw(false)

推荐一个详解 draw 和 onDraw 调用时机好文: 你真的了解Android ViewGroup的draw和onDraw的调用时机吗

总结

0 人点赞