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

感谢我们和我一同,在Android世界打怪升级!

在清明节时各大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当中。

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

我们去自定义一个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));
    }
    @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,是不是就能够做到将页面是非化了?

替换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:///post/6892277675012915207
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里面调用该办法即可。

1.3 计划剖析

两位大佬的计划都非常的棒,我们理性的来比照一下。

  • 鸿洋教师: 运用自定义FrameLayout的计划需要一个BaseActivity一致设置,稍显费事,代码侵入性较强。

  • U2tzJTNE大佬: 计划更加简略、动态,一行代码设置甚至能够做到在当时页从五颜六色变是非,可是运用了反射,有一点点功能消耗。

二、简易计划(直接仿制)

既然研讨明白了大佬的计划,那有没有又不需要反射,设置又简略的办法呢?

能不能运用原生方式获取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级别设置,无法做到在当时页面即时变为是非。

三、注意事项

这三种计划因为都运用了色彩矩阵,所以坑都是一样的,请注意。

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是非化有一个更深入的了解。假如我的文章能给我们带来一点点的福利,那在下就满足高兴了。

下次再见!

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