Android 界面灰化处理

  • ~10.60K 字

在某些特定时刻,我们需要把app所有界面变为灰色,在web界面中我们可以使用filter:gray或者-webkit-filter: grayscale(100%);添加灰色滤镜,那么在app中该如何实现呢?

原理

在APP中将界面变灰,需要创建一个饱和度为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的实现:

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
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}

final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}

ViewRootImpl root;
View panelParentView = null;

synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}

int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}

// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}

root = new ViewRootImpl(view.getContext(), display);

view.setLayoutParams(wparams);

mViews.add(view);
mRoots.add(root);
mParams.add(wparams);

// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}

我们最终是要操作view,可以看到所有的view都是添加到mViews这个字段中,那我们可以hook这个list,在其添加view的时候,我们就调用上面的代码将其置灰,那如何才能感知到view的添加操作呢?

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

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

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

  2. hook操作

    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
    public class GlobalGray {
    /**
    * @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();
    }
    }

    }

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

    1
    GlobalGray.enable(true);
赞助喵
非常感谢您的喜欢!
赞助喵
分享这一刻
让朋友们也来瞅瞅!