最近在做一个新版本应用的适配时,被一个看起来很简单的小需求绊住了:让页面内容真正全屏铺满,同时让状态栏和导航栏保持透明。一开始我想着这不就是老一套 Window.setStatusBarColor(Color.TRANSPARENT) 就能解决的吗?结果跑到 Android 16 的机型上一调试,发现传统写法根本不起作用,状态栏和导航栏要么依然是白底黑字,要么就是内容区域没有正确延伸,底部生生留出一条黑边。查了一圈源码和官方文档才发现,从 Android 15 开始系统就在推进 edge-to-edge(边到边)的强制适配,到了 Android 16(API 36)直接用 targetSdk 36 编译时,系统栏透明和内容全屏已经变成了默认规则。这篇文章就把我这次的完整适配过程、核心原理和踩过的坑都整理出来,希望对正在做 Android 16 适配的朋友有帮助。
1. 全屏与透明系统栏,到底要实现什么效果
先理清一下需求。很多产品经理口中的“内容全屏,状态栏和导航栏透明”,其实包含着完全不同的两种状态,也是最容易让开发产生误解的地方:
- 边到边(Edge-to-Edge)但系统栏可见:内容区域扩展到状态栏和导航栏后面,系统栏本身保留但背景是透明的,用户能看到内容从屏幕顶部延伸到最底下,但状态图标和导航条仍然可见,不影响操作。这是现代 Android 应用推荐的默认形态。
- 沉浸式全屏(Immersive Mode)隐藏系统栏:状态栏和导航栏临时隐藏或变为半透明悬浮状态,比如看视频、打游戏时想要完全无干扰的体验。这种情况下系统栏其实是“不显示”的,和“透明可见”是两码事。
我在文中主要讨论的是第一种:系统栏透明但可见,页面内容全屏延伸到系统栏区域,这也是 Android 16 强制要求应用开发者适配的模式。第二种需要额外调用 API 隐藏系统栏,后面我会单独讲。
还有个很容易混淆的点:透明不代表不可见。透明是指系统栏背景没有颜色,状态栏里的时间、信号、电池图标,以及底部的手势条仍然会覆盖在你的应用内容之上,所以必须给这些区域腾出空间,否则 UI 就会被遮挡。
1.1 需求拆解:谁需要这种全屏效果
可能对全屏和透明系统栏有需求的应用大体分两类:
第一类是以内容为核心的应用,比如小说阅读器、浏览器、图片预览、Dashboard 类页面。这类页面希望内容从上到下连贯展示,不希望顶部有个突兀的纯色状态栏打断视觉。比如背景是深蓝色,顶部状态栏如果还是黑色或者白色,就会显得很割裂,内容全屏后让背景色直接透过透明系统栏延伸到最顶端,视觉上会自然得多。
第二类是沉浸式需求强烈的应用,比如视频播放器、游戏、地图导航。它们更希望隐藏状态栏和导航栏,让用户聚焦在内容上。不过游戏或视频全屏时往往会选择隐藏系统栏,而地图导航通常会在状态栏下显示简易信息,所以系统栏透明且可见的模式正好适合做“悬浮半透明导航”。
如果你的应用有列表、表单、聊天界面,同样要处理系统栏透明,因为默认内容会延伸到顶部和底部,如果不给根布局加上安全区 padding,第一个条目会被状态栏遮住,底部输入框会被手势导航条挡住。
1.2 Android 16 为什么把透明全屏变成“默认规则”
老开发者都知道,以前 Android 的默认形态其实和 iOS 不太一样:应用内容默认只在 system windows 内的可用区域绘制,状态栏和导航栏通常是不透明的黑色或品牌色,应用如果要实现“沉浸式”,需要自己写大量兼容代码,比如 FLAG_TRANSLUCENT_STATUS、SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 等等。这套机制理解成本低,但问题也很多:各家 ROM 自行魔改 SystemUI,导致同一套代码在不同厂商手机上表现完全不同,很多开发者干脆只适配了某几个机型。
随着屏幕比例变长和全面屏手势普及,Android 团队决定彻底终结这种混乱状态。从 Android 15 开始,强制要求以 targetSdk 35 及以上编译的应用默认启用 edge-to-edge;Android 16 延续并强化了这一策略。也就是说,当你的应用指定 targetSdk 36 时,系统会忽略你在全局主题里设置的 statusBarColor 和 navigationBarColor,默认帮你把系统栏颜色变成透明,同时把内容区域扩展到系统栏后面,根本不需要你再去做那些“透明”的 hack。
换句话说,Android 16 的开发者不再需要主动“申请”全屏和透明,而是默认就是全屏和透明。你需要做的是反过来——学会在新的默认模式下正确布局,让内容不会撞到系统图标上。所以说,标题里的“Android16 系统内容全屏,状态栏和导航栏透明”并不是一种需要刻意实现的黑科技,而是每位 targetSdk 36 开发者都要面对的新基线。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统级解决方案选型:老 API 为什么会失效
这部分是重点。既然 Android 16 默认强制 edge-to-edge,那我们把所有旧的实现方式都扔掉吗?也不是,关键是理解哪些 API 被废弃、哪些还能用、新 API 解决了什么问题。
2.1 旧方案为什么失效
在 Android 10 以前,很多应用实现“透明状态栏”的代码是这样写的:
java复制getWindow().setStatusBarColor(Color.TRANSPARENT);
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
Android 10 以后,还开始流行调用 WindowInsetsController 等新 API,但大多数人依然会习惯性在 Activity 的 onCreate 里先手动把状态栏颜色设成透明作为“保险”,像这样:
kotlin复制window.statusBarColor = Color.TRANSPARENT
window.navigationBarColor = Color.TRANSPARENT
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
到了 Android 15 / 16 + targetSdk 36,情况就变了。系统强制执行 edge-to-edge,代码里再调用上述旧属性时,很多会被忽略,甚至有的系统栏颜色会出现不预期的黑色闪烁。比如 Window#setStatusBarColor 在 targetSdk 35+ 时基本只会应用在 3-button navigation 模式下,对手机默认的手势导航条不再生效。导航栏同理,系统现在只支持用透明背景上的对比度来区分图标颜色,而不是用纯色背景。
另外,SYSTEM_UI_FLAG_LAYOUT_* 这一整套 flags 在 API 30 就已经被标记为 deprecated,不过当时清理得不够彻底。Android 13 后很多 flags 实际上已经由系统策略覆盖,到了 API 36 再用就会收到警告甚至直接无效。强行设置还可能让布局在运行不同系统版本的设备上表现不同,形成新的兼容性问题。
所以我的建议是:只要开发的 targetSdk 已经 >= 35,就没必要再写那一套老代码。把控制权交给系统,用官方的 enableEdgeToEdge() 作为入口,然后自己处理 Insets 更省心。
2.2 官方推荐的统一入口:enableEdgeToEdge
Jetpack 的 androidx.activity 库从 Activity 1.8.0 开始提供了 enableEdgeToEdge() 扩展函数,这是目前最推荐的适配入口。它可以看作官方团队帮你把系统栏透明、图标明暗、以及必要的布局 flag 都设置好。我是在 targetSdk 升级到 35 之后开始使用的,迁移成本非常低。
简单说,enableEdgeToEdge() 实现了以下效果:
- 内容扩展到状态栏和导航栏后面;
- 将系统栏背景清为透明;
- 根据系统深色模式,自动设置浅色状态栏图标(深色图标,对应浅色栏背景)还是亮色状态栏图标;
- 适配三键导航和手势导航两种模式下的导航栏。
在 Activity 中调用时,需要放在 setContentView 之前:
kotlin复制class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_main)
}
}
如果项目还在使用 Fragment,或者多种入口 Activity,每个 Activity 都要在 setContentView 前调用一次。因为它本质是修改当前 window 的属性,不能通过一个 Application 全局设置。
也有一部分情况不适合直接用 enableEdgeToEdge(),比如需要在自定义主题里指定状态栏为固定品牌色,或者某个页面希望沿用沉浸式隐藏。这种时候更推荐使用 `WindowCompat.setDecorFitsSystemWind
