05
2017
10

【Android系列】View的绘制之draw过程

本文将讲述View绘制的最后一个过程——draw过程,继measure和layout过程之后,View已经确认了自身的大小和位置,draw过程将完成View内容的绘制,到此,View的绘制才真正完成。

draw第一步:从DecorView开始

和measure与layout过程一样,draw过程也是由ViewRootImpl对象开始执行的。在ViewRootImpl类的performTraversals方法中,经由performDrawdrawdrawSoftware一直到调用DecorView的draw方法,draw过程开始遍历整个View树。

draw方法的六个步骤

View的draw方法和measurelayout方法一样,在自定义View时不用重写draw方法,而是要重写onDraw方法,这一点在draw方法的注释中也给出了说明:

When implementing a view, implement onDraw(android.graphics.Canvas) instead of overriding this method.

下面看下draw方法的源码:

/* View.draw */
public void draw(Canvas canvas) {
    /* * 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
    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);

        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);

        // we're done...
        return;
    }
}

draw方法中一开始的注释可以看到,draw方法是分6步完成的:
1. 绘制背景;
2. 保存画布当前状态为绘制fading edges做准备;
3. 绘制内容;
4. 绘制子View;
5. 绘制dading edges并还原画布状态;
6. 绘制装饰(如滑动条)。

其中,第2和第5步是可选的,上面的代码除去了第2和第5步,因为它们并不常用并且会拖慢绘制过程。第3步是View绘制自身的内容,而第4步是父View绘制子View的过程,draw过程也是在这里传递给子View的。

draw第二步:从父View向子View传递

经过上一节的分析,我们已经知道View是通过onDraw方法绘制自身的,并且是通过dispatchDraw方法绘制子View的。

我们看View的onDraw方法,它是一个空方法,这在我们的意料之中,毕竟不同类型的View在屏幕上展现的内容都不一样,也就是说每个View(不是ViewGroup)都会重写onDraw方法。

我们再来看dispatchDraw方法,它在View中也是一个空方法,不过ViewGroup在继承View时重写了此方法,在ViewGroup的dispatchDraw方法中有这样一段代码:

/* ViewGroup.dispatchDraw */
for (int i = 0; i < childrenCount; i++) {
    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);
    }
}

这里在for循环中遍历了每一个子View,并通过drawChild方法绘制子View的内容:

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

到此,draw过程就从父View传递到了子View,并重复此过程直到到达View树的叶子节点。

draw第三步:在View处终止

当draw过程传递到View树的叶子节点时,由于已经没有需要绘制的子View,因此draw过程到这里也就完成了。对应的,View的dispatchDraw方法理所当然是一个空方法。

到这里,View绘制的三个过程都已经介绍完了,如果你还对measure或者layout过程不甚了解,可以阅读另外两篇文章:【Android系列】View的绘制之measure过程【Android系列】View的绘制之layout过程

按照惯例,我们也给出draw过程的流程图:

20170930_img1

现在,我们再回顾一下View绘制的整个过程:

20170925_img1

View的绘制是从ViewRootImpl的performTraversals方法开始的,并经过measure、layout和draw三个过程才能最终将一个View绘制出来。其中measure过程对View的宽高进行测量,layout过程会确定View在父容器中的放置位置,draw过程负责将View绘制到屏幕上。

qrcode

坚持就是胜利ヾ(◍°∇°◍)ノ゙

上一篇:Android Camera增加自定义图像处理并录制MP4 下一篇:chroot ubuntu 16.04 on android