Esquema de realización global en blanco y negro de la aplicación de Android

¡Gracias por unirte a mí para luchar contra monstruos y actualizarte en el mundo de Android!

Durante el Festival Ching Ming, todas las aplicaciones principales se procesarán en blanco y negro. Cuando recibí esta solicitud, me sentí muy molesto. ¿Necesito crear otra máscara?

Sin embargo, después de una serie de búsquedas, encontré el plan de implementación de dos grandes dioses ( Hong Yang , U2tzJTNE ), ¡que en realidad es bastante simple!

Pongámonos de pie sobre los hombros de gigantes para analizar el principio y pensar si existe una implementación más fácil.

1. Principio

Los esquemas de atenuación de los dos maestros son los mismos y ambos pueden ver el mismo fragmento de código:

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

Todos usan ColorMatrix (matriz de color) proporcionada por Android y establecen su saturación en 0, de modo que los estilos gris y blanco sin saturación se dibujan con Paint.

Sin embargo, los dos eligieron diferentes esquemas al usar Paint para dibujar.

1.1 Hongyang: anular el método de dibujo

El profesor Hongyang analizó, si establecemos la saturación del diseño raíz de cada actividad en 0 , ¿está bien?

¿Quién es ese diseño?

El Sr. Hongyang analizó nuestro diseño y finalmente setContentView se establecerá en un FrameLayout de R.id.content.

Personalicemos un GrayFrameLayout, use este pincel con una saturación de 0 al dibujar, y el diseño envuelto por este FrameLayout se volverá en blanco y negro.

// 转载自鸿洋
// 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();
    }
}
复制代码

Luego usamos GrayFrameLayout para reemplazar el FrameLayout de este R.id.content, ¿podemos hacer que la página sea en blanco y negro?

El método para reemplazar FrameLayout se puede ver en el artículo de [Hong Yang] .

1.2 U2tzJTNE: monitorear la adición de DecorView

El jefe de U2tzJTNE utilizó otro esquema ingenioso.

他先创建了一个具有数据变化感知能力的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);
            }
           
			....
        });
    }
}
复制代码

¿No parece más sencillo de esta manera? ¡Usando el método nativo de la aplicación para lograr blanco y negro! Por supuesto, también hay desventajas, porque la configuración en el nivel de Actividad no puede cambiar instantáneamente la página actual a blanco y negro.

3. Asuntos que requieren atención

Estos tres esquemas utilizan la matriz de color, por lo que los hoyos son los mismos, tenga en cuenta.

3.1 El fondo de la ventana de la imagen de inicio no puede cambiar de color

El fondo de la ventana ya se muestra cuando podemos configurar el renderizado.

Solución: solo puede modificarlo en el paquete actual o ignorarlo.

3.2 SurfaceView no puede cambiar de color

Debido a que usamos setLayerType para volver a dibujar, y SurfaceView tiene una ventana independiente, que está separada de la ventana en el diseño y se ejecuta en otros subprocesos sin afectar el dibujo del subproceso principal, por lo que la solución actual no puede cambiar el color de SurfaceView.

Solución: 1. Utilice TextureView. 2. Verifique si este SurfaceView puede configurar un filtro, generalmente algunos reproductores de terceros o de fabricación propia.

3.3 Cambio de color multiproceso

Es posible que tengamos un subprograma incorporado en la aplicación, y el subprograma básicamente se ejecuta en un proceso separado, pero si nuestra configuración en blanco y negro cambia durante el proceso en ejecución, otros procesos no pueden percibirlo.

Solución: use MMKV para almacenar la configuración en blanco y negro, configure el uso compartido de múltiples procesos y juzgue la pantalla en blanco y negro antes de abrir el subprograma.

Resumir

Finalmente, resumamos el esquema en blanco y negro.

Use ColorMatrix para establecer la saturación en 0, configúrelo en Paint y deje que el diseño raíz tome este Paint para volver a dibujar.

Esto concluye la introducción al blanco y negro global de APP. Espero que después de leer este artículo, tenga una comprensión más profunda del blanco y negro de APP. Si mi artículo puede brindarle un pequeño beneficio, entonces seré lo suficientemente feliz.

¡hasta luego!

Supongo que te gusta

Origin juejin.im/post/7167300200921301028
Recomendado
Clasificación