Android开发:窗口失焦时如何保持导航栏隐藏状态
问题根因
你当前的实现无法在窗口失焦场景下保持导航栏隐藏,核心原因有两点:
- 单独使用
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION属于临时隐藏标记,系统在窗口失焦、弹出系统级窗口、触发系统交互时,会主动清除该标记,导航栏会自动恢复显示。 - 你仅在
onWindowFocusChanged回调中设置UI标记,弹窗弹出、最近任务唤起等场景触发系统UI状态变更时,不会触发该回调,没有重新应用隐藏规则,自然无法维持隐藏状态。
实现方案
要实现和Google Play一致的导航栏隐藏效果(保留状态栏、ActionBar,仅隐藏导航栏,窗口失焦、弹窗场景下不自动弹出导航栏),需要使用粘性沉浸式标记,同时在系统UI状态变更时主动重设隐藏规则,具体实现如下:
1. 封装导航栏隐藏方法
抽离通用的隐藏逻辑,注意不要加状态栏隐藏相关标记,避免影响状态栏和ActionBar的正常展示:
@Suppress("DEPRECATION") private fun setNavigationBarHidden() { val systemUiFlags = ( // 核心隐藏导航栏标记 View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // 粘性沉浸标记:系统交互临时调出导航栏后会自动隐藏,失焦/弹窗不会清除隐藏状态 or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY // 以下两个标记用于保持布局稳定,避免导航栏隐藏/显示时布局被挤压 or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_STABLE ) window.decorView.systemUiVisibility = systemUiFlags }
2. 在多节点触发隐藏逻辑
不要只在onWindowFocusChanged中调用,需要覆盖所有可能导致标记被清除的场景:
- 在
onResume生命周期调用,覆盖应用从后台切回的场景:
override fun onResume() { super.onResume() setNavigationBarHidden() }
- 保留
onWindowFocusChanged中的调用,覆盖窗口焦点切换场景:
override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) setNavigationBarHidden() }
- 给DecorView添加系统UI可见性监听,一旦检测到导航栏被系统显示,立刻延迟重设隐藏规则,覆盖弹窗、系统交互场景:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 监听系统UI状态变化 window.decorView.setOnSystemUiVisibilityChangeListener { visibility -> if (visibility and View.SYSTEM_UI_FLAG_HIDE_NAVIGATION == 0) { // 加短延迟避免和系统UI设置逻辑冲突 window.decorView.postDelayed({ setNavigationBarHidden() }, 100) } } // 其余初始化逻辑 }
3. 特殊场景适配
- Android 11(API 30)及以上版本适配:可以使用官方新的WindowInsetsController API实现,效果完全等价:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { window.insetsController?.let { controller -> controller.hide(WindowInsets.Type.navigationBars()) controller.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE } } else { // 低版本走前面的旧API逻辑 window.decorView.setOnSystemUiVisibilityChangeListener { visibility -> if (visibility and View.SYSTEM_UI_FLAG_HIDE_NAVIGATION == 0) { window.decorView.postDelayed({ setNavigationBarHidden() }, 100) } } } }
- 应用内弹窗/Dialog适配:Dialog的窗口是独立于Activity的,默认会重置系统UI标记,弹出Dialog时如果要保持导航栏隐藏,需要给Dialog的Window也设置相同的导航栏隐藏规则。
补充:
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY标记的交互逻辑和系统原生应用完全一致:用户从屏幕边缘(导航栏所在区域)向内滑动时,会临时调出半透明的导航栏,无操作数秒后会自动隐藏,不会常驻。
内容的提问来源于stack exchange,提问作者Sethuraman Srinivasan
相关产品推荐
相关产品推荐

