Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 從0開始自定義控件之 View 的 draw 過程 (九)

Android 從0開始自定義控件之 View 的 draw 過程 (九)

編輯:關於Android編程

前言

前面已經了解了 View 三大流程的 measure 和 layout 過程,這一篇繼續學習最後的 draw 過程。draw 的過程依舊是在 ViewRootImpl#performTraversals 方法中調用的,其調用順序是在最後, 相較與 measure 和 layout 過程要簡單的多,它的作用就是將 View 繪制到屏幕上面。

View 的繪制

下面直接通過查看 View#draw 源碼,來分析下其 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)
    ......

    // Step 2, save the canvas' layers
    ......
    int solidColor = getSolidColor();
    if (solidColor == 0) {
        final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;

        if (drawTop) {
            canvas.saveLayer(left, top, right, top + length, null, flags);
        }

        if (drawBottom) {
            canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
        }

        if (drawLeft) {
            canvas.saveLayer(left, top, left + length, bottom, null, flags);
        }

        if (drawRight) {
            canvas.saveLayer(right - length, top, right, bottom, null, flags);
        }
    } else {
        scrollabilityCache.setFadeColor(solidColor);
    }

    // Step 3, draw the content
    if (!dirtyOpaque) onDraw(canvas);

    // Step 4, draw the children
    dispatchDraw(canvas);

    // Step 5, draw the fade effect and restore layers
    ......

    if (drawTop) {
        ......
        canvas.drawRect(left, top, right, top + length, p);
    }

    if (drawBottom) {
        ......
        canvas.drawRect(left, bottom - length, right, bottom, p);
    }

    if (drawLeft) {
        ......
        canvas.drawRect(left, top, left + length, bottom, p);
    }

    if (drawRight) {
        ......
        canvas.drawRect(right - length, top, right, bottom, p);
    }
    ......
    // Step 6, draw decorations (foreground, scrollbars)
    onDrawForeground(canvas);
}

上面的源碼注釋寫的很清晰,通過查看後我們了解到 View 的繪制共分為如下六步:

1:繪制背景。 2:如果需要,保存圖層信息。 3:繪制 View 的內容。 4:如果 View 有子 View,繪制 View 的子 View。 5:如果需要,繪制 View 的邊緣(如陰影等)。 6:繪制 View 的裝飾(如滾動條等)。

其中以上六步,第二步和第五步並不是必須的,所以我們只需重點分析其他四步即可。

繪制背景

繪制背景調用了 View#drawBackground 方法:

private void drawBackground(Canvas canvas) {
    // 獲取背景 drawable
    final Drawable background = mBackground;
    if (background == null) {
        return;
    }
    // 根據在 layout 過程中獲取的 View 的位置參數,來設置背景的邊界
    setBackgroundBounds();

    .....

    // 獲取 mScrollX 和 mScrollY值 
    final int scrollX = mScrollX;
    final int scrollY = mScrollY;
    if ((scrollX | scrollY) == 0) {
        background.draw(canvas);
    } else {
        // 如果 mScrollX 和 mScrollY 有值,則對 canvas 的坐標進行偏移
        canvas.translate(scrollX, scrollY);
        // 調用 Drawable 的 draw 方法繪制背景
        background.draw(canvas);
        canvas.translate(-scrollX, -scrollY);
    }
}

繪制內容

繪制內容調用了 View#onDraw 方法,由於 View 的內容各不相同,所以該方法是一個空實現,需要由子類去實現:

protected void onDraw(Canvas canvas) {
}

繪制子 View

繪制子 View 調用了 View#dispatchDraw 方法,該方法同樣是一個空實現:

protected void dispatchDraw(Canvas canvas) {

}

當只有包含子類的時候,才會去重寫它,ViewGroup 不正好是嗎? 來看下 ViewGroup 對該方法的實現吧:

protected void dispatchDraw(Canvas canvas) {
    ......
    final int childrenCount = mChildrenCount;
    ......

    for (int i = 0; i < childrenCount; i++) {
            ......
            if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                    transientChild.getAnimation() != null) {
                more |= drawChild(canvas, transientChild, drawingTime);
            }
            ......
    }
}

ViewGroup#dispatchDraw 方法的代碼比較多,只分析重點,遍歷了所有的子 View 並調用了 ViewGroup#drawChild 方法:

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

該方法最終還是調用了子 View 的 draw 方法,似曾相識啊,和上篇中的 layout 過程是一樣呢。

由於 ViewGroup 已經為我們實現了該方法,所以我們一般都不需要重寫該方法。

繪制裝飾

繪制裝飾調用了 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);
        }

        foreground.draw(canvas);
    }
}

該方法默認實現是繪制了滾動指示器、滾動條、和前景。

setWillNotDraw

View 中有一個特殊的方法,setWillNotDraw(boolean willNotDraw):

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

該方法是用於設置 WILL_NOT_DRAW 標記位的。默認情況下, View 沒有啟用這個優化標記位的,但是 ViewGroup 會默認啟用。

如果當我們自定義的控件繼承自 ViewGroup 並且本身並不具備任何繪制時,那麼可以設置 setWillNotDraw 方法為 true,設置為 true 後,系統會進行相應的優化。

如果當我們知道我們自定義繼承自 ViewGroup 的控件需要繪制內容時,那麼需要設置 setWillNotDraw 方法為 false,來關閉 WILL_NOT_DRAW 這個標記位。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved