Android 中状态栏和导航栏等系统区域的显示控制

今天在这儿系统梳理一下 Android 系统中状态栏(Status Bar)和导航栏(Navigation Bar)的显示控制,以及它们随着操作系统版本演进的变化。

核心概念

  • System Bars(系统栏):统称状态栏和导航栏。
  • Edge-to-Edge(边到边):指应用的内容可以绘制到屏幕的边缘,延伸到系统栏的后面。这是现代 Android UI 的推荐做法。
  • Insets(边衬区):系统栏、挖孔、手势区域等占用的空间。应用需要响应 Insets 来避免内容被遮挡。
  • Window Flags:用于控制窗口的各种属性,包括系统栏的可见性和行为。
  • System UI Visibility Flags(View 层级):在旧版本中用于控制系统栏的可见性和布局的标志。
  • WindowInsetsController:较新的 API,用于更细致地控制系统栏的外观和行为。
  • WindowCompat.setDecorFitsSystemWindows(window, false):启用 Edge-to-Edge 的关键方法。

演进历程和控制方法

Android 4.0 (API 14) 及之前 – 基本控制

  • 状态栏:默认可见且不透明。
  • 导航栏:物理按键为主,部分设备开始出现虚拟导航栏。
  • 控制方式:
    • 通过 AndroidManifest.xml 中的 android:theme 设置主题,例如 @android:style/Theme.NoTitleBar.Fullscreen 来隐藏状态栏和标题栏。
    • 通过 Window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) 动态隐藏状态栏。
    • 通过 Window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) 动态显示状态栏。
    • 导航栏的控制非常有限。

Android 4.1 (API 16) – “System UI Visibility” 和精细化控制

  • 引入了 View.setSystemUiVisibility() 和一系列 SYSTEM_UI_FLAG_* 常量,允许更灵活地控制系统栏。
  • 关键 Flags
    • SYSTEM_UI_FLAG_FULLSCREEN:隐藏状态栏(类似 FLAG_FULLSCREEN)。
    • SYSTEM_UI_FLAG_HIDE_NAVIGATION:隐藏导航栏。用户与屏幕交互后,导航栏会重新出现。
    • SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN:即使状态栏可见,应用内容也会布局到状态栏下方。开发者需要处理状态栏遮挡。
    • SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION:即使导航栏可见,应用内容也会布局到导航栏下方。开发者需要处理导航栏遮挡。
    • SYSTEM_UI_FLAG_LAYOUT_STABLE:与 LAYOUT_FULLSCREENLAYOUT_HIDE_NAVIGATION 结合使用,当系统栏显示/隐藏时,保持布局稳定,避免内容跳动。
    • SYSTEM_UI_FLAG_IMMERSIVE:与 HIDE_NAVIGATIONFULLSCREEN 结合,当用户从屏幕边缘滑动时才显示系统栏,提供更沉浸的体验。
    • SYSTEM_UI_FLAG_IMMERSIVE_STICKYIMMERSIVE 的变体,系统栏在短暂显示后会自动再次隐藏,更适合游戏或视频播放。
  • 状态栏着色:非常有限,通常是黑色或白色。

Android 4.4 (API 19) – Translucent System Bars (半透明系统栏)

  • 允许将状态栏和导航栏设置为半透明。
  • 控制方式:
    • 在主题中设置 android:windowTranslucentStatusandroid:windowTranslucentNavigationtrue
    • 或者通过代码 Window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)Window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
  • 注意:应用内容会自动延伸到半透明系统栏的下方,需要配合 fitsSystemWindows="true" 或手动处理 Insets 来避免内容遮挡。
  • 问题: fitsSystemWindows 属性的行为有时比较复杂和难以预测,尤其是在嵌套布局中。

Android 5.0 (API 21) – Material Design 和着色控制

  • 状态栏着色:引入了 Window.setStatusBarColor(int color)Window.setNavigationBarColor(int color),允许开发者设置系统栏为任意不透明颜色。
    • 需要在主题中或代码中先添加 Window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) 并清除 FLAG_TRANSLUCENT_STATUS / FLAG_TRANSLUCENT_NAVIGATION
  • fitsSystemWindows 的改进:行为更加一致,但仍是处理 Insets 的主要(且有时棘手)方式。
  • Light Status Bar(浅色状态栏图标):
    • SYSTEM_UI_FLAG_LIGHT_STATUS_BAR (API 23+):当状态栏背景为浅色时,可以将状态栏图标(时间、电量、通知图标)变为深色,以保证可见性。

Android 6.0 (API 23) – Light Status Bar (正式引入)

  • 如上所述,正式引入 SYSTEM_UI_FLAG_LIGHT_STATUS_BAR

Android 8.0 (API 26) – Light Navigation Bar(浅色导航栏图标)

  • 引入 SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR:当导航栏背景为浅色时,可以将导航栏按钮(返回、主页、概览)变为深色。
    • 需要设备制造商支持,并且导航栏是半透明或透明的。

Android 9 (API 28) – Display Cutouts(屏幕挖孔/刘海屏)

  • 随着刘海屏和挖孔屏的出现,需要处理这些区域。
  • windowLayoutInDisplayCutoutMode:在主题或窗口属性中设置,控制内容如何与挖孔区域交互:
    • default:默认行为,内容在竖屏时不会延伸到挖孔区域。
    • shortEdges:允许内容在竖屏和横屏时都延伸到屏幕短边上的挖孔区域。
    • never:内容永远不会延伸到挖孔区域。
  • WindowInsets.getDisplayCutout():获取挖孔区域的详细信息和安全边距。

