• Android 界面灰化处理(特殊节日)


    这是一篇迟到了许久的文章,因工作原因一直没时间写(好吧,其实就是懒)。。

    导语

    清明节当天,各大公司都将自己的应用设成了全局灰色调,以哀悼逝去的同胞,作为程序猿的我们,当然要研究一下此技术的实习方案。

    来吧,先看效果

    原图:

    变灰后:

    思路

    首先,核心代码如下:

    Paint paint = new Paint();
    ColorMatrix cm = new ColorMatrix();
    cm.setSaturation(0);
    mPaint.setColorFilter(new ColorMatrixColorFilter(cm));
    view.setLayerType(View.LAYER_TYPE_HARDWARE, paint);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    上述代码原理是:
    创建一个饱和度为0的颜色过滤器(饱和度为0时,view就变成了黑白色),将这个颜色过滤器通过setLayerType方法设置到当前View上。(此方法会开启此View的硬件加速功能)

    确定了原理,我们现在要确定代码执行的时机:
    因为我们要实现的功能是全局灰度化,所以首先view的选型最好是一个根view,这样我们就不用在每个子view上重复操作了,其次我们还要适配Dialog和PopWindow等Window类型的组件,所以我们不能从Activity上动心思了,因为Activity本质也是一个Window,所以综上所述,我们将代码的执行时机选在了WindowManager的addView方法上面,因为不论是Activity的创建还是Dialog等组件的创建最终都会走到这个方法里面。
    我们只要在WindowManager执行addView方法时进行hook,加入我们上面的代码逻辑即可。

    实现

    上面确定了大体思路,我们现在来进一步分析实现方案:

    首先,既然是要进行hook操作,那么我们就要先确定hook点:

    WindowManager是一个接口类,其实现类为WindowManagerImpl,而WindowManagerImpl中真正实现窗口操作的类为WindowManagerGlobal,是一个单例类,我们看一下其addView的实现:

    public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
        mViews.add(view);
    }
    
    • 1
    • 2
    • 3

    可以发现方法内部将view保存到了一个list里面,那我们可以hook这个list,在其添加view的时候,我们就调用上面的代码将其置灰,那如何才能感知到view的添加操作呢?

    实现具有数据感知能力的list:

    我们可以自定义一个数组,让其继承于 ArrayList,在其执行添加删除操作时,通过回调进行通知,具体实现如下:

    public class ObservableArrayList<T> extends ArrayList<T> {
        private List<OnListChangeListener> mListeners = new ArrayList<>();
    
        public void addOnListChangedListener(OnListChangeListener listener) {
            if (mListeners != null) {
                mListeners.add(listener);
            }
        }
    
        public void removeOnListChangedListener(OnListChangeListener listener) {
            if (mListeners != null) {
                mListeners.remove(listener);
            }
        }
    
        @Override
        public boolean add(T object) {
            super.add(object);
            notifyAdd(size() - 1, 1);
            return true;
        }
    
        @Override
        public void add(int index, T object) {
            super.add(index, object);
            notifyAdd(index, 1);
        }
    
        @Override
        public boolean addAll(Collection<? extends T> collection) {
            int oldSize = size();
            boolean added = super.addAll(collection);
            if (added) {
                notifyAdd(oldSize, size() - oldSize);
            }
            return added;
        }
    
        @Override
        public void clear() {
            int oldSize = size();
            super.clear();
            if (oldSize != 0) {
                notifyRemove(0, oldSize);
            }
        }
    
        @Override
        public T remove(int index) {
            T val = super.remove(index);
            notifyRemove(index, 1);
            return val;
        }
    
        @Override
        public boolean remove(Object object) {
            int index = indexOf(object);
            if (index >= 0) {
                remove(index);
                return true;
            } else {
                return false;
            }
        }
    
        @Override
        public T set(int index, T object) {
            T val = super.set(index, object);
            if (mListeners != null) {
                for (OnListChangeListener listener : mListeners) {
                    listener.onChange(this, index, 1);
                }
            }
            return val;
        }
    
        @Override
        protected void removeRange(int fromIndex, int toIndex) {
            super.removeRange(fromIndex, toIndex);
            notifyRemove(fromIndex, toIndex - fromIndex);
        }
    
        private void notifyAdd(int start, int count) {
            if (mListeners != null) {
                for (OnListChangeListener listener : mListeners) {
                    listener.onAdd(this, start, count);
                }
            }
        }
    
        private void notifyRemove(int start, int count) {
            if (mListeners != null) {
                for (OnListChangeListener listener : mListeners) {
                    listener.onRemove(this, start, count);
                }
            }
        }
    
        public interface OnListChangeListener {
    
            void onChange(ArrayList list, int index, int count);
    
            void onAdd(ArrayList list, int start, int count);
    
            void onRemove(ArrayList list, int start, int count);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107

    接下来就是执行hook操作:

    /**
     * @param enable 是否开启全局灰色调
     */
    public static void enable(boolean enable) {
        if (!enable) {
            return;
        }
        try {
            //灰色调Paint
            final Paint mPaint = new Paint();
            ColorMatrix mColorMatrix = new ColorMatrix();
            mColorMatrix.setSaturation(0);
            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) {
                    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    最后,只需在Application的onCreate中调用上述代码进行hook即可。

    已知存在的问题

    1、开启了View的硬件加速,可能会存在兼容性问题
    2、对于SurfaceView和Textureview以及其子类无效

    对于问题1笔者在项目中暂未遇到问题,如果读者朋友们在项目中遇到兼容性问题,欢迎留言;

    对于问题2其原因是因为这两个组件内部有独立的绘图表面(Surface),笔者目前未想到好的解决方案,如果有思路欢迎一块交流。

    组件使用

    allprojects {
       repositories {
          ...
            maven { url 'https://jitpack.io' }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    添加依赖:

    implementation 'com.github.U2tzJTNE:GlobalGray:1.0'
    
    • 1

    Application中全局调用开启

     GlobalGray.enable(true);
    
    • 1

    https://github.com/U2tzJTNE/GlobalGray

    转自知乎用户

  • 相关阅读:
    从Outlook到Notes
    vue 鼠标移入移出事件执行多次(尤其ie)
    毕业设计-电子商务网站(一)
    【Hack The Box】linux练习-- Doctor
    史上最全 JVM 性能调优:线程 + 子系统 + 类加载 + 内存分配 + 垃圾回收(内附最全学习笔记),不得不服,面试官也问不出毛病
    java常见面试题(未整理,后续可能一直更新)
    C++11标准模板(STL)- 算法(std::nth_element)
    【第三篇】商城系统-基础业务-实现类别管理
    第17集丨如何为成功“保鲜”
    结构体数组经典运用---选票系统
  • 原文地址:https://blog.csdn.net/qq_32136827/article/details/128150700