文章

沉浸式模式和透明化系统栏适配

沉浸式模式和透明化系统栏适配

沉浸式模式和透明化系统栏适配

适配基础

Translucent Bar(透明化系统栏) 和 Immersive Mode(沉浸式模式) 概念

沉浸式状态栏,Android 官方从来没有给出过沉浸式状态栏这样的命名,只有沉浸式模式(Immersive Mode)这种说法。一般分 2 种说法:透明系统栏和沉浸式模式。一般使用透明系统栏比较多,沉浸式模式基本只有游戏,全屏视频才用得上。
6gkc8

Translucent system bars 透明化系统栏(透明状态栏,内容布局到系统栏)

透明化系统栏,使得布局侵入系统栏的后面,必须启用 fitsSystemWindows 属性来调整布局才不至于被系统栏覆盖。

Immersive full-screen mode 沉浸式 (全屏) 模式、隐藏状态栏和导航栏

隐藏 状态栏、ActionBar、导航栏 等使屏幕全屏,让 Activity 接收所有的(整个屏幕的)触摸事件。沉浸式状态栏的叫法是错误的,应该是一种沉浸式的模式。它要求隐藏状态栏,使屏幕全屏,常应用在游戏,视频播放场景。

适配的变更

  1. Android3.0 及以上增加了 setSystemUiVisibility,后续版本增加了很多 SystemUI Flags
  2. Android4.4 及以上开始支持透明化状态栏和沉浸模式
  3. Android5.0 及以上支持状态栏/导航栏变色
  4. Android6.0 及以上支持状态栏字体颜色变更
  5. Android11 及以上,所有 flags 都过时了,用 WindowInsetsController 来适配

fitSystemWindows 适配

WindowInsets分发 章节的 fitSystemWindows

主题中各种颜色

vpriq
主题:

1
2
3
4
5
6
7
8
<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@android:color/holo_blue_light</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>
</resources>

可能取消 System UI flags 的操作

  1. 触摸屏幕任何位置
  2. 顶部下拉状态栏
  3. 底部上拉导航栏
  4. Window 的变化 (如:跳转到其他界面、弹出键盘等)

沉浸式模式和透明化系统栏适配方案

QMUIStatusBarHelper,简单的工具类,方便 (推荐)

https://github.com/Tencent/QMUI_Android/blob/master/qmui/src/main/java/com/qmuiteam/qmui/util/QMUIStatusBarHelper.java

ImmersionBar 库太大,不方便定制

UltimateBarX

https://github.com/Zackratos/UltimateBarX

适配场景

沉浸式模式——隐藏状态栏

长时间隐藏状态栏(比如:游戏 App)

1
2
3
4
5
6
7
8
9
10
private fun hideStatusBar(on: Boolean) {
    val winParams = window.attributes
    val bits = WindowManager.LayoutParams.FLAG_FULLSCREEN
    if (on) {//隐藏状态栏
        winParams.flags = winParams.flags or bits
    } else {//显示状态栏
        winParams.flags = winParams.flags and bits.inv()
    }
    window.attributes = winParams
}
  1. 用户任何交互操作都不会使系统自动清除状态栏隐藏状态
  2. 顶部下拉状态栏操作会是状态栏暂时显示,延迟几秒后自动消失。

短暂时间隐藏状态栏(比如:阅读 App)

1
window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_FULLSCREEN

会被以下场景打断:
2. 顶部下拉状态栏
4. Window 的变化 (如:跳转到其他界面、弹出键盘等)

沉浸式隐藏状态栏

