ViewGroup,它本身并没有任何可画的东西,它是一个透明的控件,因些并不会触发onDraw,但是你现在给LinearLayout设置一个背景色,其实这个背景色不管你设置成什么颜色,系统会认为,这个LinearLayout上面有东西可画了,因此会调用onDraw方法。
android代码一直在优化,我看了几个版本的源码,目前,我用的是API30的源码,再去看ViewGroup为什么不走onDraw()的时候,已经不是一句 if (!dirtyOpaque) 就能决定是否执行onDraw()的事了。
原因详解
在API27中,还是我们熟悉的那个 if 判断决定 onDraw()的执行
在API27以后,你会发现在draw()方法里找不到 上面这个 if 语句,那么问题来了:他是如何控制 ViewGroup 不执行 onDraw() 的呢?
这个时候,我们的目光该放在这两个片段上了,还是在 View 这个类里面
片段一:
view.java
/**
* 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);
}
...
从这一段我们能获取两个信息:
注释:
- ViewGroup.drawChild()调用此方法,使每个子视图都绘制自己。这是视图根据图层类型专门处理渲染行为的地方,硬件加速。
- 是否走draw()方法由两个标志决定 mPrivateFlags & PFLAG_SKIP_DRAW
片段二 :
public RenderNode updateDisplayListIfDirty() {
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
if (isShowingLayoutBounds()) {
debugDrawFocus(canvas);
}
} else {
draw(canvas);
}
}
从这一段我们能获取这么个信息:是否走draw()方法由两个标志决定 mPrivateFlags & PFLAG_SKIP_DRAW
硬件加速
现在Android默认开启硬件加速,什么是硬件加速呢?为了加快Android绘制速度,适当解放cpu资源,Android将一部分绘制放到gpu执行。而对应的Android里面的canvas,也分为是否支持硬件加速,因此绘制流程也有所差异,流程图简示如下:
[]表示该调用该类里的对应方法。
()表示方法里的参数
从上图可以看出,不管是否开启硬件加速,都会经历“跳过绘制”的逻辑判断,而该判断的分支就决定了viewGroup的ondraw()方法是否执行。如果“跳过绘制”成立,那么调用dispatchDraw()方法,继而调用子view进行绘制(如果有子view)。如果“跳过绘制”不成立,那么调用draw(x1),该方法上面分析过了:会调用dispatchDraw()和ondraw()方法。
draw(x1)的方法如下:
public void draw(Canvas canvas) {
//省略
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// 绘制自身内容
onDraw(canvas);
// 绘制子view
dispatchDraw(canvas);
//省略
// we're done...
return;
}
//省略
}
viewGroup和View初始化时对于PFLAG_SKIP_DRAW标记做了不同的处理。
viewGroup初始化的时候,默认设置了WILL_NOT_DRAW,从字面意思来看是“不会绘制”标记,这个标记是否和PFLAG_SKIP_DRAW有联系呢?继续查看setFlags方法:
vew.java setFlags方法
//省略
if ((changed & DRAW_MASK) != 0) {
if ((mViewFlags & WILL_NOT_DRAW) != 0) {
if (mBackground != null
|| mDefaultFocusHighlight != null
|| (mForegroundInfo != null && mForegroundInfo.mDrawable != null)) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
} else {
mPrivateFlags |= PFLAG_SKIP_DRAW;
}
} else {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
}
requestLayout();
invalidate(true);
}
//省略
到此处就比较明朗,将两个标记值联系起来了:
1、如果设置了WILL_NOT_DRAW标记,那么继续检查background、foreground(mDrawable字段)、focusHighLight是否有值,如果三者任意一个设置了,那么将PFLAG_SKIP_DRAW标记清除,否则将该标记加上。
2、如果没有设置WILL_NOT_DRAW标记,那么将PFLAG_SKIP_DRAW标记清除。
如何让viewGroup onDraw()执行
既然知道了MyFrameLayout没有绘制的原因,那么就有方法让它执行绘制流程。
先来看看WILL_NOT_DRAW
view.java
/**
* 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);
}
/**
* 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;
}
View类里暴露了设置WILL_NOT_DRAW标记的接口:
setWillNotDraw(boolean willNotDraw),可以在viewgroups里使用setWillNotDraw(false)。
不想设置该标记也是可行的,前面说过即使设置了WILL_NOT_DRAW,后面还是有判断background、foreground、focusHighLight是否有值。
background:view背景
foreground(mDrawable字段):view前景
focusHighLight:view获得焦点时高亮
我们只要设置了其中一个值,PFLAG_SKIP_DRAW标记将会被清空。
来看看这三个值如何影响PFLAG_SKIP_DRAW标记
view.java
public void setBackgroundDrawable(Drawable background) {
if (background != null) {
if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
requestLayout = true;
}
}
}
public void setForeground(Drawable foreground) {
if (foreground != null) {
if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
}
}
}
private void setDefaultFocusHighlight(Drawable highlight) {
mDefaultFocusHighlight = highlight;
mDefaultFocusHighlightSizeChanged = true;
if (highlight != null) {
if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
}
}
}
总结
若要ViewGroup onDraw()执行,只需要setWillNotDraw(false)、设置背景、设置前景、设置焦点高亮,4个选项其中一项满足即可。
当然也可以重写dispatchDraw()方法,在该方法里绘制自定义view的内容。