Android——View的工作流程——draw过程

一、draw 作用

draw 的作用是将 View 绘制到屏幕上

二、draw 是什么

三、draw 过程

1. 单一 View 的 draw 过程

(1)draw()
draw 过程的入口是 View 类的draw()方法,该方法的作用是将视图渲染到给定的 Canvas 上。

  • 调用该方法前必须完成 layout 过程
  • 自定义 View 时,应复写onDraw(canvas)方法进行绘制,不应复写该方法
  • 自定义 View 时,若确实要复写该方法,则需先调用super.draw(canvas)完成系统的绘制,然后再进行自定义的绘制
/**
 * Manually render this view (and all of its children) to the given Canvas.// 手动将视图渲染到给定的Canvas上
 * The view must have already done a full layout before this function is called. // 调用该方法前必须要完成 layout 过程
 *  When implementing a view, implement  * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method. // 自定义 View 时重写"onDraw()"方法,而不是本方法
 * If you do need to override this method, call the superclass version.// 若自定义的视图确实要复写该方法,那么需先调用 super.draw(canvas)完成系统的绘制,然后再进行自定义的绘制
 *
 * @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);

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

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

draw 过程必须以合适的顺序进行:
① 绘制 View 的背景
② 绘制 View 的内容
③ 绘制子 View
④ 绘制装饰(渐变框、滑动条、前景等)

(2)drawBackground()

/**
 * Draws the background onto the specified canvas.
 *
 * @param canvas Canvas on which to draw the background
 */
private void drawBackground(Canvas canvas) {
    // 获取背景 drawable
    final Drawable background = mBackground;
    if (background == null) {
        return;
    }

    // 根据 layout 过程中获取的 View 的位置参数,来设置背景的边界
    setBackgroundBounds();

    // Attempt to use a display list if requested.
    if (canvas.isHardwareAccelerated() && mAttachInfo != null &&
        mAttachInfo.mHardwareRenderer != null) {
        mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);

        final RenderNode renderNode = mBackgroundRenderNode;
        if (renderNode != null && renderNode.isValid()) {
            setBackgroundRenderNodeProperties(renderNode);
            ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
            return;
        }
    }

    // 获取 scrollX 和 scrollY
    final int scrollX = mScrollX;
    final int scrollY = mScrollY;
    if ((scrollX | scrollY) == 0) {
        // 调用 Drawable 的 draw 方法绘制背景
        background.draw(canvas);
    } else {
        // 若 scrollX 和 scrollY 有值,则对 canvas 的坐标进行偏移
        canvas.translate(scrollX, scrollY);
        // 调用 Drawable 的 draw 方法绘制背景
        background.draw(canvas);
        canvas.translate(-scrollX, -scrollY);
    }
}

(3)onDraw()
作用是绘制 View 本身的内容,由于每个 View 的内容各不相同,所以该方法的实现是一个空实现。在自定义 View 中,需子类去复写该方法,从而绘制自身的内容。自定义 View 中必须重写onDraw()方法

/**
 * 作用:绘制View本身的内容
 *
 * @param canvas the canvas on which the background will be drawn
 */
protected void onDraw(Canvas canvas) {}

(4)dispatchDraw()
作用是绘制子 View,由于单一 View中无子 View,故 View 类中该方法为空实现

/**
 * Called by draw to draw the child views. This may be overridden
 * by derived classes to gain control just before its children are drawn
 * (but after its own view has been drawn).
 * @param canvas the canvas on which to draw the view
 */
protected void dispatchDraw(Canvas canvas) {

}

(5)onDrawForeground()
作用是绘制装饰。

/**
 * 作用:绘制装饰
 *
 * 

Foreground content may consist of scroll bars, a {@link #setForeground foreground} * drawable or other view-specific decorations. The foreground is drawn on top of the * primary view content.

* * @param canvas canvas to draw into */ 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); } foreground.draw(canvas); } }

2. ViewGroup 的 draw 过程

(1)draw()
ViewGroup 绘制的入口是从 View 类中继承的方法draw(),该方法的具体介绍参见上文单一 View 的绘制。

(2)drawBackground()
继承 View 类中该方法,参见上文单一 View 的绘制。

(3)onDraw()
继承 View 类中该方法,参见上文单一 View 的绘制。

(4)dispatchDraw()
ViewGroup 类中重写了该方法。

/**
 * 作用:遍历子View & 绘制子View
 * 注:
 *   a. ViewGroup中:由于系统为我们实现了该方法,故不需重写该方法
 *   b. View中默认为空实现(因为没有子View可以去绘制)
 */
protected void dispatchDraw(Canvas canvas) {
    ......

    // 1. 遍历子View
    final int childrenCount = mChildrenCount;
    ......

    for (int i = 0; i < childrenCount; i++) {
        ......
        if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
            transientChild.getAnimation() != null) {
            // 2. 绘制子View视图
            more |= drawChild(canvas, transientChild, drawingTime);
        }
        ....
    }
}

/**
 * 作用:绘制子View
 */
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    // 最终还是调用了子 View 的 draw ()进行子View的绘制
    return child.draw(canvas, this, drawingTime);
}

四、重点说明

1. View 类的setWillNotDraw()方法

该方法用于设置标志位WILL_NOT_DRAW的值。当一个 View 不需要绘制内容时,并且该标志位为 true,系统进行相应的优化。

/**

     * If this view doesn't do any drawing on its own, set this flag to // 如果这个View不需要绘制任何内容,那么设置这个标记位为true后,系统会进行相应的优化。
     * allow further optimizations. By default, this flag is not set on // 默认情况下,View没有启用这个优化标志位,但是ViewGroup默认启用这个标志位。
     * View, but could be set on some View subclasses such as ViewGroup. // 当我们的自定义控件继承于ViewGroup并且本身不具备绘制功能时,可开启开标志位便于系统进行后续的优化
     *
     * Typically, if you override {@link #onDraw(android.graphics.Canvas)}
     * you should clear this flag. // 如果一个自定义View重写了onDraw()来绘制内容时,应显示关闭WILL_NOT_DRAW这个标记位。
     *
     * @param willNotDraw whether or not this View draw on its own
     */
public void setWillNotDraw(boolean willNotDraw) {
    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}

对实际开发的意义:

  • 自定义控件继承自 ViewGroup、且本身不进行任何绘制
    标志位设为 true,系统会进行相应的优化。

  • 自定义控件继承自 ViewGroup、且明确知道一个 ViewGroup 需要通过onDraw()来绘制内容时
    标志位设为 false。

你可能感兴趣的:(Android——View的工作流程——draw过程)