API 30+如何检测应用是否处于Edge-to-Edge全屏模式?
核心问题分析
通过WindowCompat.setDecorFitsSystemWindows(window, false)实现Edge-to-Edge(全屏)模式后,在API30+版本中旧的systemUiVisibility标志已被废弃,无法用传统方式检测当前是否处于Edge-to-Edge状态;同时直接给decorView设置OnApplyWindowInsetsListener会打断系统默认的Insets传递逻辑,导致导航栏可见性检测失效,无法动态调整内容的底部padding。
可行解决方案
方案1:自行维护Edge-to-Edge状态变量(最简单直接)
既然Edge-to-Edge状态是通过自定义的fullScreen()和undoFullScreen()方法切换的,完全可以在Activity中维护一个状态变量,直接记录当前模式,无需依赖系统API检测。
修改代码如下:
- 在Activity中添加状态变量:
var isEdgeToEdgeEnabled: Boolean = false private set
- 更新扩展函数,同步状态变量:
fun Activity.fullScreen(isLightStatusBar: Boolean? = null) { window ?: return WindowCompat.setDecorFitsSystemWindows(window, false) isEdgeToEdgeEnabled = true // 可选:状态栏样式设置 isLightStatusBar?.let { val controller = WindowCompat.getInsetsController(window, window.decorView) controller.isAppearanceLightStatusBars = it } if (Build.VERSION.SDK_INT >= 21) { window.statusBarColor = Color.TRANSPARENT } } fun Activity.undoFullScreen(isLightStatusBar: Boolean? = null) { window ?: return WindowCompat.setDecorFitsSystemWindows(window, true) isEdgeToEdgeEnabled = false // 可选:恢复状态栏样式 isLightStatusBar?.let { val controller = WindowCompat.getInsetsController(window, window.decorView) controller.isAppearanceLightStatusBars = it } }
之后需要判断状态时,直接使用isEdgeToEdgeEnabled变量即可,比如给内容设置padding:
if (isEdgeToEdgeEnabled) { binding.content.setPadding(0, 0, 0, navBarHeight) } else { binding.content.setPadding(0, 0, 0, 0) }
方案2:基于WindowInsets动态调整Padding(推荐,符合现代Android规范)
无需检测Edge-to-Edge状态,直接通过系统传递的WindowInsets动态调整内容的padding,这种方式完全兼容API30+及以下版本,且不需要维护额外状态。
关键注意点:不要给decorView设置OnApplyWindowInsetsListener,而是给内容容器(比如binding.content)设置,避免打断系统默认的Insets传递逻辑。
示例代码:
// 在Activity或Fragment的onViewCreated中设置 binding.content.setOnApplyWindowInsetsListener { view, insets -> val windowInsets = WindowInsetsCompat.toWindowInsetsCompat(insets) // 获取导航栏的底部Insets值 val navBarBottomInset = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom // 获取状态栏的顶部Insets值(如果需要处理状态栏遮挡) val statusBarTopInset = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars()).top // 动态设置内容的padding,避开系统栏 view.setPadding( view.paddingLeft, statusBarTopInset, view.paddingRight, navBarBottomInset ) // 返回CONSUMED表示已处理Insets,不再传递给子View;若需要子View处理,返回insets即可 WindowInsetsCompat.CONSUMED }
当setDecorFitsSystemWindows(window, false)时,系统会将系统栏的Insets传递给内容View,此时navBarBottomInset会是导航栏的实际高度,内容会自动添加padding避开导航栏;当切换回true时,系统会自动处理内容的padding,此时navBarBottomInset会变为0,内容的底部padding也会恢复正常,完全无需手动判断状态。
方案3:通过WindowInsetsController判断系统栏可见性(针对隐藏系统栏的场景)
如果你的场景是完全隐藏系统栏(而非Edge-to-Edge延伸),可以通过WindowInsetsController检测系统栏的可见性:
val controller = WindowCompat.getInsetsController(window, window.decorView) val isNavBarVisible = controller.isVisible(WindowInsetsCompat.Type.navigationBars()) val isStatusBarVisible = controller.isVisible(WindowInsetsCompat.Type.statusBars())
注意:这仅适用于系统栏被完全隐藏的情况,Edge-to-Edge模式下系统栏仍可见,此方法无法区分是否处于Edge-to-Edge状态。
额外说明
- API29及以下版本的Insets处理差异,使用
WindowInsetsCompat可以自动兼容,无需单独做版本判断。 - 避免混合使用
setSystemUiVisibility和WindowCompat.setDecorFitsSystemWindows,官方明确说明两者会冲突,导致异常行为。
内容的提问来源于stack exchange,提问作者eddym

