View的setWillNotDraw()方法作用
仅View一个类代码就超过3万行,从头到尾看一遍难度太大。除了记住常用的方法,还有一些不那么常用的。本篇将要介绍的是setWillNotDraw()方法。
1、setWillNotDraw() 方法介绍
如果调用View的setWillNotDraw(true)方法,也就是给View设置WILL_NOT_DRAW标志,会跳过当前View的绘制,对应View的onDraw()不会执行。
/**
* 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);
}
如何判断View有没有设置过此标记呢?提供了willNotDraw()方法判断View是否设置了WILL_NOT_DRAW。自定义View / ViewGroup时,如果遇到不绘制的问题,可以先检查此标记。
/**
* Returns whether or not this View draws on its own.
*
* @return true if this view has nothing to draw, false otherwise
*/
@ViewDebug.ExportedProperty(category = "drawing")
public boolean willNotDraw() {
return (mViewFlags & DRAW_MASK) == WILL_NOT_DRAW;
}
注意,PFLAG_SKIP_DRAW和WILL_NOT_DRAW的值是一样的,在后面源码中的作用等效:
static final int PFLAG_SKIP_DRAW = 0x00000080;
static final int WILL_NOT_DRAW = 0x00000080;
2、ViewGroup默认开启WILL_NOT_DRAW
ViewGroup为了提高自身的绘制性能,默认会设置WILL_NOT_DRAW标记。在ViewGroup的构造方法中:
public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initViewGroup();
initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
}
调用initViewGroup()初始化ViewGroup,添加WILL_NOT_DRAW标志位:
private void initViewGroup() {
// ViewGroup 默认不绘制
if (!isShowingLayoutBounds()) {
setFlags(WILL_NOT_DRAW, DRAW_MASK);
}
...
}
注意到isShowingLayoutBounds()这个方法,对应的是开发者选项中的显示布局边界选项。如果打开了此选项,就不会给ViewGroup设置WILL_NOT_DRAW。
/**
* Returns {@code true} when the View is attached and the system developer setting to show
* the layout bounds is enabled or {@code false} otherwise.
*/
public final boolean isShowingLayoutBounds() {
return DEBUG_DRAW || mAttachInfo != null && mAttachInfo.mDebugLayout;
}
所以,当继承ViewGroup,实现自定义ViewGroup容器时。注意是否需要去掉这个标记位, 否则在自定义ViewGroup中重写的onDraw() 不会被执行。有两种方法可以达到这种效果:
①设置ViewGroup的背景色, 会设置PFLAG_SKIP_DRAW ;它和WILL_NOT_DRAW 其实是同一个值。
②使用setWillNotDraw(false) 去掉WILL_NOT_DRAW标志。
之前在布局优化一文中,提到通过给ViewGroup等容器设置null背景,起到一定优化作用。其实原理在这里,没有背景的ViewGroup减少一个绘制自身的步骤,如果设置了重复多余的背景,反而增加了绘制任务。
3、setWillNotDraw() 如何起作用
在View的绘制流程一文中详细的了解View的绘制流程,可以获知在ViewGroup的drawChild()方法中绘制子View:
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
调用View的draw(Canvas canvas, ViewGroup parent, long drawingTime)方法,在该方法中判断当前View有没有设置PFLAG_SKIP_DRAW,如果设置了,那么不会走View的draw(),进而onDraw()不会被调用。
/**
* This method is called by ViewGroup.drawChild() to have each child view draw itself.
*
* This is where the View specializes rendering behavior based on layer type,
* and hardware acceleration.
*/
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
...
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else {
draw(canvas);
}
...
}
而是执行dispatchDraw(Canvas canvas)方法。在ViewGroup就是绘制子View,而在View中默认实现是空方法:
protected void dispatchDraw(Canvas canvas) {
}
参考博客:
关于onDraw()方法不被执行的解决方法(setWillNotDraw)