Android ViewGroup onDraw为什么没调用

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的内容。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/40844.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

上海市静安区财政局领导带队调研合合信息,政企共话科技创新

近日,上海市静安区财政局副局长应文婷一行赴市北高新园区,实地走访科技企业上海合合信息科技股份有限公司(简称“合合信息”),了解公司技术创新成果及产业布局,倾听企业在发展过程中的政策需求。合合信息董…

Linux——认识Linux的目录结构 常用命令 vim命令 权限及其控制

目录 linux的目录结构常用linux的命令ls(list)和llcd 切换目录mkdir 创建文件夹touch命令:创建普通文本文件pwd 显示路径whoamisu:普通--超级账号man:查看手册rm:删除网络命令ifconfig重定向 >>cat 查看文本文件clear清屏hi…

python使用Anconda安装Cartopy

安装 Cartopy的话官方推荐是使用conda安装,打开Anconda Prompt后,新建一个环境(如果已有环境可跳过这步),然后激活环境: conda create -n newenv python3.9 conda activate newenv接着按照官网的推荐在Anc…

<数据结构>NO11.归并排序|递归|非递归|优化

文章目录 归并排序递归写法非递归写法修正方案1.归并一段拷贝一段修正方案2.修正区间 算法优化算法分析 归并排序的应用外排序和内排序 归并排序 递归写法 思路: 如果给出两个有序数组,我们很容易可以将它们合并为一个有序数组。因此当给出一个无序数组时&#xf…

【Ceph集群应用】Ceph块存储之RBD接口详解

Ceph块存储之RBD接口详解 1.创建Ceph块存储系统RBD接口1.1 删除镜像1.2 还原镜像1.3 在线扩容1.4 回滚镜像到指定位置1.5 删除快照1.6 快照分层1.7 快照展平1.8 镜像的导出导入 接上文基于ceph-deploy部署Ceph集群详解 1.创建Ceph块存储系统RBD接口 (1)…

区块链-java学习和劝退

字面意思:按照区域划分,每个区域通过可信的账本进行结算,将各个区域链接,形成小中心,大整体的财务结算认证体系; 1、学习前准备 您最好掌握一定的财务基本知识; 2、学习步骤 1)区…

记一次rabbitmq消息发送成功,消费丢失问题

记一次rabbitmq消息发送成功,消费丢失问题 背景 测试数据归档,偶现数据未归档 排查 idea线上调试,log日志,数据库消息发送记录,代码分块重复执行看哪块出的问题,结果均无问题,最后使用rabbi…

blender 建模马拉松

效果展示 蘑菇模型创建: 创建蘑菇头 shift A ,创建立方体; 右下工具栏添加细分修改器(视图层级:2,渲染:2);tab键进入编辑模式,alt z 进入透显模式&…

spring复习:(24)ApplicationContext中的BeanPostProcess是在哪里注册到容器的?

在ApplicationContext实现类的构造方法里。 public ClassPathXmlApplicationContext(String configLocation) throws BeansException {this(new String[] {configLocation}, true, null);}上边的构造方法调用如下构造方法 public ClassPathXmlApplicationContext(String[] conf…

Python分布式任务队列Celery

一、分布式任务队列Celery介绍 Python celery是一个基于Python的分布式任务队列,主要用于任务的异步执行、定时调度和分布式处理。它采用了生产者/消费者模式,通过消息中间件实现多个工作者进程之间的协作。 Python celery的架构主要包括以下组件&…

transformer Position Embedding

这是最近一段很棒的 Youtube 视频,它深入介绍了位置嵌入,并带有精美的动画: Transformer 神经网络视觉指南 -(第 1 部分)位置嵌入 让我们尝试理解计算位置嵌入的公式的“sin”部分: 这里“pos”指的是“单词…

SQL性能规范

一、随聊 记录一下吧,2023年7月13日00:11:11,现在的状态真的很,忙,干不完的活,希望巨大的压力,能够让自己快速成长,回想我这一路,21年大专毕业,用一年时间熟悉软件&…

使用selenium模拟登录解决滑块验证问题

目录 1.登录入口 2.点击“账号密码登录” 3.输入账号、密码并点击登录 4.滑块验证过程 5.小结 本次主要是使用selenium模拟登录网页端的TX新闻,本来最开始是模拟请求的,但是某一天突然发现,部分账号需要经过滑块验证才能正常登录&#x…

【个人笔记】对linux中一切皆文件的理解与ls命令

目录 Linux中一切皆文件ls命令常用参数常用命令lscpu lspci Linux中一切皆文件 理解参考:为什么说:Linux中一切皆文件? ls命令 ls(英文全拼: list directory contents)命令用于显示指定工作目录下之内容…

javascript 导出表格的excel

一个php网站的表格,需要增加导出excel的功能, 因对web开发不甚了解,开始想着用php导出, 搜索一番发现比较复杂,而且我的表格里已经有数据了, 如果导出又要去库中获取一次,不是负担加倍, 可否把现有表格数据,直接导出来? 答案是肯定的,用js在前端导出 开源js组件…

Camtasia Studio 2023保存为mp4格式的视频的详细教程,Camtasia的视频导出功能

很多用户刚接触Camtasia Studio,不熟悉如何保存mp4格式的视频。在今天的文章中小编为大家带来了Camtasia Studio 2023保存为mp4格式的视频的详细教程介绍。 1、 打开Camtasia Studio。 Camtasia Studio- 2023 win: https://souurl.cn/1JFEsn Camtasia …

【C++学习记录】(二)--一个C++工程文件里有哪些东西?

写在前面 首先,我有一个完整的C工程文件,文件分别是包含Debug、include、Service和src。 1.了解文件结构: 首先,查看每个文件夹中的内容以了解文件的组织结构。Debug文件夹通常包含与调试相关的文件,include文件夹可能包含头文件…

数据库复习

select 查询 字段别名用 as (可以为中文) 例如 select distinct 关键字 去重复值 例如select distinct deptno from test where 条件过滤 and or 和 not运算符 and同时成立 or有一个成立就可以了 优先级and>or>not不符合(!) in 匹配多个值 selec…

个人号的微信API接口,微信机器人二次开发

前段时间应公司需求,要开发一套自定义的微信机器人,具体需求是可以自己批量添加好友、批量打标签等进行好友管理,社群管理需要自动聊天,自动回复,发朋友圈,转发语音,以及定时群发等,…

Redis简介(1)

⭐ 作者简介:码上言 ⭐ 代表教程:Spring Boot vue-element 开发个人博客项目实战教程 ⭐专栏内容:个人博客系统 ⭐我的文档网站:http://xyhwh-nav.cn/ 文章目录 Redis简介1、NoSQL1.1、什么是NoSQL?1.2、NoSQL 特点…