如何用10行代码让app全局置灰

2020-04-08 00:00:00 代码 源码 方法 自定义 替换

内容概要

前段时间由于新冠肺炎特别严重,政府规定今年的4月4号为悼念日,所有互联网项目能置灰的要跟随置灰处理。我们可以看到在京东、百度等部分app中都有置灰的功能。如果是在网页上的话,只需要一句代码就可以搞定了,但是app里实现可能有些同学会感觉迷茫。今天笔者也跟上潮流,给大家分享一篇如何在app中实现全局置灰吧,没有这个需求的朋友们也可以学习探讨一下思路,希望可以帮到大家!


一 、如何实现页面灰度化

实现灰度化的思路应该从Paint出发,因为系统是通过Paint将内容绘制到界面上的,如果能找到Paint相关的设置方法,那就再也合适不过了。自定义View做得多的同学可能知道Paint中可以设置ColorMatrix,以下是其源码,从源码的注释我们可以看到如果将setSaturation的sat参数设置为0就代表灰度模式。

OK,说干就干,我们自定义一个图片控件,修改其Paint中的Matrix属性试试。首先初始化ColorMatrix对象,然后按照源码中的说法,将setSaturation参数的值设置为0,接下来将该ColorMatrix设置到Paint中,后再onDraw方法中使用刚刚的Paint对象,代码大致如下:

以上是在ImageView中实现灰度化,那么文本的TextView其实也是一样的,因为本质都是使用Paint进行的绘制,所以可以直接将上面代码拷贝到自定义TextView中。

接下来我们在页面中使用上面自定义好的2个View,我们运行一下,看一下效果。不出所以然,文本和图片都变成了灰色,具体运行结果如下图:

效果是实现了,又有一个新的问题摆在眼前。在项目里数以万计的ImageView和TextView,总不可能一个一个来替换吧,这样做要换到猴年马月去?


二、如何提升替换效率

从上面TextView、ImageView二者的置灰实现没有任何区别,我们可以猜测是不是所有的View都能给置灰呢,那么ViewGroup作为一个特殊的View是否可以置灰呢?接下来我们来验证一下这个猜想,自定义一个RelativeLayout,还是之前的代码,需要注意的是这里需要复写dispatchDraw方法。关于onDraw方法和dispatchDraw方法的差别这里也稍微解释一下,ViewGroup容器组件的绘制,当它没有背景时直接调用的是dispatchDraw方法, 而绕过了onDraw方法,当它有背景的时候就调用onDraw方法,而onDraw()方法里包含了dispatchDraw方法的调用。也就是说这里必须复写dispathDraw方法,否则将没效果。

定义好ViewGroup以后,接下来将它使用到页面的根布局上面,运行项目看结果:

我们看到页面确实也成功灰度化了,心中暗暗窃喜,终于可以减少很多工作量了。然而问题还是有的,项目中有几百个页面,每个页面ViewGroup又不一样,我难道要先准备GrayLinearLayout、GrayRelativeLayout、GrayFramelayout等等,然后一个一个去换根布局吗,想想都有点累,那有没有更加优雅的方式呢?


三、步步逼近,从源码中寻找优雅的全局替换方式

一般来说,好用的方案往往来源于源码中,这也是程序员经常查看源码的原因。带着这个疑问我们先来简单复习一下View的加载过程,在我之前分享插件化的时候有简单提到过。我们从setContentView方法中一路点下去,终会看到调用了LayoutInflater类中的createViewFromTag方法,这段调用过程很简单,没看过的同学可以亲自去查看一下。笔者点了很多次了,这里就直接贴出来相关代码吧,重点是红框里面的部分。

在红框里是具体的生成View过程,这里分为3种情况,优先Factory2,其次是Factory,后是默认的onCreateView方法。关于这里为什么会有3种情况,是因为历史原因,为了兼容AppCompact。默认情况下,前两个都是为空的,会直接进入onCreateView方法。那么思路来了,我们可以可以在加载View的时候,通过替换BaseActivity的布局统一替换所有页面呢?

方式一:通过setFactory方法给定我们自己的Factory从而替代系统的加载View,然后实现统一替换。

在Activity的onCreate方法中调用以下代码,hook住系统加载View的流程,根据前面的源码,如果设置了factory将进入自定义的onCreateView方法而不再进入系统的onCreateView方法。看过AMS或者事件分发流程的同学肯定知道,在我们自己写的xml根布局之上还有一个系统的FrameLayout,关于这一块不太懂的同学可以去看我之前的事件分发文章,里面有详细讲到。这里我们就利用这一点,找到这个id是"content"的系统FrameLayout,然后将该FrameLayout替换成我们自己的带有置灰代码的新FrameLayout即可。

方式二:走系统的流程,在系统的回调方法中替换

通过前面的源码得知,在不设置Factory的情况下,将调用到系统的onCreateView方法,所以我们也可以直接在Activity的onCreateView方法中加上以上代码,效果是一样的。

这两种方式差别不大,都是可以用的,这里建议使用第二种方式,尽量走系统自身的回调。


总结

本次我们从app全局置灰怎么来实现这一话题,渐渐深入,不断探索更优雅的实现方式。将一个ImageView置灰的猜想,引申出了十几行代码就置换了项目中所有的View,本质也是通过查看源码的方式,一步一步地套出了方案。本文方案参考自鸿洋大神的技术文章,笔者在此基础上进行了一些拓展和源码解释,这里也必须感谢一下大神们的默默付出,站在巨人的肩膀上感觉真心不错。看完以后对内容有疑问或者有改进建议的同学,欢迎一起探讨学习,共同进步!



相关文章