1
2
3
4
5
private fun hideStatusBar(){
    var tag = (View.SYSTEM_UI_FLAG_FULLSCREEN
            or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
    layout.systemUiVisibility = tag
}
  1. 用户的 Window 的变化 (如:跳转到其他界面、弹出键盘等) 操作会使系统自动清除状态栏隐藏状态(系统会自动取消 View.SYSTEM_UI_FLAG_FULLSCREEN 的设置,但是不会取消 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
  2. 用户的 顶部下拉状态栏 操作会使状态栏暂时显示,延迟几秒后自动消失。
  3. 使用此 flag,系统会自动忽略输入法的 SOFT_INPUT_ADJUST_RESIZE 的特性。

沉浸式模式——隐藏导航栏

短暂时间隐藏导航栏

1
2
3
private fun hideNavigationBar() {
    window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
}

用户的 任何操作 都会使系统自动清除此状态。SYSTEM_UI_FLAG_HIDE_NAVIGATION 被系统自动清除时会连带清除 SYSTEM_UI_FLAG_FULLSCREEN

隐藏状态栏 Immersive

1
2
3
4
5
private fun hideNavigationBarImmersive() {
    val tag = (View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
            or View.SYSTEM_UI_FLAG_IMMERSIVE)
    window.decorView.systemUiVisibility = tag
}

下面操作会使系统自动清除此状态:
2. 顶部下拉状态栏
3. 底部上拉导航栏
4. Window 的变化 (如:跳转到其他界面、弹出键盘等)

触摸屏幕不会取消该 flag

隐藏状态栏 Immersive Sticky

1
2
3
4
5
private fun hideNavigationBarImmersiveSticky() {
    val tag = (View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
            or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
    window.decorView.systemUiVisibility = tag
}

Window的变化(如:跳转到其他界面、弹出键盘等) 操作会使系统自动清除此状态;用户的 顶部下拉状态栏底部上拉导航栏 操作会是使导航栏暂时显示,延迟几秒后自动消失

沉浸式模式——全屏模式

当你确定要使用沉浸式模式,那么只需要重写 Activity 的 onWindowFocusChanged() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        fullScreen1(); // 按需选择方法
        // fullScreen2();
        // fullScreen3()
    }

}

用于视频播放的情形

1
2
3
4
5
6
7
8
9
10
private fun fullScreen1() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
        val flags = (View.SYSTEM_UI_FLAG_FULLSCREEN
                or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                or View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
        window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or flags
    }
}
  1. 状态栏和导航栏都被隐藏
  2. 用户的任何交互都会导致全屏状态被系统清除
  3. 注意内容布局到状态栏和导航栏被遮挡住

用于沉浸阅读的情形

1
2
3
4
5
6
7
8
9
10
11
private fun fullScreen2() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
        val flags = (View.SYSTEM_UI_FLAG_FULLSCREEN
                or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                or View.SYSTEM_UI_FLAG_IMMERSIVE)
        window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or flags
    }
}
  1. 状态栏和导航栏都被隐藏
  2. 下拉通知栏和上拉导航栏都会退出,不会恢复
  3. 注意内容布局到状态栏和导航栏被遮挡住

用于游戏等严格沉浸的情形

1
2
3
4
5
6
7
8
9
10
private fun fullScreen1() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
        val flags = (View.SYSTEM_UI_FLAG_FULLSCREEN
                or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                or View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
        window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or flags
    }
}
  1. 状态栏和导航栏都被隐藏
  2. 下拉通知栏和上拉导航栏临时退出全屏,过个几秒又会恢复全屏
  3. 注意内容布局到状态栏和导航栏被遮挡住

透明化系统栏——(半) 透明状态栏/导航栏

Android4.4-Android5.0 实现状态栏半透明和导航栏半透明

  1. Android4.4-Android5.0 只能半透明状态栏和导航栏,不支持设置颜色(注意:不是全透明)
  2. 用这个 flag:WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUSFLAG_TRANSLUCENT_NAVIGATION 代码设置,或在 values-v19 设置主题
  • WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUSWindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION 介绍:
1
2
3
4
5
0. 状态栏用`LayoutParams.FLAG_TRANSLUCENT_STATUS`,导航栏用`WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION`
1. 状态栏半透明
2. 也可以在主题中配置windowTranslucentStatus属性,效果一样;其中带有xxx_TranslucentDecor主题的默认设置了该flag
3. 设置了该flag,自动添加了View.SYSTEM_UI_FLAG_LAYOUT_STABLE和View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
4. 已经过时,大于等于Android21用Window.setStatusBarColor(Android5.0)设置一个半透明替代;Android4.4~Android5.0用这个flag
实现
  • 主题方式实现:
1
2
3
4
<style name="ThemeTransparent" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:windowTranslucentStatus">true</item>
    <item name="android:windowTranslucentNavigation">true</item>
</style>
  • 代码方式实现:
1
2
3
// 过时 Use Window.setStatusBarColor(int) with a half-translucent color instead.
this.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
this.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)

在不同版本设置 WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS 后的效果:

版本效果
Android4.4ol9vq
Android5.1qru86
Android6.0p4e9m
Android10.0zrzcb

