前言:
Android低版本中的 WindowInsets 分发机制是「懒惰式且缺乏自动性」的 —— 依赖 ViewRootImpl.performTraversals() 调用链的主动触发,无法在布局未完成的场景中保证完整、时机正确地分发 Insets。
从一个Bug开始说起:
今天测试反馈了一个bug,有个项目的某个界面,有一个按钮,应该正常显示的,那个 界面的情况是【刚进入时是沉浸式图片的状态,点击后隐藏所有图标进入全屏状态,再次点击显示图片恢复沉浸式图片的状态】, 但是在某些设备上无法显示,排查后发现,出现这个问题的前提是
看起来很奇怪,但是想起前几天解决的【底部导航栏开启导致toolbar的显示问题】的问题,感觉很可能是系统的问题,有兴趣的可以去看一下这篇文章:
先看一下沉浸式图片的代码:
123456789101112131415161718 | fun enterImageImmersiveMode(activity: AppCompatActivity, lightStatusBar: Boolean = false) { val window = activity.window val controller = WindowInsetsControllerCompat(window, window.decorView) // 使用post延迟执行以确保UI状态稳定 window.decorView.post { WindowCompat.setDecorFitsSystemWindows(window, false) window.statusBarColor = Color.TRANSPARENT window.navigationBarColor = Color.TRANSPARENT controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_DEFAULT controller.show(WindowInsetsCompat.Type.systemBars()) controller.isAppearanceLightStatusBars = lightStatusBar controller.isAppearanceLightNavigationBars = lightStatusBar activity.supportActionBar?.hide() }} |
---|
为了避免设置的问题特意加上了post,但是还是有问题,而调用下面的代码后,设置isVisible就正常了
123456789101112 | fun enterFullscreen(activity: AppCompatActivity) { val window = activity.window WindowCompat.setDecorFitsSystemWindows(window, false) val controller = WindowInsetsControllerCompat(window, window.decorView) controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE // 使用post延迟执行以确保UI状态稳定 window.decorView.post { controller.hide(WindowInsetsCompat.Type.systemBars()) activity.supportActionBar?.hide() }} |
---|
问题本质分析
这是 WindowInsets 与 View 布局生命周期在 Android 8 下处理不一致 导致的,具体包括:
深入剖析:WindowInsets 的传递机制演化
Android版本 | WindowInsets分发机制 | 是否自动触发重新分发 | 是否支持动态监听Insets变化 |
---|---|---|---|
Android 4.4 (API 19) | 初步引入,使用 fitSystemWindows() | ❌ 手动触发 | ❌ |
Android 5~8 (API 21–27) | WindowInsets 支持透明状态栏,但依赖 ViewRootImpl 分发 | ❌(需要手动调用 requestApplyInsets()) | ❌ |
Android 9 (API 28) | 小幅增强,支持异形屏,但仍需手动触发 | ❌ | ✅ 部分事件 |
Android 10+ (API 29+) | 系统主动监听 WindowInsets 变化,并自动分发 | ✅ 自动触发 | ✅ 完整支持 |
所以,在 Android 8 及以前:
深入剖析:为什么 Android 10+ 就不会出这个问题?
从 Android 10(API 29) 开始,谷歌对 WindowInsets 做了核心重构:
改进 | 说明 |
---|---|
View.OnApplyWindowInsetsListener 增强 | 改为「监听 + 主动分发」机制 |
ViewRootImpl 改动 | 引入 WindowInsetsAnimationController 和 InsetsState,用于实时动画支持 |
DecorView 支持透明栏动画 | 在 insets 改变时自动 request layout 和重新分发 |
WindowInsetsController 提供完整动画行为 | 可以通过滑动触发系统栏显示/隐藏,保证时序正确 |
所以 Android 10+ 可以保证即使在 View 动态变为 VISIBLE 后,也能重新触发 Insets 传递流程,而 Android 8 不会。
深入剖析:Android 8 源码中的关键限制
我们可以从 Android 8 中的 ViewRootImpl.performTraversals()
看到如下逻辑:
12345 | if (mFirst || windowShouldResize || insetsChanged || ...) { performMeasure(); performLayout(); performDraw();} |
---|
其中 insetsChanged
只有在窗口尺寸或类型变化时才会触发,且:
1 | dispatchApplyInsets(windowInsets); |
---|
只有在 layout 流程中执行,如果 View 在这个阶段是 GONE 或不可见,系统不会再重新分发一次 insets,需要手动:
1 | view.requestApplyInsets() // 或 decorView |
---|
倒推bug出现的情况
场景:
结果:
解决方案:
初始化沉浸式图片展示状态时手动调用【window.decorView.requestApplyInsets() 】,强制刷新 Insets 和 Layout
1234567891011121314151617181920 | fun enterImageImmersiveMode(activity: AppCompatActivity, lightStatusBar: Boolean = false) { val window = activity.window val controller = WindowInsetsControllerCompat(window, window.decorView) // 使用post延迟执行以确保UI状态稳定 window.decorView.post { WindowCompat.setDecorFitsSystemWindows(window, false) window.statusBarColor = Color.TRANSPARENT window.navigationBarColor = Color.TRANSPARENT controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_DEFAULT controller.show(WindowInsetsCompat.Type.systemBars()) controller.isAppearanceLightStatusBars = lightStatusBar controller.isAppearanceLightNavigationBars = lightStatusBar activity.supportActionBar?.hide() // 💡 强制刷新 Insets 和 Layout window.decorView.requestApplyInsets() }} |
---|
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。