Android APP グローバル白黒実現スキーム

モンスターとの戦いや Android の世界でのアップグレードにご参加いただき、ありがとうございます。

清明節の期間中、主要なアプリはすべて白黒で処理されます.このリクエストを受け取ったとき、私は非常に面倒でした.別のスキンを作成する必要がありますか?

しかし、一連の検索の後、2 つの偉大な神 ( Hong YangU2tzJTNE ) の実装計画を見つけました。これは実際には非常に単純です!

巨人の肩に乗って原理を分析し、もっと簡単に実装できる方法はないか考えてみましょう。

1.原則

2 つのマスターのグレイ表示スキームは同じであり、どちらも同じコードを見ることができます。

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

いずれも Android が提供する ColorMatrix (カラー マトリックス) を使用し、その彩度を 0 に設定して、彩度のないグレーと白のスタイルをすべてペイントで描画します。

ただし、ペイントを使用して描画する場合、2 人は異なるスキームを選択しました。

1.1 Hongyang: draw メソッドをオーバーライドする

Hongyang 先生は、各 Activity のルート レイアウトの飽和度を 0 に設定しても問題ないかどうかを分析しました。

そのレイアウトは誰ですか?

Hongyang 氏がレイアウトを分析し、最終的に setContentView が R.id.content の FrameLayout に設定されます。

GrayFrameLayout をカスタマイズして、描画時に彩度 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 の置き換え方法は[Hong Yang]の記事で見ることができます。

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 {
        //灰色调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. 注意事項

これら 3 つのスキームはすべてカラー マトリックスを使用するため、ピットは同じです。

3.1 起動イメージの windowBackground の色を変更できない

windowBackground は、レンダリングを設定できる時点で既に表示されています。

解決策: 現在のパッケージでのみ変更するか、無視することができます。

3.2 SurfaceView で色を変更できない

再描画には setLayerType を使用し、SurfaceView には独立した Window があり、レイアウトで Window から分離され、メイン スレッドの描画に影響を与えずに他のスレッドで実行されるため、現在のソリューションでは SurfaceView の色を変更できません。

解決策: 1. TextureView を使用します。2. この SurfaceView がフィルタを設定できるかどうかを確認します。通常はサードパーティまたは自作のプレーヤーです。

3.3 多工程色替え

アプリにアプレットが組み込まれている場合があり、アプレットは基本的に別のプロセスで実行されますが、実行プロセス中に白黒構成が変更された場合、他のプロセスはそれを認識できません。

解決策: MMKV を使用して白黒構成を保存し、マルチプロセス共有を設定し、アプレットを開く前に白黒表示を判断します。

要約する

最後に、黒と白のスキームをまとめましょう。

ColorMatrix を使用して彩度を 0 に設定し、それを Paint に設定して、ルート レイアウトがこの Paint を再描画するようにします。

以上でAPPのグローバルな白黒の紹介を終わりますが、この記事を読んでAPPの白黒についての理解を深めていただければ幸いです。私の記事があなたに少しでもお役に立てれば幸いです。

また後で!

おすすめ

転載: juejin.im/post/7167300200921301028