沉浸式状态栏

同事为状态栏有蒙层和无法调整黑色字符图标困扰了很久,最近稍微闲下来了,就开始搞一搞。我翻看了今日头条、微博的处理情况,都很ok啊。算是同是做资讯的,没理由咱不行啊,待我试试?

  • 沉浸式状态栏版本兼容情况
Android版本 <4.4 4.4-5.0 =>5.0
透明状态栏 ×
Android版本 <6.0 =>6.0
黑白字符状态栏 ×

fitsSystemWindows使用

  • 官方描述

Boolean internal attribute to adjust view layout based on system windows such as the status bar. If true, adjusts the padding of this view to leave space for the system windows. Will only take effect if this view is in a non-embedded activity.

  • 中文描述

Boolean内部属性是基于系统窗口(如status bar)调整视图布局。如果为true,将调整视图padding为系统窗口预留出空间。Will only take effect if this view is in a non-embedded activity. 这句不是很明白,我想官方想表达的意思是,要想生效,需要设置沉浸式状态栏。

  • 注意: 该方法在4.4以上才会生效,android:fitsSystemWindows默认值为false,并且在哪个控件设置android:fitsSystemWindows=”true”会有不一样的效果:
  1. 不设置android:fitsSystemWindows值,效果如图1
  2. 只要LinearLayout设置了android:fitsSystemWindows=”true”,子控件(如下面布局代码中TextView)不管设不设置为true,只会为LinearLayout添加padding给系统窗口预留空间,效果如图2
  3. 只设置TextView的android:fitsSystemWindows=”true”,只会为TextView添加padding给系统窗口预留空间,有一个灰色蒙层导致色值有点偏差,往下看有解决方法,效果如图3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@mipmap/beauty"
tools:context="com.blacktoast.demo.statusbar.MainActivity">

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello World!"
android:background="#55ff0000"
android:fitsSystemWindows="true" />

</LinearLayout>

不设置android:fitsSystemWindows值

LinearLayout设置了android:fitsSystemWindows="true"

只设置TextView的android:fitsSystemWindows="true"

透明状态栏(灰色蒙层)

  1. <4.4版本无法调整透明状态栏

    无处理方案,GG

  2. 4.4 <= 版本 < 5.0 系统提供方法调整透明状态栏

    分为两种方法实现styles.xml和代码调整

styles.xml

  • values/styles.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="utf-8"?>
<resources>

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>

<style name="AppTheme.MainTheme"></style>
</resources>
  • values-v19/styles.xml
1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme.MainTheme">
<item name="android:windowTranslucentStatus">true</item>
</style>
</resources>

代码调整

1
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
  1. 版本>= 6.0可能有灰色蒙层(我们需要解决的问题在这)

之前没遇到这个问题,一接手有点懵逼。。先Google看看?。参照这个链接试了一把解决Android7.0下沉浸式状态栏变灰问题,反正我是没试成功。。博主分析的没毛病,后来一细想,应该是我没添加FLAG_TRANSLUCENT_STATUS的原因。我没去深究,因为我发现新的解决思路。

  • 7.0DecorView源码
1
2
3
4
5
6
7
8
9
10
11
12
DecorView(Context context, int featureId, PhoneWindow window,WindowManager.LayoutParams params) {
super(context);
......//省略无关代码
mForceWindowDrawsStatusBarBackground = context.getResources()
.getBoolean(R.bool.config_forceWindowDrawsStatusBarBackground)
&& context.getApplicationInfo().targetSdkVersion >= N;

//设置默认的值,灰色
mSemiTransparentStatusBarColor = context.getResources()
.getColor(R.color.system_bar_background_semi_transparent, null /* theme */);
......//省略无关代码
}
1
2
3
4
5
6
7
private int calculateStatusBarColor() {
int flags = mWindow.getAttributes().flags;
return (flags & FLAG_TRANSLUCENT_STATUS) != 0 ?
mSemiTransparentStatusBarColor :
(flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 ?
mWindow.mStatusBarColor : Color.BLACK;
}
  • 博主是通过反射更改默认色值,博主的分析我就不讲解了,可以自己去详细看。系统获取状态栏色值主要是通过calculateStatusBarColor()方法,分为三种情况:
  1. 如果window的flag包含FLAG_TRANSLUCENT_STATUS,则使用mSemiTransparentStatusBarColor色值,即使用R.color.system_bar_background_semi_transparent色值;
  2. 如果window的flag不包含FLAG_TRANSLUCENT_STATUS,并且window的flag包含FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS时,使用mWindow.mStatusBarColor色值,即setStatusColor的色值;
  3. 如果window的flag不包含FLAG_TRANSLUCENT_STATUS,并且window的flag不包含FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS时,使用黑色Color.BLACK

使用方式1和2都能实现透明状态栏的效果,以下采用方式2实现,注意:需放在setContentView()方法之前

1
2
3
4
5
6
7
8
9
Window window = activity.getWindow();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
window.getDecorView().setSystemUiVisibility(option);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
activity.getWindow().setStatusBarColor(色值);
}

黑白图标字符状态栏

解决黑白图标字符状态栏需考虑三种情况:魅族手机、小米手机、其他

  1. 魅族手机:Flyme系统API
  2. 小米手机:MIUI 9「状态栏黑色字符」实现方法变更通知
  3. 其他:
  4. 锤子手机:使用3的处理方式,还需手动调整设置(设置-全局高级设置-状态栏风格-自适应),否则还是存在灰色蒙层
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
public static void setStatusTextColor(boolean useDart, Activity activity) {
if (isFlyme()) {
processFlyme(activity, useDart);
} else if (isMIUI()) {
processMIUI(activity, useDart);
} else {
if (useDart) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
activity.getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
activity.getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
}
}
}
private static void processFlyme(Activity activity, boolean darkmode) {
Window window = activity.getWindow();
if(window != null) {
try {
WindowManager.LayoutParams lp = window.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((Object)null);
int value = meizuFlags.getInt(lp);
if(darkmode) {
value |= bit;
} else {
value &= ~bit;
}

meizuFlags.setInt(lp, value);
window.setAttributes(lp);
} catch (Exception var8) {
Log.w("StatusBarUtils", "setStatusBarDarkIcon: failed");
}
}
}
private static void processMIUI(Activity activity, boolean darkmode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // 即基于 Android 6.0 ,开发版 7.7.13 及以后版本
compatHighMIUI(activity, darkmode);
} else {
compatLowMIUI(activity, darkmode);
}
}
@TargetApi(Build.VERSION_CODES.M)
private static void compatHighMIUI(Activity activity, boolean darkmode) {
View decorView = activity.getWindow().getDecorView();
if (darkmode) {
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
} else {
int flag = decorView.getSystemUiVisibility() & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
decorView.setSystemUiVisibility(flag);
}
}
private static void compatLowMIUI(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);
} catch (Exception e) {
e.printStackTrace();
}
}

参考demo

参考链接:



# Android,沉浸式状态栏  

tocToc: