You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin中PopupMenu弹出时如何全程隐藏状态栏与导航栏

全屏应用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 20:39:17