默認狀態下,ViewGroup爲何不走onDraw()?

1、原由

遇到這個問題是在一次自定義ViewGroup中重寫了onDraw()方法,而且在onDraw中繪製了畫面,可是運行事後,畫面沒有效果。隨後本身寫了一個簡單的Demo,打出日誌看了一下繪製流程,以下:javascript

從上面的圖片能夠清楚的發如今繪製時,只是走了onMeasure,onLayout和dispatchDraw,onDraw方法卻沒有調用。php

隨後借鑑了網上的說法,在自定義ViewGroup的xml中設置了一個背景顏色,並運行打印logjava

這個時候就能夠看見在dispatchDraw以前調用了onDraw方法。canvas

兩種代碼相同的自定義ViewGroup,只是改變了在xml中ViewGroup的背景,一個就會調用onDraw而另一個則不會,那麼爲何在不改變ViewGroup的狀況下不走onDraw方法呢?那麼又該如何解決這個問題??bash

帶着疑問,咱們去源代碼中找出緣由和解決方法。app

2、分析緣由

衆所周知,ViewGroup是繼承自View的,那麼直接去查看View。經過代碼跟蹤發如今ViewGroup中實現OnDraw方法,實際上是調用View中的draw(Canvas canvas)方法,以下:函數

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);
            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;
        }
複製代碼

在這段代碼中,原生清楚的標註出繪製分爲幾個步驟,但今天的主角不是這個暫不細說,咱們直接找到Step 3下面的一句代碼ui

if (!dirtyOpaque) onDraw(canvas);
複製代碼

重點!重點!重點在onDraw前存在一個條件,!dirtyOpaque爲真纔會執行onDraw(),而這個條件就直接影響了View子類的onDraw的執行,那麼咱們就能夠猜想文章一開頭ViewGroup不執行onDraw是否是在這受到了攔截?咱們繼續往下分析,來看看dirtyOpaque是個什麼東西?spa

final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
複製代碼

從上面代碼能夠看到dirtyOpaque爲一個boolean值,英文翻譯過來就是「髒的不透明的」,也就是說當背景不透明的時候纔會去執行onDraw(),而這個操做也就直接影響了ViewGroup所實現的OnDraw的執行。從代碼看dirtyOpaque的值主要取決於privateFlags,而privateFlags則是由一個全局變量mPrivateFlags所賦值,那麼咱們接着來看mPrivateFlags的值是怎麼來的?翻譯

經過查找,在View的構造函數的最後一行有computeOpaqueFlags這麼一個函數,對mPrivateFlags進行了初始化。

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;
        }
     ...
    }
複製代碼

代碼分析若是背景不爲空,而且背景不透明,則將mPrivateFlags設置爲PFLAG_OPAQUE_BACKGROUND,反之亦然。那麼分析到這裏也就大體清楚了,咱們理一理基本思路:

  1. 在computeOpaqueFlags()判斷當前view的背景是否爲空且是否透明,相對應賦值給mPrivateFlags;
  2. 而後在draw()中經過mPrivateFlags來判斷dirtyOpaque是true仍是flase;
  3. 而只有dirtyOpaque爲false時纔會執行onDraw,也就是說當背景不爲空且不爲透明時纔會執行onDraw

那麼回到文章最開始的問題,ViewGroup爲何不走onDraw?是否是ViewGroup的背景爲空或者是透明的??接下來就來驗證一下。

在ViewGroup的源碼中,ViewGroup的構造函數裏有對ViewGroup進行初始化,而在這個初始化中就代表了ViewGroup的背景問題。以下:

public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        initViewGroup();
        initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
    }

    private void initViewGroup() {
        // ViewGroup doesn't draw by default
        if (!debugDraw()) {
            setFlags(WILL_NOT_DRAW, DRAW_MASK);
        }
        ...
    }
複製代碼

在initViewGroup()方法的一開始源碼就直接代表ViewGroup doesn't draw by default(在默認狀態下,ViewGroup是不會繪製的),也能夠在代碼中看見setFlags()中傳入了WILL_NOT_DRAW這個參數,而WILL_NOT_DRAW則表示View是不會被繪製,而且不會調用onDraw方法。看到這裏緣由也就呼之欲出

在默認狀態下,ViewGroup的背景爲透明的,而在View中的draw方法中表示只有不透明才執行onDraw,這也就致使後續ViewGroup的onDraw失效。

3、解決辦法

3.一、方法一

在文章一開始draw()代碼的Step 4中dispatchDraw(canvas)沒有條件限制,可將onDraw中的處理移至到dispatchDraw()中。

3.二、方法二

源碼中,ViewGroup的初始背景經過setFlags設定爲透明的,根據setFlags能夠找到這麼一個方法setWillNotDraw,

public void setWillNotDraw(boolean willNotDraw) {
        setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
    }
複製代碼

源碼中也只是將setFlags封裝了一層供外部調用,那麼咱們就能夠在ViewGroup的構造函數中調用該方法,而且傳入參數false,即

setWillNotDraw(false);
複製代碼

3.三、方法三

直接在ViewGroup的xml下添加一個背景。

4、爲何View沒有限制

在View的構造函數中有這麼一段代碼

int viewFlagValues = 0;
...
 if (viewFlagMasks != 0) {
    setFlags(viewFlagValues, viewFlagMasks);
}
複製代碼

在View的初始化中對於Flag的設置就與ViewGroup相反,也就致使在默認狀況下View是不受影響的。

相關文章
相關標籤/搜索