沉浸式是应用界面开发的不可避免要考虑的内容,在早期的 Android 应用中,屏幕的顶部是状态栏,底部是导航栏,而 APP 内容则被夹在中间,这种「三明治」式的布局结构长期主导 Android 设备的屏幕设计。
不过这种"非沉浸式"设计,在 2024 年以后的 Android 生态里,正在逐渐被淘汰。Google 从 Android 15 开始,强制所有 App 启用 Edge-to-Edge(边缘到边缘)。也就是说,你的内容必须延伸到状态栏和导航栏的后面,否则在新系统上会出现诡异的空白或黑边。

在 Android 14(API 级别 34)及更低版本中,应用的界面默认不会绘制在系统栏和刘海屏下方。
在 Android 15(API 级别 35)及更高版本中,如果您的应用以 SDK 35 为目标平台,则会在系统栏和刘海屏下方进行绘制。这样可带来更流畅的用户体验,并让您的应用充分利用可用的窗口空间。
沉浸式之前:Android 的三明治时代
在 Android 早期,系统栏(状态栏 + 导航栏)是 不透明且不可逾越 的。应用的 Activity 内容区域,默认就是从状态栏下方开始,到导航栏上方结束。开发者通常无需主动处理状态栏和导航栏的避让,不需要写任何代码,内容自然就在"安全区域"里。不过在 View 体系中,系统也提供了 fitSystemWindows 机制,用于让 View 自动适配系统窗口区域。
此时还可以通过主题色来控制系统栏颜色:
xml
<!-- res/values/styles.xml -->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- 状态栏颜色 -->
<item name="android:statusBarColor">@color/red</item>
<!-- 导航栏颜色 -->
<item name="android:navigationBarColor">@color/blue</item>
<!-- 状态栏文字颜色(浅色/深色) -->
<item name="android:windowLightStatusBar">true</item>
</style>
这就是"沉浸式之前"的完整方案:系统栏不透明 + 自定义颜色 + 内容自动避让。
沉浸式的引入:Edge-to-Edge
Edge-to-Edge(边缘到边缘) 指的是 App 内容从屏幕的最左边缘延伸到最右边缘,从最上边缘延伸到最下边缘。系统栏(状态栏、导航栏)变成半透明或全透明,浮在你的内容上方。
而其最关键的 API 就是 enableEdgeToEdge(),它是 androidx.activity:activity 1.8.0 引入的扩展函数。这个方法做了以下三件事:
- 将状态栏和导航栏设置为透明
- 允许你的内容绘制到系统栏区域后面
- 自动处理亮色/暗色主题下系统栏图标的颜色(浅色/深色)
也就是说,在调用 enableEdgeToEdge() 之后,应用内容就从原来的三明治结构,延伸到系统栏的后面,这种沉浸式带来的视觉体验的提升是显著的。不过对于开发者来说,就必须手动处理边缘的避开逻辑,以免内容被系统栏遮挡。
例如,使用如下的代码开启沉浸式,你就可以在状态栏之下显示应用内容:
kotlin
// 在 Activity.onCreate() 中调用
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge() // ← 启用沉浸式
setContentView(R.layout.activity_main)
}

虽然说需要调用 enableEdgeToEdge() 方法才能进入沉浸式,但从 Android 15(targetSDK >=35)开始,即使不调用此方法,也会自动启用 Edge-to-Edge。
另外这里再提一句,Edge-to-Edge 的本质 API 是 WindowCompat.setDecorFitsSystemWindows(window, false) 而 androidx.activity 中的 enableEdgeToEdge() 只是 Jetpack 对这一行为的进一步封装,它会自动处理系统栏透明、图标颜色等问题。在开发中我们用后者就行。
沉浸式的适配:WindowInsets
对于沉浸式的适配,实际上就是对边缘区域的留白,以避免应用的内容被压在状态栏之下。这里就不得不提到 WindowInsets 这个类了。这个类就是告诉你,屏幕的上下左右的边缘有多少像素被系统占用了,你的内容需要避开多少。
例如:
kotlin
val rootView = findViewById<View>(android.R.id.content)
val windowInsets = ViewCompat.getRootWindowInsets(rootView)
if (windowInsets != null) {
val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
// 示例返回:Insets { left=0, top=74, right=0, bottom=126 }
}
这些值不是坐标,不是系统栏高度,而是你的内容需要往内缩多少像素才能不被挡住:
top = 74→ 内容从第 74 像素开始,才不会被状态栏遮挡bottom = 126→ 内容在第 126 像素之前结束,才不会被导航栏遮挡
上面只用到了 systemBars 这个类型,但是在 WindowInsetsCompat.Type 中系统还提供了很多系统 UI 类型供开发者查询:
| Type | 含义 | 典型返回值 |
|---|---|---|
statusBars() |
状态栏 | top=74, 其余=0 |
navigationBars() |
导航栏 | bottom=126, 其余=0 |
systemBars() |
上面两个的并集 | top=74, bottom=126 |
ime() |
软键盘 | 收起时 bottom=0,弹出时 bottom=键盘高度 |
displayCutout() |
刘海屏/挖孔屏 | 刘海区域 top=额外高度 |
systemGestures() |
系统手势区域 | 左右边缘及底部有预留区域,用于避免系统手势冲突 |
这里要注意,上述表格中的返回值因设备而异,不一定相同。
获取和设置 Inset
在传统的 View 体系中,可以通过如下方式来获取并设置 Inset:
kotlin
// Activity 中
enableEdgeToEdge()
// 在根 View 上监听 insets
ViewCompat.setOnApplyWindowInsetsListener(rootView) { view, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
// 手动设置 padding,让内容避开系统栏
view.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets // 返回 insets,让后续 View 也能收到
}
而在 Compose 中,系统提供了开箱即用的 Modifier:
kotlin
// 同时避开状态栏和导航栏
Modifier.systemBarsPadding()
// 只避开状态栏
Modifier.statusBarsPadding()
// 只避开导航栏
Modifier.navigationBarsPadding()
// 避开软键盘
Modifier.imePadding()
// 组合多种 insets
Modifier.windowInsetsPadding(
WindowInsets.systemBars.union(WindowInsets.ime)
)
