作者:小帅
链接:https://zhuanlan.zhihu.com/p/587516253
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

在清明节时各大APP都会进行黑白化处理,当时在接到这个需求的时候感觉好麻烦,是不是又要搞一套皮肤?

然而在一系列搜索之后,找到了两位大神(鸿洋、U2tzJTNE)的实现方案,其实相当的简单!

让我们一起站在巨人的肩膀上来分析一下原理,并思考会不会有更简便的实现?

一、原理

两位大神的置灰方案是相同的,都能看到一段同样的代码:

Paint mPaint = new Paint();
ColorMatrix mColorMatrix = new ColorMatrix();
// 设置饱和度为0
mColorMatrix.setSaturation(0);
mPaint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix));

他们都用了Android提供的ColorMatrix(颜色矩阵),将其饱和度设置为0,这样使用Paint绘制出来的都是没有饱和度的灰白样式!

然而两位在何时使用Paint绘制时选择了不同方案。

1.1 鸿洋:重写draw方法

鸿洋分析,如果我们把每个Activity的根布局饱和度设置为0是不是就可以了?

那根布局是谁?

鸿洋分析我们的布局最后setContentView最后都会设置到一个R.id.content的FrameLayout当中。

我们去自定义一个GrayFrameLayout,在draw的时候使用这个饱和度为0的画笔,被这个FrameLayout包裹的布局都会变成黑白。

// 转载自鸿洋
// https://blog.csdn.net/lmj623565791/article/details/105319752
public class GrayFrameLayout extends FrameLayout {private Paint mPaint = new Paint();public GrayFrameLayout(Context context, AttributeSet attrs) {super(context, attrs);ColorMatrix cm = new ColorMatrix();cm.setSaturation(0);mPaint.setColorFilter(new ColorMatrixColorFilter(cm));}@Overrideprotected void dispatchDraw(Canvas canvas) {canvas.saveLayer(null, mPaint, Canvas.ALL_SAVE_FLAG);super.dispatchDraw(canvas);canvas.restore();}@Overridepublic void draw(Canvas canvas) {canvas.saveLayer(null, mPaint, Canvas.ALL_SAVE_FLAG);super.draw(canvas);canvas.restore();}
}

然后我们用GrayFrameLayout去替换这个R.id.content的FrameLayout,是不是就可以做到将页面黑白化了?

替换FrameLayout的方法可以去【鸿洋】这篇文章下查看。

1.2 U2tzJTNE:监听DecorView的添加

U2tzJTNE大佬 使用了另一种巧妙的方案。

他先创建了一个具有数据变化感知能力的ObservableArrayList(当内容发生变化有回调)。

之后使用反射将WindowManagerGlobal内的mViews容器(ArrayList,该容器会存放所有的DecorView),替换为ObservableArrayList,这样就可以监听到每个DecorView的创建,并且拿到View本身。

拿到DecorView,那就可以为所欲为了!

大佬使用了setLayerType(View.LAYER_TYPE_HARDWARE, mPaint),对布局进行了重绘。至于为什么要用LAYER_TYPE_HARDWARE?因为默认的View.LAYER_TYPE_NONE会把Paint强制设置为null。

