Kotlin中PopupMenu弹出时如何全程隐藏状态栏与导航栏
问题表现
- PopupMenu启动时状态栏短暂闪烁
- 菜单弹出到选项选择全流程中导航栏持续显示,无法保持隐藏
问题根源
原生PopupMenu属于独立弹出窗口,默认会触发系统栏显示逻辑。原有实现将系统栏隐藏逻辑放在onWindowFocusChanged回调中执行,属于事后补救,执行时机晚于系统栏被弹窗拉起的时机,必然出现闪烁、导航栏常驻问题。另外原有代码创建PopupMenu时传入applicationContext,没有对应窗口令牌,会导致窗口沉浸式配置失效。
核心修改逻辑
- 移除
onWindowFocusChanged中的后置隐藏逻辑,在Activity窗口初始化阶段就配置沉浸式规则 - 所有弹出类组件必须使用Activity作为Context,禁止传入ApplicationContext
- 给PopupMenu内部的弹出窗口同步配置和主窗口一致的沉浸式隐藏规则,从源头阻止系统栏被拉起
- 菜单关闭时重新校验沉浸式状态,避免异常场景下系统栏意外显示
可直接运行的修改后代码
MainActivity.kt
class MainActivity : AppCompatActivity() { private lateinit var popupTriggerBtn: Button private fun applyFullScreenRule(targetWindow: Window) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { targetWindow.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { targetWindow.setDecorFitsSystemWindows(false) targetWindow.insetsController?.apply { hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars()) systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE } } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { @Suppress("DEPRECATION") targetWindow.decorView.systemUiVisibility = ( View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN ) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) supportActionBar?.hide() // 主窗口初始化即应用全屏规则 applyFullScreenRule(window) popupTriggerBtn = findViewById(R.id.popup_press) popupTriggerBtn.setOnClickListener { // 必须传入Activity上下文,禁止用applicationContext val popupMenu = PopupMenu(this, popupTriggerBtn) popupMenu.menuInflater.inflate(R.menu.popupmenu, popupMenu.menu) // 反射获取PopupMenu内部弹窗实例,配置关闭时的全屏校验 runCatching { val popupField = PopupMenu::class.java.getDeclaredField("mPopup") popupField.isAccessible = true val menuPopupHelper = popupField.get(popupMenu) val innerPopupField = menuPopupHelper.javaClass.getDeclaredField("mPopup") innerPopupField.isAccessible = true val innerPopupWindow = innerPopupField.get(menuPopupHelper) as PopupWindow innerPopupWindow.setOnDismissListener { applyFullScreenRule(window) } } popupMenu.setOnMenuItemClickListener { item -> when (item.itemId) { R.id.menuA -> Toast.makeText(this, "Menu A", Toast.LENGTH_LONG).show() R.id.menuB -> Toast.makeText(this, "Menu B", Toast.LENGTH_LONG).show() R.id.menuC -> Toast.makeText(this, "Menu C", Toast.LENGTH_LONG).show() } true } popupMenu.show() // 弹窗挂载后立刻给菜单窗口同步全屏规则,从源头阻止系统栏弹出 Handler(Looper.getMainLooper()).post { runCatching { val popupField = PopupMenu::class.java.getDeclaredField("mPopup") popupField.isAccessible = true val menuPopupHelper = popupField.get(popupMenu) val getListMethod = menuPopupHelper.javaClass.getDeclaredMethod("getListView") getListMethod.isAccessible = true val menuList = getListMethod.invoke(menuPopupHelper) as ListView val menuWindow = (menuList.context as ContextThemeWrapper).windowContext as Window applyFullScreenRule(menuWindow) } } } } }
布局文件(activity_main.xml)
原有布局无需修改,直接复用即可:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/popup_press" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="150dp" android:text="POP UP MENU" android:textSize="24sp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
菜单文件(popupmenu.xml)
原有菜单配置无需修改,直接复用即可:
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/menuA" android:title="menu A"> </item> <item android:id="@+id/menuB" android:title="menu B"> </item> <item android:id="@+id/menuC" android:title="menu C"> </item> </menu>
稳定性优化方案
如果担心安卓版本迭代修改PopupMenu内部字段名导致反射失效,可以直接弃用原生PopupMenu,用ListPopupWindow自定义实现菜单效果,所有窗口属性完全自主控制,不需要反射,兼容性更好。
内容的提问来源于stack exchange,提问作者kotlining
相关产品推荐
相关产品推荐

