Android ViewGroup onDraw为什么没调用

ViewGroup,它本身并没有任何可画的东西,它是一个透明的控件,因些并不会触发onDraw,但是你现在给LinearLayout设置一个背景色,其实这个背景色不管你设置成什么颜色,系统会认为,这个LinearLayout上面有东西可画了,因此会调用onDraw方法。

android代码一直在优化,我看了几个版本的源码,目前,我用的是API30的源码,再去看ViewGroup为什么不走onDraw()的时候,已经不是一句 if (!dirtyOpaque) 就能决定是否执行onDraw()的事了。

原因详解

在API27中,还是我们熟悉的那个 if 判断决定 onDraw()的执行

在API27以后,你会发现在draw()方法里找不到 上面这个 if 语句,那么问题来了:他是如何控制 ViewGroup 不执行 onDraw() 的呢?

这个时候,我们的目光该放在这两个片段上了,还是在 View 这个类里面

片段一:

cpp 复制代码
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

片段二 :

cpp 复制代码
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)的方法如下:

cpp 复制代码
    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方法:

cpp 复制代码
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);
        }
        //省略

到此处就比较明朗,将两个标记值联系起来了:

cpp 复制代码
1、如果设置了WILL_NOT_DRAW标记,那么继续检查background、foreground(mDrawable字段)、focusHighLight是否有值,如果三者任意一个设置了,那么将PFLAG_SKIP_DRAW标记清除,否则将该标记加上。
2、如果没有设置WILL_NOT_DRAW标记,那么将PFLAG_SKIP_DRAW标记清除。

如何让viewGroup onDraw()执行

既然知道了MyFrameLayout没有绘制的原因,那么就有方法让它执行绘制流程。

先来看看WILL_NOT_DRAW

cpp 复制代码
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标记

cpp 复制代码
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;
            }
      }
    }

总结

cpp 复制代码
若要ViewGroup onDraw()执行,只需要setWillNotDraw(false)、设置背景、设置前景、设置焦点高亮,4个选项其中一项满足即可。

当然也可以重写dispatchDraw()方法,在该方法里绘制自定义view的内容。

相关推荐
爱笑鱼1 小时前
Binder(八):远端进程死了,BinderProxy 为什么还能收到通知?
android
zhangphil1 小时前
Android ContentProvider/ContentResolver读图片,跨进程 IPC慢
android
yueqc12 小时前
Android .so 文件压缩
android·so·包体积
龚礼鹏2 小时前
深入解析 Android Automotive (AAOS) 启动流程与 CarService 核心机制(基于 Android 16 最新源码视角)
android
rosmis2 小时前
agent各指标定义
android·java·开发语言
风样滴男人哟4 小时前
PHP特性之反射类ReflectionClass机制
android·开发语言·php
小孔龙4 小时前
RenderNode 与 DisplayList:Android 硬件加速的绘制记录与复用
android·性能优化
qizayaoshuap5 小时前
鸿蒙 ArkTS 实战:搜索列表 城市实时过滤(示例 94)
android·华为·harmonyos
朝星5 小时前
Framework筑基之Handler消息机制5(应用层)
android
2601_965798475 小时前
ForumLab Review: Fast PHP Script for SEO Forum Sites
android·adb·php