Draw 过程

View 的工作流程主要是 measure layout draw 这三大流程完成的, 测量, 布局, 绘制, 其中 measure 测量布局的宽高, layout 确定 View 在布局中 4 个顶点的位置, draw 则是将布局绘制在屏幕上, 本篇主要讲的是绘制过程

Draw 的过程比较简单, 它的作用是将 View 绘制到屏幕上面, View 的绘制遵循以下几步

  1. 绘制背景
  2. 绘制自己
  3. 绘制子 View
  4. 绘制装饰

这一点可以通过 View 的 Draw 方法的源码可以提现出来, 同时这里也运用了一个模板设计模式, View 的绘制都遵循着这个模板来

 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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
    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); // 绘制子view

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

      // 省略部分代码 ...

这里需要注意, 如果是 ViewGroup 默认不会绘制, 不会调用 onDraw 方法, 仔细观察上方的源码可以发现有一个 dirtyOpaque , 如果这个值为 false 的时候, 才会去执行 onDraw 方法, 我们看看这个条件是什么

1
2
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);

其实在 View 初始化的时候, 会设置这个条件, 我们看看这个条件是什么

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
    protected void computeOpaqueFlags() {
        // Opaque if:
        //   - Has a background 有背景
        //   - Background is opaque 背景是不透明的
        //   - Doesn't have scrollbars or scrollbars overlay 没有滚动条

        if (mBackground != null && mBackground.getOpacity() == PixelFormat.OPAQUE) {
            mPrivateFlags |= PFLAG_OPAQUE_BACKGROUND;
        } else {
            mPrivateFlags &= ~PFLAG_OPAQUE_BACKGROUND;
        }

        final int flags = mViewFlags;
        if (((flags & SCROLLBARS_VERTICAL) == 0 && (flags & SCROLLBARS_HORIZONTAL) == 0) ||
                (flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_INSIDE_OVERLAY ||
                (flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_OUTSIDE_OVERLAY) {
            mPrivateFlags |= PFLAG_OPAQUE_SCROLLBARS;
        } else {
            mPrivateFlags &= ~PFLAG_OPAQUE_SCROLLBARS;
        }
    }

其实 View 中还有一个方法可以改变这个值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    /**
     * 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);
    }

从方法的注释中可以看出, 如果一个 View 不需要绘制任何内容, 那么设置这个标记位为 true 后, 系统会进行相应的优化, 默认情况下 View 是没有启用这个标记位的, 但是 ViewGroup 默认是启用了这个标记位的

也就是说当我们自定义 ViewGroup 时候, 是默认不具备绘制功能的, 如果我们想要具备绘制功能的话, 要么设置一个背景, 要么调用 setWillNotDraw(false) 清除这个标记位

因为默认的 ViewGroup 是不具备绘制功能的, 当我们在实际开发中的编写布局界面时, 避免过度绘制, 如无需要, 就不用在 ViewGroup 中设置背景了