Navigation Component中从含DialogFragment的Fragment回退时重复显示DialogFragment
我之前也碰到过类似的坑,结合你给出的代码来看,问题出在Navigation返回栈的处理上,咱们一步步拆解:
问题复现场景
- 在
MainActivity里用item.onNavDestinationSelected(navController)自动处理向上导航 - 从
StationsFragment点击菜单可以正常打开GameInfoDialog - 关闭Dialog后点击顶部向上箭头,Dialog居然又弹出来了
你提供的相关代码
MainActivity的向上导航逻辑
override fun onOptionsItemSelected(item: MenuItem): Boolean { return item.onNavDestinationSelected(navController) || super.onOptionsItemSelected(item) }
StationsFragment打开Dialog的代码
override fun onOptionsItemSelected(item: MenuItem): Boolean { val bundle = Bundle() bundle.putInt(GAME_ID_BUNDLE_KEY, gameId) findNavController().navigate(R.id.action_stationsFragment_to_gameInfoDialog, bundle) return true }
导航图配置片段
<fragment android:id="@+id/stationsFragment" android:name="com.accad.accadgame.screens.fragments.StationsFragment" android:label="@string/stations_fragment_title" tools:layout="@layout/fragment_stations" > <argument android:name="game_id" app:argType="integer" android:defaultValue="-1" /> <action android:id="@+id/action_stationsFragment_to_sectionsFragment" app:destination="@id/sectionsFragment" app:popUpTo="@+id/stationsFragment" app:popUpToInclusive="false" /> <action android:id="@+id/action_stationsFragment_to_gameInfoDialog" app:destination="@id/gameInfoDialog" app:popUpTo="@id/stationsFragment" app:popUpToInclusive="false" /> </fragment> <dialog android:id="@+id/gameInfoDialog" android:name="com.accad.accadgame.screens.dialogs.GameInfoDialog" android:label="GameInfoDialog" tools:layout="@layout/dialog_game_info" > <argument android:name="game_id" app:argType="integer" android:defaultValue="-1" /> </dialog>
问题根源
当你调用navigate()打开Dialog时,这个导航动作会被加入到Navigation的返回栈中。但默认情况下,如果你只是调用dismiss()关闭Dialog,并没有从返回栈中移除这条导航记录。所以当你点击向上箭头时,Navigation会执行返回栈里的上一个操作——也就是再次触发打开Dialog的动作,这就导致了重复弹出的问题。
另外,你在StationsFragment的onOptionsItemSelected里直接返回true,虽然不是核心问题,但会拦截所有菜单事件的默认处理,建议改成按菜单ID分支处理,更规范。
两种可行的解决方案
方案1:用Navigation API关闭Dialog(推荐)
不要手动调用dismiss(),而是在Dialog里通过popBackStack()来关闭,这样会同时从返回栈中移除对应的记录:
// 在GameInfoDialog的关闭按钮点击事件中 binding.closeButton.setOnClickListener { findNavController().popBackStack() }
这样操作后,关闭Dialog时返回栈就干净了,再点击向上箭头就会正常回到StationsFragment的上一级页面,或者退出App(根据你的导航结构)。
方案2:在MainActivity中拦截向上箭头的Dialog场景
修改MainActivity的向上导航逻辑,判断当前目的地是否是Dialog类型,如果是就直接弹出返回栈:
override fun onOptionsItemSelected(item: MenuItem): Boolean { if (item.itemId == android.R.id.home) { val currentDest = navController.currentDestination // 判断当前是否是Dialog类型的导航目的地 if (currentDest is DialogFragmentNavigator.Destination) { navController.popBackStack() return true } } return item.onNavDestinationSelected(navController) || super.onOptionsItemSelected(item) }
这个方案不需要修改Dialog的关闭逻辑,适合不想改动Dialog代码的场景。
额外的规范优化
把StationsFragment的onOptionsItemSelected改成分支处理,避免拦截所有菜单事件:
override fun onOptionsItemSelected(item: MenuItem): Boolean { return when(item.itemId) { // 替换成你的信息菜单项ID R.id.menu_item_game_info -> { val bundle = Bundle() bundle.putInt(GAME_ID_BUNDLE_KEY, gameId) findNavController().navigate(R.id.action_stationsFragment_to_gameInfoDialog, bundle) true } else -> super.onOptionsItemSelected(item) } }
总结
核心就是要让Dialog的关闭操作同步更新Navigation的返回栈,要么通过Navigation API关闭,要么在向上导航时特殊处理Dialog场景,这样就能避免重复弹出的问题了。
内容的提问来源于stack exchange,提问作者Ali Habbash

