Android App首页全局黑白化方案探索

本文探讨了Android应用实现全局黑白化的三种方法,包括重写draw方法、监听DecorView以及简易方案,并分析了各自的优缺点。同时,文章指出了启动图、SurfaceView和多进程场景下的变色问题及其解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

前言

一、原理

二、 重写 draw 方法

三 、监听 DecorView 

四、方案分析

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

六、注意事项

1 、启动图windowBackground无法变色

2、 SurfaceView无法变色

3、 多进程变色

总结


前言

        关于黑白化页面,这几年是比较常见的场景。比如,在清明节或特殊时政,各大APP都会进行黑白化处理。普通小白刚接到需求的时一定会感觉是不是好麻烦,要搞一套换肤吗?

        下面有三种实现方案,一起来探索吧!~


一、原理

置灰方案的原理都是相同的,看下面一段代码:

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

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

具体在何时使用Paint绘制时选择了不同逻辑。


二、 重写 draw 方法

如果我们把每个 Activity 的根布局饱和度设置为0是不是就可以了?那根布局是谁?

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

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

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));
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        canvas.saveLayer(null, mPaint, Canvas.ALL_SAVE_FLAG);
        super.dispatchDraw(canvas);
        canvas.restore();
    }

    @Override
    public void draw(Canvas canvas) {
        canvas.saveLayer(null, mPaint, Canvas.ALL_SAVE_FLAG);
        super.draw(canvas);
        canvas.restore();
    }
}

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


三 、监听 DecorView 

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

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

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

public static void enable(boolean enable) {
    try {
        //灰色调Paint
        final 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);
        //反射获取mViews
        Field mViewsField = windowManagerGlobal.getDeclaredField("mViews");
        mViewsField.setAccessible(true);
        Object mViewsObject = mViewsField.get(windowManagerGlobalInstance);

        //创建具有数据感知能力的ObservableArrayList
        ObservableArrayList<View> observerArrayList = new ObservableArrayList<>();
        observerArrayList.addOnListChangedListener(new ObservableArrayList.OnListChangeListener() {
            @Override
            public void onChange(ArrayList list, int index, int count) {
            }

            @Override
            public 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);
                }
            }

            @Override
            public void onRemove(ArrayList list, int start, int count) {
            }
        });
        //将原有的数据添加到新创建的list
        observerArrayList.addAll((ArrayList<View>) mViewsObject);
        //替换掉原有的mViews
        mViewsField.set(windowManagerGlobalInstance, observerArrayList);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

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


四、方案分析

前面两个的方案都非常的棒,咱们理性的来对比一下。

重写 draw 方法:使用自定义 FrameLayout 的方案需要一个 BaseActivity 统一设置,稍显麻烦,代码侵入性较强。

监听 DecorView :方案更加简单、动态,一行代码设置甚至可以做到在当前页从彩色变黑白,但是使用了反射,有一点点性能消耗。

那有没有又不需要反射,设置又简单的方法呢?能不能使用原生方式获取 DecorView 的实例呢?


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

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

public class StudyApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Paint mPaint = new Paint();
        ColorMatrix mColorMatrix = new ColorMatrix();
        mColorMatrix.setSaturation(0);
        mPaint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix));
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public 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 级别设置,无法做到在当前页面即时变为黑白。


六、注意事项

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

1 、启动图windowBackground无法变色

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

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

2、 SurfaceView无法变色

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

解决方案:

  1. 使用 TextureView

  2. 看下这个 SurfaceView 是否可以设置滤镜,正常都是一些三方或者自制的播放器。

3、 多进程变色

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

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


总结

最后咱们再总结一下黑白化方案: 使用了 ColorMatrix 设置饱和度为 0,设置到 Paint 中,让根布局拿着这个 Paint 去进行重绘。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

艾阳Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值