Android 10 (API 29) – Gesture Navigation(手势导航)和 Edge-to-Edge 强制

  • 手势导航成为主流:系统导航栏变为一个细条或者完全隐藏,通过手势操作。这对应用UI布局提出了新的要求,因为底部和侧边的手势区域可能与 UI 元素冲突。
  • Edge-to-Edge 变得更加重要:Google 开始强烈推荐应用采用 Edge-to-Edge 设计。
  • 强制 Edge-to-Edge 的开端:如果 targetSdkVersion >= 29,系统会更倾向于让应用内容延伸到系统栏后面,即使没有显式设置 LAYOUT_FULLSCREENLAYOUT_HIDE_NAVIGATION
  • Mandatory Gesture Areas:引入了系统手势区域的概念,应用应避免在这些区域放置关键交互元素。使用 WindowInsetsCompat.Type.systemGestures()WindowInsetsCompat.Type.mandatorySystemGestures() 获取这些区域的 Insets。

Android 11 (API 30) – WindowInsetsController 和更一致的 API

  • 废弃 View.setSystemUiVisibility():这个 API 因为其复杂性和难以理解的行为而被废弃。
  • 引入 WindowInsetsController:提供了一套更现代化、更直观的 API 来控制系统栏的可见性、行为和外观。
  • WindowCompat.getInsetsController(window, view) 获取控制器。
  • hide(WindowInsets.Type.statusBars()) / hide(WindowInsets.Type.navigationBars())
  • show(WindowInsets.Type.statusBars()) / show(WindowInsets.Type.navigationBars())
  • setSystemBarsBehavior(BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE):类似于 IMMERSIVE_STICKY
  • BEHAVIOR_SHOW_BARS_BY_TOUCH:用户触摸即显示。
  • BEHAVIOR_SHOW_BARS_BY_SWIPE:用户滑动边缘显示。
  • setSystemBarsAppearance(APPEARANCE_LIGHT_STATUS_BARS, APPEARANCE_LIGHT_STATUS_BARS):控制状态栏图标颜色。
  • setSystemBarsAppearance(APPEARANCE_LIGHT_NAVIGATION_BARS, APPEARANCE_LIGHT_NAVIGATION_BARS):控制导航栏图标颜色。
  • WindowCompat.setDecorFitsSystemWindows(window, false):成为启用 Edge-to-Edge 的标准方法。当设置为 false 时,应用内容会延伸到系统栏后面。开发者必须使用 setOnApplyWindowInsetsListener 来处理 Insets。

Android 12 (API 31) 及之后 – 默认启用 Edge-to-Edge(针对特定情况)

  • 系统进一步推动 Edge-to-Edge。
  • 对于使用手势导航且 targetSdkVersion >= 31 的应用,系统可能会默认强制应用以 Edge-to-Edge 方式运行。

Android 15 (API 35) – 默认启用 Edge-to-Edge(全面)

  • targetSdk 升级到 35 后,应用会默认启用 edge-to-edge 显示。这意味着 WindowCompat.setDecorFitsSystemWindows(window, false) 的行为成为默认,开发者必须处理 Insets。

现代(推荐)的控制方法 (API 30+)

  1. 启用 Edge-to-Edge:在 Activity 的 onCreate 方法中, super.onCreate() 之后, setContentView() 之前调用: WindowCompat.setDecorFitsSystemWindows(window, false)
  1. 处理 Insets:使用 ViewCompat.setOnApplyWindowInsetsListener 来获取系统栏、挖孔、手势区域等的大小,并相应地调整你布局中视图的 paddingmargin

[/crayon]

  1. 控制系统栏可见性和外观( WindowInsetsController):

[/crayon]

  1. 设置系统栏颜色:即使内容延伸到系统栏后面,你仍然可以为它们设置半透明或透明的背景色,以获得更好的视觉效果。 在主题 themes.xml(推荐 values-v21 及以上):

[/crayon]
或者代码中(需要在 FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS 被设置后):

[/crayon]
如果你希望系统栏有一个半透明的遮罩效果(例如在深色模式下),可以使用类似 #80000000(50% 透明度的黑色)的颜色。

总结和最佳实践

  • 拥抱 Edge-to-Edge:这是现代 Android 应用的趋势。
  • 使用 WindowCompat.setDecorFitsSystemWindows(window, false) 启用它。
  • 核心是处理 Insets:使用 ViewCompat.setOnApplyWindowInsetsListenerWindowInsetsCompat.Type.*
  • 使用 WindowInsetsController (API 30+) 控制系统栏的可见性、行为和图标颜色。
  • 避免使用废弃的 View.setSystemUiVisibility()
  • 为系统栏设置透明或半透明背景色,以配合 Edge-to-Edge 设计。
  • 在不同设备和 Android 版本上充分测试,特别注意有挖孔和使用手势导航的设备。
  • 利用 Material Components:许多 Material Design 组件(如 AppBarLayoutBottomAppBarCoordinatorLayout)已经内置了对 Insets 的良好处理。

这个演进过程确实比较复杂,但理解了这些核心概念和 API 的变化,就能更好地掌控应用的视觉表现,并提供更沉浸的用户体验。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注