// 转载自U2tzJTNE
// https://juejin.cn/post/6892277675012915207
public static void enable(boolean enable) {try {//灰色调Paintfinal Paint mPaint = new Paint();ColorMatrix mColorMatrix = new ColorMatrix();mColorMatrix.setSaturation(enable ? 0 : 1);mPaint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix));//反射获取windowManagerGlobal@SuppressLint("PrivateApi")Class<?> windowManagerGlobal = Class.forName("android.view.WindowManagerGlobal");@SuppressLint("DiscouragedPrivateApi")java.lang.reflect.Method getInstanceMethod = windowManagerGlobal.getDeclaredMethod("getInstance");getInstanceMethod.setAccessible(true);Object windowManagerGlobalInstance = getInstanceMethod.invoke(windowManagerGlobal);//反射获取mViewsField mViewsField = windowManagerGlobal.getDeclaredField("mViews");mViewsField.setAccessible(true);Object mViewsObject = mViewsField.get(windowManagerGlobalInstance);//创建具有数据感知能力的ObservableArrayListObservableArrayList<View> observerArrayList = new ObservableArrayList<>();observerArrayList.addOnListChangedListener(new ObservableArrayList.OnListChangeListener() {@Overridepublic void onChange(ArrayList list, int index, int count) {}@Overridepublic void onAdd(ArrayList list, int start, int count) {// 拿到DecorView触发重绘View view = (View) list.get(start);if (view != null) {view.setLayerType(View.LAYER_TYPE_HARDWARE, mPaint);}}@Overridepublic void onRemove(ArrayList list, int start, int count) {}});//将原有的数据添加到新创建的listobserverArrayList.addAll((ArrayList<View>) mViewsObject);//替换掉原有的mViewsmViewsField.set(windowManagerGlobalInstance, observerArrayList);} catch (Exception e) {e.printStackTrace();}
}

只需要在Application里面调用该方法即可。

1.3 方案分析

两位大佬的方案都非常的棒,咱们理性的来对比一下。

  • 鸿洋: 使用自定义FrameLayout的方案需要一个BaseActivity统一设置,稍显麻烦,代码侵入性较强。
  • U2tzJTNE: 方案更加简单、动态,一行代码设置甚至可以做到在当前页从彩色变黑白,但是使用了反射,有一点点性能消耗。

二、简易方案(直接复制)

既然研究明白了大佬的方案,那有没有又不需要反射,设置又简单的方法呢?

能不能使用原生方式获取DecorView的实例呢?

突然灵光一闪,Application里面不是有registerActivityLifecycleCallbacks这个注册监听方法吗?监听里面的onActivityCreated不是可以获取到当前的Activity吗?那DecorView不就拿到了!

搞起!上代码!

public class StudyApp extends Application {@Overridepublic void onCreate() {super.onCreate();Paint mPaint = new Paint();ColorMatrix mColorMatrix = new ColorMatrix();mColorMatrix.setSaturation(0);mPaint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix));registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {@Overridepublic void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {// 当Activity创建,我们拿到DecorView,使用Paint进行重绘View decorView = activity.getWindow().getDecorView();decorView.setLayerType(View.LAYER_TYPE_HARDWARE, mPaint);}....});}
}

这样看起来是不是更简单了!使用了APP原生的方法实现了黑白化!当然也有缺点,因为在Activity级别设置,无法做到在当前页面即时变为黑白。

三、注意事项

这三种方案因为都使用了颜色矩阵,所以坑都是一样的,请注意。

3.1 启动图windowBackground无法变色

在我们可以设置渲染的时候windowBackground已经展示完毕了。

解决方案:只能在当前的包里修改,或者不去理会。

3.2 SurfaceView无法变色

因为我们使用了setLayerType进行重绘,而SurfaceView是有独立的Window,脱离布局内的Window,运行在其他线程,不影响主线程的绘制,所以当前方案无法使SurfaceView变色。

解决方案:
1、使用TextureView。
2、看下这个SurfaceView是否可以设置滤镜,正常都是一些三方或者自制的播放器。

3.3 多进程变色

我们可能会在APP内置小程序,小程序基本是运行在单独的进程中,但是如果我们的黑白配置在运行过程中发生变化,其他进程是无法感知的。

解决方案:使用 MMKV 存储黑白配置,并设置多进程共享,在开启小程序之前都判断一下黑白展示。

总结

最后咱们再总结一下黑白化方案。

使用了ColorMatrix设置饱和度为0,设置到Paint中,让根布局拿着这个Paint去进行重绘。

这样APP全局黑白化的介绍就结束了,希望大家读完这篇文章,会对APP黑白化有一个更深入的了解。如果我的文章能给大家带来一点点的福利,那在下就足够开心了。

更多Android 知识点归整

Android 性能调优系列https://0a.fit/dNHYY

Android 车载学习指南https://0a.fit/jdVoy

Android Framework核心知识点笔记https://0a.fit/acnLL

Android 音视频学习笔记https://0a.fit/BzPVh

Jetpack全家桶(含Compose)https://0a.fit/GQJSl

Kotlin 入门到精进https://0a.fit/kdfWR

Flutter 基础到进阶实战https://0a.fit/xvcHV

Android 八大知识体系https://0a.fit/mieWJ

Android 中高级面试题锦https://0a.fit/YXwVq

后续如有新知识点,将会持续更新,尽请期待……

Android APP全局黑白化实现方案 - 小帅的文章 - 知乎 https://zhuanlan.zhihu.com/p/587516253

Android APP全局黑白化实现方案相关推荐

  1. 安卓APP全局黑白化实现方案

    感谢大家和我一起,在Android世界打怪升级! 在清明节时各大APP都会进行黑白化处理,当时在接到这个需求的时候感觉好麻烦,是不是又要搞一套皮肤? 然而在一系列搜索之后,找到了两位大神(鸿洋.U2t ...

  2. 【转】Android APP 启动黑屏优化补丁 (2016-01-18 10:35:23)

    [转]Android APP 启动黑屏优化补丁 (2016-01-18 10:35:23) 转载▼ 这个必须转,这才是高手,解决官方都解决不好的问题! 原文地址,以下为转贴内容: 使用说明 ***** ...

  3. android 自动替换资源文件,简单高效的实现Android App全局字体替换

    Android O推出了一项新的功能「Fonts in XML」,借助这项功能,我们能够像使用其他资源文件一样使用字体,比较方便地实现App全局字体的替换. 为了能够在API 14或者以上的设备上使用 ...

  4. Android App 线上热修复方案

    热修复一词恐怕最早应用在微软.为了巩固其windows系统和office的市场占有率,微软开发并维护了一套线上修复方案,用于修复漏洞及特定问题(LDR),避免延续到发版解决(GDR),详见HotFix ...

  5. android app全局字体,Android app全局字体设置

    相信很多对设计追求极致的开发者们对Android系统的默认字体都会感到不满意,这个时候需要使用自定义的字体,当然可以使用系统提供的Typeface来加载自定义字体,但是,一个个TextView的设置, ...

  6. Android App 线上热修复方案Xposed

    热修复一词恐怕最早应用在微软.为了巩固其windows系统和office的市场占有率,微软开发并维护了一套线上修复方案,用于修复漏洞及特定问题(LDR),避免延续到发版解决(GDR),详见HotFix ...

  7. Android App罕见错误和优化方案

    本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 1.App如果被定义一个有参数构造函数,那么需要再定义一个无参数的,如果不则会在某些情况下初始化失败 ...

  8. Android app启动白屏适配方案

    至于app启动时候为什么会白屏,咱们不多赘述,相关的文章以及启动时间优化文章多了去了.咱们这里主要讲述优化完成后如何解决白屏问题. app启动时候黑屏或者白屏是还没加载到布局文件,就已经显示了wind ...

  9. Android APP全局置灰

    在activity基类BaseActivity的onCreate方法中添加以下代码: val paint = Paint() val cm = ColorMatrix() cm.setSaturati ...

最新文章

  1. 性能,安全,集成才是web之道
  2. Debug常用命令 精简版本
  3. 深度学习(神经网络)[1]——单层感知器
  4. centos7.3 编译安装 git 2.13
  5. Mac系统容易忽视但很实用的命令整理
  6. 46 -算法 - Leetcode -169 - 多数元素 - map insert 迭代器
  7. Shiro 权限管理入门之认证与授权
  8. 关于循环经济的三维展示
  9. iOS 简易音乐播放界面
  10. mac上谷歌浏览器添加插件显示程序包无效的解决办法
  11. 【题解】LuoGu4408:[NOI2003]逃学的小孩
  12. ~蓝杰那些事儿~2014.06.21.~胡先生和魏小姐的故事
  13. Web前端html表格制作
  14. H5案例分享:微信视频播放全屏问题
  15. 附加类型”XXXX“的实体失败,因为相同类型的其他实体已具有相同的主键值。在使用 “Attach“ 方法或者将实体的状态设置为 “Unchanged“ 或 “Modified“
  16. 共享单车背后还隐藏着多少惊天秘密?
  17. C# GDAL 数字图像处理Part7 仿射变换图像配准
  18. Java基础类中的恶作剧?
  19. 怎样在matlab中查找函数的具体代码,几种查看Matlab函数源代码的方法
  20. python编程从入门到实践 配套资源下载地址 免费!

热门文章

  1. 离开学校怎样自学修炼成为一名网页设计师(四)
  2. unity3d培训_005
  3. 在 ASP.NET Core 使用 IOptions pattern
  4. Spring Gateway路由网关
  5. java有声小说如何开发_怎么才能做有声小说播音?有声书主播如何训练?
  6. word 标尺快捷键
  7. 2014诺贝尔物理学奖:蓝光LED
  8. 传智播客就是牛人培养牛人的地方!
  9. layim使用中添加自定义事件与显示样式
  10. iMeta | 调控肠道菌群的宿主源代谢分子概述