今天在这儿系统梳理一下 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_FULLSCREEN 或
LAYOUT_HIDE_NAVIGATION 结合使用,当系统栏显示/隐藏时,保持布局稳定,避免内容跳动。
SYSTEM_UI_FLAG_IMMERSIVE:与
HIDE_NAVIGATION 和
FULLSCREEN 结合,当用户从屏幕边缘滑动时才显示系统栏,提供更沉浸的体验。
SYSTEM_UI_FLAG_IMMERSIVE_STICKY:
IMMERSIVE 的变体,系统栏在短暂显示后会自动再次隐藏,更适合游戏或视频播放。
状态栏着色:非常有限,通常是黑色或白色。
Android 4.4 (API 19) – Translucent System Bars (半透明系统栏)
允许将状态栏和导航栏设置为半透明。
控制方式:
在主题中设置
android:windowTranslucentStatus 和
android:windowTranslucentNavigation 为
true。
或者通过代码
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_FULLSCREEN 或
LAYOUT_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+)
启用 Edge-to-Edge:在 Activity 的
onCreate 方法中,
super.onCreate() 之后,
setContentView() 之前调用:
WindowCompat.setDecorFitsSystemWindows(window, false)
处理 Insets:使用
ViewCompat.setOnApplyWindowInsetsListener 来获取系统栏、挖孔、手势区域等的大小,并相应地调整你布局中视图的
padding 或
margin。
C++
123456789101112131415161718
[crayon-6a1bbb5052148939416733 inline="true" class="language-kotlin" lang="kotlin"]ViewCompat.setOnApplyWindowInsetsListener(yourRootViewOrSpecificView) { view, windowInsets -> val systemBarsInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) val displayCutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) val imeInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime()) // 软键盘 view.updatePadding( left = systemBarsInsets.left + displayCutoutInsets.left, top = systemBarsInsets.top + displayCutoutInsets.top, right = systemBarsInsets.right + displayCutoutInsets.right, bottom = systemBarsInsets.bottom + displayCutoutInsets.bottom // 或 imeInsets.bottom 当键盘弹出时 ) // 或者只处理特定方向,例如只为顶部 AppBar 添加状态栏高度的 padding // appBar.updatePadding(top = systemBarsInsets.top) // bottomNav.updatePadding(bottom = systemBarsInsets.bottom) WindowInsetsCompat.CONSUMED // 或者返回修改后的 insets}
[/crayon]
控制系统栏可见性和外观(
WindowInsetsController):
C++
1234567891011121314
[crayon-6a1bbb505214c959129533 inline="true" class="language-kotlin" lang="kotlin"]val insetsController = WindowCompat.getInsetsController(window, window.decorView) // 隐藏状态栏insetsController?.hide(WindowInsetsCompat.Type.statusBars())// 显示状态栏insetsController?.show(WindowInsetsCompat.Type.statusBars()) // 设置状态栏图标为深色 (当状态栏背景为浅色时)insetsController?.isAppearanceLightStatusBars = true// 设置导航栏按钮为深色 (当导航栏背景为浅色时)insetsController?.isAppearanceLightNavigationBars = true // 控制系统栏在隐藏后的行为 (例如滑动边缘显示)insetsController?.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
[/crayon]
设置系统栏颜色:即使内容延伸到系统栏后面,你仍然可以为它们设置半透明或透明的背景色,以获得更好的视觉效果。 在主题
themes.xml(推荐 values-v21 及以上):
C++
12
[crayon-6a1bbb5052151962273264 inline="true" class="language-xml" lang="xml"]
[/crayon]
或者代码中(需要在
FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS 被设置后):
C++
12
[crayon-6a1bbb5052154337793410 inline="true" class="language-kotlin" lang="kotlin"]window.statusBarColor = Color.TRANSPARENTwindow.navigationBarColor = Color.TRANSPARENT
[/crayon]
如果你希望系统栏有一个半透明的遮罩效果(例如在深色模式下),可以使用类似
#80000000(50% 透明度的黑色)的颜色。
总结和最佳实践
拥抱 Edge-to-Edge:这是现代 Android 应用的趋势。
使用
WindowCompat.setDecorFitsSystemWindows(window, false) 启用它。
核心是处理 Insets:使用
ViewCompat.setOnApplyWindowInsetsListener 和
WindowInsetsCompat.Type.*。
使用
WindowInsetsController (API 30+) 控制系统栏的可见性、行为和图标颜色。
避免使用废弃的
View.setSystemUiVisibility()。
为系统栏设置透明或半透明背景色,以配合 Edge-to-Edge 设计。
在不同设备和 Android 版本上充分测试,特别注意有挖孔和使用手势导航的设备。
利用 Material Components:许多 Material Design 组件(如
AppBarLayout、
BottomAppBar、
CoordinatorLayout)已经内置了对 Insets 的良好处理。
这个演进过程确实比较复杂,但理解了这些核心概念和 API 的变化,就能更好地掌控应用的视觉表现,并提供更沉浸的用户体验。
打赏赞(1)微海报分享