效果:

e4uzm

解决状态栏和标题栏重叠

存在的问题:

  1. 在 Android4.4 版本状态栏的顶部有一个渐变,会显示出黑色的阴影(底部的导航栏也是一样的效果);Android4.4 后的版本状态栏就是个半透明的
  2. 状态栏透明了,标题栏和状态栏重叠了
  • 解决标题栏和状态栏重叠方式 1:添加属性 android:fitsSystemWindows="true"
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"
    tools:context=".samples.ui.屏幕适配相关.沉浸式_透明状态栏.沉浸式场景.沉浸到状态栏.主题方案.沉浸到状态栏_主题方案">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@color/red_A400"
        android:fitsSystemWindows="true"
        android:gravity="center"
        android:text="标题栏"
        android:textColor="@color/black"
        android:textSize="20sp" />
</LinearLayout>

效果:
xtzf1

在 TextView 增加 paddingTop=状态栏高度:存在标题内容被挤压了问题;fitsSystemWindows 不能设置在 LinearLayout 上,否则状态栏颜色就不会和 TextView 颜色一致,需要设置在 TextView 上

  • 解决标题栏和状态栏重叠方式 2:添加一个 View,高度为状态栏高度,背景为 TextView 的颜色
小结
  1. Android4.4 上实现沉浸式状态栏的套路是:为 window 添加 WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS Flag,然后添加一个和 status bar 一样大小的 View 占位,从而让让标题栏不会与 status bar 重叠。而图片延伸到状态栏只需要设置 WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS 就 OK
  2. WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS 沉浸式在 Android4.4-Android5.0 之间的版本表现得不是很好,状态栏的顶部有一个渐变,会显示出黑色的阴影(底部的导航栏也是一样的效果)

Android5.0 及以上实现状态栏透明和导航栏透明

  1. 内容不会沉浸到状态栏和导航栏(没啥实际意义)
  2. 全透明
1
2
3
4
5
6
7
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
    // 注意要清除 FLAG_TRANSLUCENT_STATUS flag
    window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
    window.statusBarColor = resources.getColor(android.R.color.transparent)
    window.navigationBarColor = resources.getColor(android.R.color.transparent)
}

效果:

iz6hy

[半]透明状态栏/导航栏 小结

1. 半透明状态栏 (API>=19)
1
2
3
4
5
6
7
private fun translateStatusBar(on: Boolean) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        var p = window.attributes
        p.flags = p.flags or WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
        window.attributes = p
    }
}

注意:为避免状态栏遮挡 View 上的部分信息,需要为 View 设置 fitSystemWindow=true,或手动设置 padding

2. 全透明状态栏并沉浸内容到状态栏,导航栏不沉浸 (API>=21)
1
2
3
4
5
6
7
8
9
10
11
12
private fun translateStatusBarAndNavigationBar() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        // 注意要清除 FLAG_TRANSLUCENT_STATUS flag
        window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
        window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
        window.statusBarColor = Color.TRANSPARENT
        val tag = (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
        window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or tag
    } else {
        toast("低于Android21,不做任务事情")
    }
}

效果:
ndwxi
注意:

  • Android 的状态栏的字体颜色默认为白色,只有 Android6.0 以后才提供官方的 API 选择黑色字体(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR)。所以当 6.0 以下使用透明状态栏时,若 StatusBar 下面的 View 的也为白色背景时,则会造成看不到 StatusBar 的相关信息。
  • 为避免状态栏遮挡 View 上的部分信息,需要为 View设置fitSystemWindow=true,或手动设置 padding
3. 全透明状态栏和导航栏,内容布局到状态栏和导航栏 (API>=21)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private fun translateStatusBarAndNavigationBar() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        // 注意要清除 FLAG_TRANSLUCENT_STATUS flag
        window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
        window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
        window.statusBarColor = Color.TRANSPARENT
        window.navigationBarColor = Color.TRANSPARENT
        val tag =
            (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
        window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or tag
    } else {
        toast("低于Android21,不做任务事情")
    }
}

效果:

xjae5
注意:

  1. 状态栏和导航栏会遮挡内容 view,在根布局设置 fitsSystemWindows="true"

fitsSystemWindows=”true” 效果:
sgyh2

Android 的状态栏的字体颜色默认为白色,又是透明状态栏和导航栏,导致看不清了

