@ianhanniballake提出了一个很好的观点,解释了为什么这不是完全可能的,以及它为什么能够按照它的方式工作,但实际上有一种解决方法。这种解决方法并不是最愉快的,所以请记住这一点。我在下面列出了它自己的缺点。
理论
这种解决方法涉及到一些“旧”的Android机制,这些机制早于导航控制器(我们并不总是有导航控制器)。这种解决方法围绕着几个事实:
- 所有片段都存在于某个
FragmentManager
中。导航控制器并不神奇,它仍然在幕后使用FragmentManager
。实际上,您可以将导航控制器视为FragmentManager
的包装器。
- 所有片段都带有自己的小
FragmentManager
。您可以通过片段内的childFragmentManager
访问它。在childFragmentManager
中启动的任何片段都被视为该片段的子级。
- 当片段移动到“后退栈”时,它的所有子级也会随之移动。
- 当片段被恢复时,它的子级也会被恢复。
有了这四个事实,我们就可以制定一个解决方法。
这个想法是,如果我们在一个片段的childFragmentManager
上显示所有的DialogFragment
,那么我们就可以在没有任何对话框相关问题的情况下导航到其他片段。这是因为当我们从FragA导航到FragC时,FragA的所有子项都被移动到后退堆栈中。由于我们使用childFragmentManager
启动了DialogFragment
,所以DialogFragment
也会自动关闭。
现在,当用户返回到我们的片段(FragA)时,我们的DialogFragment
会再次显示,因为FragA的childFragmentManager
也被恢复了。而我们的DialogFragment
则存在于该childFragmentManager
中。
实现
现在我们知道如何解决这个问题,让我们开始实现它。
为了简单起见,让我们重用你提供的示例。也就是说,我们假设有片段FragA
和FragC
以及对话框DialogB
。
首先,虽然导航组件很好用,但如果我们想要这样做,我们不能使用它来启动对话框。如果您使用安全参数,则仍然可以继续获得其好处,因为从技术上讲,安全参数并不与导航组件绑定。以下是启动对话框 B 的示例:
fun onLaunchBClick() {
val parentFragment = parentFragment ?: return
DialogB()
.apply {
arguments = DialogBArgs(myArg1, myArg2).toBundle()
}
.show(parentFragment?.childFragmentManager, "DialogB")
}
现在我们可以让
DialogB
启动
FragC
,但有一个问题。因为我们使用了
childFragmentManager
,导航控制器实际上看不到
DialogB
。这意味着对于导航控制器来说,我们是从
FragA
启动
FragC
。如果导航图中有多个边缘指向
DialogB
,这可能会创建一个问题。解决此问题的方法是将
DialogB
的所有方向都设置为全局。这最终是此解决方法的缺点。在这种情况下,我们可以声明一个全局操作到
FragC
并通过它来启动它。
fun onLaunchCClick() {
val direction = NavMainDirections.actionGlobalFragC()
findNavController().navigate(direction)
}
缺点
这种方法显然存在一些明显的缺点。最大的一个是对话框可以导航到的所有片段都应声明为全局操作。唯一的例外是如果对话框只有一个边缘。如果对话框只有一个边缘,而且不太可能添加新的边缘,那么你可以在它唯一的父片段中添加操作。
例如,如果DialogC
可以启动FragmentC
和FragmentD
,并且DialogC
可以从FragmentA
和FragmentZ
(2个边缘)启动,则DialogC
必须使用全局操作来启动FragmentC
或FragmentD
。
另一个缺点是我们不能再使用导航控制器来启动需要启动其他非对话框片段的对话框片段。这个缺点相对较轻,因为我们至少仍然可以使用安全参数。
最后一个缺点是性能可能会稍微差一些。考虑这样一个例子,我们有一个片段
FragA
启动
DialogB
启动
FragC
。现在如果用户点击返回,
FragA
将被恢复。但由于
DialogB
是
FragA
的子级,
DialogB
也将被恢复。这意味着需要加载和恢复额外的片段,从而降低了返回操作的性能。实际上,只要您的片段没有保存大量状态,并且每个片段没有太多的子级,这种成本就应该很小。