前言

我们都知道,安卓可以自定义状态栏是从 API19(4.4) 开始的,Google 给 KitKat 引入了 translucentStatusBar 特性。从此,安卓也可以像 iOS 一样自定义状态栏的颜色,甚至可以将布局布到状态栏后面。然而,一如往常谷歌出的东西都是坑的尿性,谷歌却没考虑到将状态栏设置为浅色后,状态栏上的文字和图标辨识困难的问题。(这不叫黑,这叫爱的深沉)

直到 Google 在 2015 年的 Google I/O 大会上推出了一个安卓状态栏的新特性:LightStatusBarWindow,即所谓的浅色状态栏模式。从 API19 到 API23 过了整整四个大版本,这才解决了这个问题。没错,重要的东西总是晚来一步。

然而,早在谷歌之前,国内的魅族与小米早已在自家的 Rom 中添加了浅色状态栏的功能。最低甚至支持 4.4 。但是很多安卓开发者并不知道此事,以至于很多 app 使用了浅色的状态栏导致用户无法看清状态栏上的文字图标。
左图是数字尾巴,正确使用了浅色状态栏模式。右图是某知名P2P app,未使用浅色状态栏模式,右上角的充电图标证明了该 app 并不是全屏状态而是的确没用浅色状态栏= =

实现

MIUI、Flyme以及原生安卓的浅色状态栏实现各不相同。而且这三个实现该功能的最低版本要求也不同。MIUI 的要求为 MIUI V6 及以上,Flyme 为 Flyme4 及以上,原生安卓为 API23 及以上。考虑到国内 MIUI 及 Flyme 用户的数量,所以我们的 app 中势必需要对当前手机系统进行判断之后调用合适的方法来设置浅色状态栏。

以下是我总结的判断手机系统浅色状态栏是否可用的工具类:

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
/**
* Created by Loyea.com on 7月20日.
*/
public class RomUtils {
class AvailableRomType {
public static final int MIUI = 1;
public static final int FLYME = 2;
public static final int ANDROID_NATIVE = 3;
public static final int NA = 4;
}
public static boolean isLightStatusBarAvailable () {
if (isMIUIV6OrAbove() || isFlymeV4OrAbove() || isAndroidMOrAbove()) {
return true;
}
return false;
}
public static int getLightStatausBarAvailableRomType() {
if (isMIUIV6OrAbove()) {
return AvailableRomType.MIUI;
}
if (isFlymeV4OrAbove()) {
return AvailableRomType.FLYME;
}
if (isAndroidMOrAbove()) {
return AvailableRomType.ANDROID_NATIVE;
}
return AvailableRomType.NA;
}
//Flyme V4的displayId格式为 [Flyme OS 4.x.x.xA]
//Flyme V5的displayId格式为 [Flyme 5.x.x.x beta]
private static boolean isFlymeV4OrAbove() {
String displayId = Build.DISPLAY;
if (!TextUtils.isEmpty(displayId) && displayId.contains("Flyme")) {
String[] displayIdArray = displayId.split(" ");
for (String temp : displayIdArray) {
//版本号4以上,形如4.x.
if (temp.matches("^[4-9]\\.(\\d+\\.)+\\S*")) {
return true;
}
}
}
return false;
}
//MIUI V6对应的versionCode是4
//MIUI V7对应的versionCode是5
private static boolean isMIUIV6OrAbove() {
String miuiVersionCodeStr = getSystemProperty("ro.miui.ui.version.code");
if (!TextUtils.isEmpty(miuiVersionCodeStr)) {
try {
int miuiVersionCode = Integer.parseInt(miuiVersionCodeStr);
if (miuiVersionCode >= 4) {
return true;
}
} catch (Exception e) {}
}
return false;
}
//Android Api 23以上
private static boolean isAndroidMOrAbove() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return true;
}
return false;
}
private static String getSystemProperty(String propName) {
String line;
BufferedReader input = null;
try {
Process p = Runtime.getRuntime().exec("getprop " + propName);
input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024);
line = input.readLine();
input.close();
} catch (IOException ex) {
return null;
} finally {
if (input != null) {
try {
input.close();
} catch (IOException e) {
}
}
}
return line;
}
}