4、全版本适配透明状态栏和导航栏
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
public static void transparentStatusAndNavigation(Window window) {
    // make full transparent statusBar
    if (Build.VERSION.SDK_INT >= 19 && Build.VERSION.SDK_INT < 21) {
        setWindowFlag(window, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
                | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, true);
    }
    if (Build.VERSION.SDK_INT >= 19) {
        int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
        visibility = visibility | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
        window.getDecorView().setSystemUiVisibility(visibility);
    }
    if (Build.VERSION.SDK_INT >= 21) {
        int windowManager = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
        windowManager = windowManager | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
        setWindowFlag(window, windowManager, false);
        window.setStatusBarColor(Color.TRANSPARENT);
        window.setNavigationBarColor(Color.TRANSPARENT);
    }
}
private static void setWindowFlag(final int bits, boolean on) {
    Window win = getWindow();
    WindowManager.LayoutParams winParams = win.getAttributes();
    if (on) {
        winParams.flags |= bits;
    } else {
        winParams.flags &= ~bits;
    }
    win.setAttributes(winParams);
}

https://stackoverflow.com/a/52264516

状态栏/导航栏变色,非沉浸到状态/导航栏

状态栏变色,只支持 Android5.0 及以上,Android4.4 不支持。

  • flag WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS 介绍
    设置了 FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,表明 Window 会负责系统 bar background 的绘制,如果设置了系统 bar(状态栏和导航栏)会被绘制成透明背景,然后用 getStatusBarColor()getNavigationBarColor() 的颜色填充相应的区域
  • Window.statusBarColor/setNavigationBarColor(color) 介绍
  1. 设置状态栏的背景色,Android5.0 及以上(API21)可用
  2. window 必须 drawing the system bar backgrounds,通过设置 WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS 这个 flag,且 WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS 这个 flag 必须没有设置
  3. 如果 color 带有透明,考虑设置 View.SYSTEM_UI_FLAG_LAYOUT_STABLEView.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN

实现

  • 代码方式:
    xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@color/red_A400"
        android:gravity="center"
        android:text="标题栏"
        android:textColor="@color/black"
        android:textSize="20sp" />
</LinearLayout>

代码:

1
2
3
4
5
6
7
8
9
10
11
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
    // 注意要清除 FLAG_TRANSLUCENT_STATUS flag
    window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
    window.statusBarColor = resources.getColor(R.color.red_A400)
    window.navigationBarColor = resources.getColor(R.color.red_A400)

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        window.navigationBarDividerColor = resources.getColor(R.color.black) // 设置导航栏和App的一条线
    }
}

效果:

o86s2

  • 主题方式实现
1
2
3
4
5
<style name="MDTheme" parent="Theme.Design.Light.NoActionBar">
    <item name="android:windowTranslucentStatus">false</item>
    <item name="android:windowDrawsSystemBarBackgrounds">true</item>
    <item name="android:statusBarColor">@color/red_A400</item>
</style>

图片沉浸到状态栏

Android4.4-Android5.0

布局:

1
2
3
4
5
6
7
8
9
10
11
12
<?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">
    <ImageView
        android:id="@+id/imageview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/bg_girl" />
</LinearLayout>

代码:

1
2
3
4
5
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    // 过时 Use Window.setStatusBarColor(int) with a half-translucent color instead.
    this.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
    this.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
}

效果:

fnzr2

Android5.0 及以上效果就不对了。

实现状态栏字色和图标浅黑色 (Android 6.0+)

默认 Android 系统状态栏的字色和图标颜色为白色

View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR 介绍

  1. 设置状态栏 light 模式(设置为浅黑色字体)
  2. 需配置 WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS flag,不要配置 WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
  3. 过时,用 WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS 替代

代码方式实现

1
2
3
4
5
6
7
8
9
10
11
12
// 设置light模式
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
    window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
    window.decorView.systemUiVisibility = /*View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or*/ View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
}

// 清除light模式
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    val flag = window.decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
    window.decorView.systemUiVisibility = flag
}

主题方式实现

values-v23 文件夹下添加该主题:

