网上有不少文章都说自定义ViewGroup的时候,如果需要自定义绘图,不能重写onDraw方法,而是要重写dispatchDraw方法,因为ViewGroup绘制的时候不一定走onDraw方法,但是dispatchDraw方法是一定会走的。至于何时会走onDraw,何时不会走onDraw,有人说当ViewGroup设置背景的时候,会走onDraw方法,而没有背景的时候,不会走onDraw方法,这个说法也是有道理的。
切到本文的问题,为什么ViewGroup会不一定走onDraw方法呢,而非容器类的View为什么都会调用到onDraw方法呢?针对这个问题,下面把本人通过阅读源码从源码角度说一下自己的理解。
一、我们看一下View.updateDisplayListIfDirty(),不管是走dispatchDraw还是onDraw,总入口是在这个方法里面。
@NonNull
public RenderNode updateDisplayListIfDirty() {
。。。。。。。。。
computeScroll();
canvas.translate(-mScrollX, -mScrollY);
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
dispatchDraw(canvas);
。。。。。。
} else {
draw(canvas);
}
}
} 。。。。。。
return renderNode;
}
看代码可知,是通过判断mPrivateFlags变量有没有设置过PFLAG_SKIP_DRAW标识,来判断是该调用dispatchDraw方法,还是draw方法,而draw方法里面就会调用onDraw方法了。如果设置过PFLAG_SKIP_DRAW,那么就会直接走dispatchDraw方法了,否则走draw方法。
即mPrivateFlags有PFLAG_SKIP_DRAW标识,那么会走dispatchDraw方法,如果没有PFLAG_SKIP_DRAW标识,那么就会走onDraw方法。
那么mPrivateFlags有没有设置过PFLAG_SKIP_DRAW呢,如果设置过的话又是在何时设置的呢,请看下面。
二、PFLAG_SKIP_DRAW的设置或者清除
1、关于PFLAG_SKIP_DRAW这个标识的设置或者清除,一共有三处涉及到。
(1)View.setForeground(Drawable foreground) 方法
public void setForeground(Drawable foreground) {
if (mForegroundInfo == null) {
if (foreground == null) {
// Nothing to do.
return;
}
mForegroundInfo = new ForegroundInfo();
}
......
if (foreground != null) {
if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
}
........
} else if ((mViewFlags & WILL_NOT_DRAW) != 0 && mBackground == null) {
mPrivateFlags |= PFLAG_SKIP_DRAW;
}
......
}
看这段代码可知,如果设置过foreground,就会清除mPrivateFlags的PFLAG_SKIP_DRAW,而当没有设置foreground并且mViewFlags设置过WILL_NOT_DRAW时,会给mPrivateFlags设置PFLAG_SKIP_DRAW。至于mViewFlags有没有过WILL_NOT_DRAW,请看下面第2小节。
(2)View.setBackgroundDrawable(Drawable background)方法
public void setBackgroundDrawable(Drawable background) {
。。。。。。
if (background != null) {
。。。。。。
if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
requestLayout = true;
}
} else {
/* Remove the background */
mBackground = null;
if ((mViewFlags & WILL_NOT_DRAW) != 0
&& (mForegroundInfo == null || mForegroundInfo.mDrawable == null)) {
mPrivateFlags |= PFLAG_SKIP_DRAW;
}
。。。。。。
}
。。。。。。
}
看这段代码可知,如果设置过background,那么会清除mPrivateFlags的PFLAG_SKIP_DRAW,否则当没有设置过background并且mViewFlags设置过WILL_NOT_DRAW时,会给mPrivateFlags设置PFLAG_SKIP_DRAW。至于mViewFlags有没有过WILL_NOT_DRAW,请看下面第2小节。
(3)View.setFlag(int flags, int mask)方法
void setFlags(int flags, int mask) {
。。。。。。
int old = mViewFlags;
mViewFlags = (mViewFlags & ~mask) | (flags & mask);
int changed = mViewFlags ^ old;
。。。。。。
if ((changed & DRAW_MASK) != 0) {
if ((mViewFlags & WILL_NOT_DRAW) != 0) {
if (mBackground != null
|| (mForegroundInfo != null && mForegroundInfo.mDrawable !=
null))
{
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
} else {
mPrivateFlags |= PFLAG_SKIP_DRAW;
}
} else {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
}
。。。。。。
}
。。。。。。
}
看这个方法可知,当mViewFlags设置过WILL_NOT_DRAW,当设置过background或者foreground的时候,那么会清除mPrivateFlags的PFLAG_SKIP_DRAW,如果没有设置过background或者foreground的时候,会给mPrivateFlags设置PFLAG_SKIP_DRAW;当mViewFlags没有设置过WILL_NOT_DRAW的时候,也会清除mPrivateFlags的PFLAG_SKIP_DRAW。
2、这三个方法的调用时机
setForeground和setBackgroundDrawable是在View的构造方法里面调用的
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
。。。。。。
case R.styleable.View_foreground:
if (targetSdkVersion >= VERSION_CODES.M || this instanceof FrameLayout)
{
setForeground(a.getDrawable(attr));
}
break;
。。。。。。
if (background != null) {
setBackground(background);
}
。。。。。。
}
看这段代码可知,如果设置过foreground或者background二者之一,都会显示清除掉mViewFlags设置过PFLAG_SKIP_DRAW,那么foreground和background都没有设置过呢,setForeground和setBackground方法是没有被调用的,那么默认情况下mViewFlags是没有PFLAG_SKIP_DRAW标识的。
View的构造方法,对容器类View和非容器类View都会调用,那么为什么ViewGroup会表现的和非容器类View不一样呢,它一定是对PFLAG_SKIP_DRAW有过特殊的处理,果然,我们来看一下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);
}
mGroupFlags |= FLAG_CLIP_CHILDREN;
mGroupFlags |= FLAG_CLIP_TO_PADDING;
mGroupFlags |= FLAG_ANIMATION_DONE;
mGroupFlags |= FLAG_ANIMATION_CACHE;
mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE;
。。。。。。
}
可见,ViewGroup的构造方法里面调用了setFlag方法,传的参数是WILL_NOT_DRAW和DRAW_MASK,由第1小节的(3)可知,会显示地根据有无foreground或者background来对PFLAG_SKIP_DRAW进行显示地设置或者清除。
三、总结性分析
由一和二可知,ViewGroup会在构造方法里面根据是否设置过foreground或者background来对PFLAG_SKIP_DRAW进行显示的设置或者清除,如果设置过二者任何一个,就会对mViewFlags清除PFLAG_SKIP_DRAW,否则设置PFLAG_SKIP_DRAW。
而非容器类的View只有在设置过foreground或者background二者任何一个的时候,会对mViewFlags清除PFLAG_SKIP_DRAW,否则是没有处理的,而默认情况下mViewFlags是没有PFLAG_SKIP_DRAW的,所以非容器的View可以认为其mViewFlags是没有PFLAG_SKIP_DRAW标识的。
综上,ViewGroup在设置过foreground或者background二者之一的时候,会每次走onDraw方法,而二者都没设置的时候,会走dispatchDraw方法。而View因为其mViewFlags一般情况下是没有PFLAG_SKIP_DRAW的,所以总会走onDraw方法。ViewGroup和非容器类View的区别就是在没有设置背景和前景的时候,ViewGroup显示地设置了PFLAG_SKIP_DRAW,而非容器类View没有设置PFLAG_SKIP_DRAW,这样就导致了绘制流程出现了差异。