当判断当前系统可以使用浅色状态栏可以用后,根据得到的 Rom 类型调用对应的设置浅色状态栏方法:

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
/**
* Created by Loyea.com on 7月20日.
*/
public class LightStatusBarUtils {
public static void setLightStatusBar(Activity activity, boolean dark) {
switch (RomUtils.getLightStatausBarAvailableRomType()) {
case RomUtils.AvailableRomType.MIUI:
setMIUILightStatusBar(activity, dark);
break;
case RomUtils.AvailableRomType.FLYME:
setFlymeLightStatusBar(activity, dark);
break;
case RomUtils.AvailableRomType.ANDROID_NATIVE:
setAndroidNativeLightStatusBar(activity, dark);
break;
case RomUtils.AvailableRomType.NA:
// N/A do nothing
break;
}
}
private static boolean setMIUILightStatusBar(Activity activity, boolean darkmode) {
Class<? extends Window> clazz = activity.getWindow().getClass();
try {
int darkModeFlag = 0;
Class<?> layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
darkModeFlag = field.getInt(layoutParams);
Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
extraFlagField.invoke(activity.getWindow(), darkmode ? darkModeFlag : 0, darkModeFlag);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
private static boolean setFlymeLightStatusBar(Activity activity, boolean dark) {
boolean result = false;
if (activity != null) {
try {
WindowManager.LayoutParams lp = activity.getWindow().getAttributes();
Field darkFlag = WindowManager.LayoutParams.class
.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON");
Field meizuFlags = WindowManager.LayoutParams.class
.getDeclaredField("meizuFlags");
darkFlag.setAccessible(true);
meizuFlags.setAccessible(true);
int bit = darkFlag.getInt(null);
int value = meizuFlags.getInt(lp);
if (dark) {
value |= bit;
} else {
value &= ~bit;
}
meizuFlags.setInt(lp, value);
activity.getWindow().setAttributes(lp);
result = true;
} catch (Exception e) {
}
}
return result;
}
private static void setAndroidNativeLightStatusBar(Activity activity, boolean dark) {
View decor = activity.getWindow().getDecorView();
if (dark) {
decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
} else {
// We want to change tint color to white again.
// You can also record the flags in advance so that you can turn UI back completely if
// you have set other flags before, such as translucent or full screen.
decor.setSystemUiVisibility(0);
}
}
}

以上就是我总结的用于设置浅色状态栏的两个主要类:RomUtils 用于判定当前系统是否可用浅色状态栏模式,以及当前系统的类型。LightStatusBarUtils 包含了各个系统对应的设置浅色状态栏的方法,开发者可以根据 RomUtils 得到的 Rom 类型调用其中对应的方法。

效果

接着我们创建一个 app 项目,来试试效果。在 MainActivity 的布局中我放了两个 TextView 和一个 Button。两个 TextView 分别用于显示当前系统版本信息和显示当前系统是否可用浅色状态栏模式。Button 用于在浅色状态栏模式和普通模式之间切换。

以下是 MainActivity :

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
public class MainActivity extends AppCompatActivity {
private boolean isLightStatusBarNow = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView sysInfoTv = (TextView) findViewById(R.id.tv_sys_info);
TextView hintTv = (TextView) findViewById(R.id.tv_hint);
Button switchBtn = (Button) findViewById(R.id.btn_switch);
sysInfoTv.setText(Build.BRAND + " - " + Build.MODEL + " - SDK Version:" + Build.VERSION.SDK_INT);
switch (RomUtils.getLightStatausBarAvailableRomType()) {
case RomUtils.AvailableRomType.MIUI:
hintTv.setText("当前系统为MIUI6或以上 浅色状态栏可用");
break;
case RomUtils.AvailableRomType.FLYME:
hintTv.setText("当前系统为Flyme4或以上 浅色状态栏可用");
break;
case RomUtils.AvailableRomType.ANDROID_NATIVE:
hintTv.setText("当前系统为Android M或以上 浅色状态栏可用");
break;
case RomUtils.AvailableRomType.NA:
hintTv.setText("当前系统浅色状态栏不可用");
switchBtn.setEnabled(false);
switchBtn.setText("light status bar mode not available");
break;
}
switchBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isLightStatusBarNow) {
LightStatusBarUtils.setLightStatusBar(MainActivity.this, false);
isLightStatusBarNow = false;
} else {
LightStatusBarUtils.setLightStatusBar(MainActivity.this, true);
isLightStatusBarNow = true;
}
}
});
}
}

手机上运行效果:
效果图

一些常见机型上的效果:
一些常见机型上的效果

存在的坑

  • 使用原生安卓的方法来实现浅色状态栏的前提是,当前 Activity 所在的 Window 必须有 windowDrawsSystemBarBackgrounds 属性,而且必须未设置过 windowTranslucentStatus 属性。
    如果当前 Window 设置了 windowTranslucentStatus 属性,那么调用上面设置浅色状态栏的方法就不会生效。
    详细见 API 说明:
    https://developer.android.com/reference/android/R.attr.html#windowLightStatusBar
  • 据说安卓原生实现方法在 MIUI android 版本为6.0上无效(见参考2,手头没设备测试)。所以我的代码中将 MIUI 和 Flyme 的判断放在原生安卓前面,这样如果是 MIUI 或 Flyme 的设备,优先调用各自的实现方法。

Demo代码

https://github.com/Loyea/LightStatusBarModeDemo

参考:

  1. http://blog.isming.me/2016/01/09/chang-android-statusbar-text-color/
  2. http://www.jianshu.com/p/7f5a9969be53
  3. http://dev.xiaomi.com/doc/?p=254
  4. http://blog.csdn.net/devilkin64/article/details/19415717