1
2
3
4
5
6
7
<style name="MDTheme" parent="Theme.Design.Light.NoActionBar">
    <item name="android:windowTranslucentStatus">false</item>
    <item name="android:windowDrawsSystemBarBackgrounds">true</item>
    <item name="android:statusBarColor">@android:color/holo_red_light</item>
    <!-- Android 6.0以上 状态栏字色和图标为浅黑色-->
    <item name="android:windowLightStatusBar">true</item>
</style>
  • 默认效果:
    wyus0
  • 设置了 light 效果:
    1utsm

WindowInsetsController

WindowInsetsController.md

沉浸模式&透明系统栏坑

Android29 上透明导航栏颜色不对 Disabling system bar protection on Android 10

1
2
3
4
5
6
7
8
// 设置状态栏和导航栏透明的底色
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    window.statusBarColor = Color.TRANSPARENT
    window.navigationBarColor = Color.TRANSPARENT
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    window.navigationBarDividerColor = Color.RED
}

效果:

解决,在 v29 的 theme.xml 中添加:

1
2
3
4
<style name="Theme.Kingassist" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:enforceNavigationBarContrast">false</item>
    <item name="android:enforceStatusBarContrast">false</item>
</style>

效果:

无法全透明导航栏(NavigationBar)

1
2
3
4
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
window.decorView.systemUiVisibility=(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
window.navigationBarColor = Color.TRANSPARENT

en 发现导航栏半透明背景依然无法去掉:

8sthd

问题:当设置导航栏为透明色(Color.TRANSPARENT)时,导航栏会变成半透明,当设置其他颜色,则是正常的,例如设置颜色为 0x700F7FFF,显示效果如下:

dfpfh

分析:

1
2
3
4
5
6
7
8
9
10
11
// Activity.onApplyThemeResource
// Get the primary color and update the TaskDescription for this activity
TypedArray a = theme.obtainStyledAttributes(
        com.android.internal.R.styleable.ActivityTaskDescription);
if (mTaskDescription.getPrimaryColor() == 0) {
    int colorPrimary = a.getColor(
            com.android.internal.R.styleable.ActivityTaskDescription_colorPrimary, 0);
    if (colorPrimary != 0 && Color.alpha(colorPrimary) == 0xFF) {
        mTaskDescription.setPrimaryColor(colorPrimary);
    }
}

也就是说如果设置的导航栏颜色为 0(纯透明)时,将会为其修改为内置的颜色:ActivityTaskDescription_colorPrimary,因此就会出现灰色蒙层效果。

解决:将纯透明这种情况修改颜色为 0x01000000,这样也能达到接近纯透明的效果:

亮色系统 NavigationBar 版本差异

1
2
3
4
// 大于等于 Android 6.0 版本的系统,如果背景是浅色的,可通过设置状态栏和导航栏文字颜色为深色,也就是导航栏和状态栏为浅色(Android 8.0 及以上才支持导航栏文字颜色修改)
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR

window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR else 0

问题:在亮色系统 bar 基础上开启沉浸式后,在 8.0 至 9.0 系统中,导航栏深色导航 icon 不生效,而 10.0 以上版本能显示深色导航 icon:

mqv9l

通过查看源码发现,与设置状态栏和导航栏背景颜色类似,设置导航栏 icon 颜色也是不能为沉浸式:

lphm8

与软键盘冲突的坑,输入框顶不起来(SystemUI Flag 引起的软键盘变化)

如果在界面中有 EditText 的话,你会发现当软件盘弹出的时候(Activity 已经设置了 adjustResize),ToolBar 的内容都被顶上去了,但是 EditText 输入框却被有顶上来(正常情况应该是 ToolBar 没事,输入框被软键盘顶上去)

在使用 SOFT_INPUT_ADJUST_RESIZE,同时设置了 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREENView.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 时,当键盘弹出时,只会 fitSystemWindow=true 的 view 所占区域会被 resize,其他 view 将会被软键盘覆盖。

华为 EMUI3.1 上的坑

没有沉浸式效果,状态栏是透明的,显示的是桌面上的颜色

Toast 错位(android:fitsSystemWindows)

不要在 Theme 中设置 android:fitsSystemWindows
Toast 打印出来的文字都往上偏移了,依附于 Application Window 的 Window(比如 Toast)错位
Toast 错位的解决方法如下:使用应用级别的上下文

1
Toast.makeText(getApplicationContext(),"toast sth...",Toast.LENGTH_SHORT).show();

Ref

本文由作者按照 CC BY 4.0 进行授权