遇到這個問題是在一次自定義ViewGroup中重寫了onDraw()方法,而且在onDraw中繪製了畫面,可是運行事後,畫面沒有效果。隨後本身寫了一個簡單的Demo,打出日誌看了一下繪製流程,以下:javascript
從上面的圖片能夠清楚的發如今繪製時,只是走了onMeasure,onLayout和dispatchDraw,onDraw方法卻沒有調用。php
隨後借鑑了網上的說法,在自定義ViewGroup的xml中設置了一個背景顏色,並運行打印logjava
這個時候就能夠看見在dispatchDraw以前調用了onDraw方法。canvas
兩種代碼相同的自定義ViewGroup,只是改變了在xml中ViewGroup的背景,一個就會調用onDraw而另一個則不會,那麼爲何在不改變ViewGroup的狀況下不走onDraw方法呢?那麼又該如何解決這個問題??bash
帶着疑問,咱們去源代碼中找出緣由和解決方法。app
衆所周知,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,反之亦然。那麼分析到這裏也就大體清楚了,咱們理一理基本思路:
也就是說當背景不爲空且不爲透明時纔會執行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失效。
在文章一開始draw()代碼的Step 4中dispatchDraw(canvas)沒有條件限制,可將onDraw中的處理移至到dispatchDraw()中。
源碼中,ViewGroup的初始背景經過setFlags設定爲透明的,根據setFlags能夠找到這麼一個方法setWillNotDraw,
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
複製代碼
源碼中也只是將setFlags封裝了一層供外部調用,那麼咱們就能夠在ViewGroup的構造函數中調用該方法,而且傳入參數false,即
setWillNotDraw(false);
複製代碼
直接在ViewGroup的xml下添加一個背景。
在View的構造函數中有這麼一段代碼
int viewFlagValues = 0;
...
if (viewFlagMasks != 0) {
setFlags(viewFlagValues, viewFlagMasks);
}
複製代碼
在View的初始化中對於Flag的設置就與ViewGroup相反,也就致使在默認狀況下View是不受影響的。