您可以创建两个导航图来实现您想要的行为。一个用于顶级目的地,第二个用于模式表。它们需要独立,彼此之间没有任何联系。您不能仅使用一个导航图,因为“导航表面”是不同的。对于主导航,它是活动,对于模态底部工作表,它是底部工作表窗口(在 BottomSheetDialogFragment 的情况下,它实际上是一个不同的窗口)。
理论上这可以很容易实现:
-
main_nav.xml
holds Settings
, NoteList
and Trash
-
filter_nav.xml
持有FilterMenu
, Search
, and TagList
如果您不想在顶层进行后退导航,您甚至可以使用片段事务在没有导航控制器的情况下进行顶层导航。
所以基本上你需要一个(BottomSheet)DialogFragment
这需要一个单独的NavHost
独立于主要/其他NavHost
。您可以通过以下课程来实现这一目标:
dialog_fragment_modal_bottom_sheet.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/filterNavHost"/>
ModalBottomSheetDialogFragment .kt
class ModalBottomSheetDialogFragment : BottomSheetDialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
inflater.inflate(R.layout.dialog_fragment_modal_bottom_sheet, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// We can't inflate the NavHostFragment from XML because it will crash the 2nd time the dialog is opened
val navHost = NavHostFragment()
childFragmentManager.beginTransaction().replace(R.id.filterNavHost, navHost).commitNow()
navHost.navController.setGraph(R.navigation.filter_nav)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return super.onCreateDialog(savedInstanceState).apply {
// Normally the dialog would close on back press. We override this behaviour and check if we can navigate back
// If we can't navigate back we return false triggering the default implementation closing the dialog
setOnKeyListener { _, keyCode, event ->
if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) {
view?.findNavController()?.popBackStack() == true
} else {
false
}
}
}
}
}
我们在这里做了两个技巧:
我们需要手动创建NavHost
分段。如果我们直接将其放入 XML 中,则第二次打开对话框时会崩溃,因为 ID 已被使用
我们需要覆盖对话框的后退导航。对话框是位于 Activity 之上的一个单独窗口,因此Activity
's onBackPressed()
没有被叫到。相反,我们添加一个OnKeyListener
当释放后退按钮时(ACTION_UP
)我们检查NavController
是否可以弹出返回堆栈(返回)。如果它可以弹出后退堆栈,我们将返回 true,从而消耗后退事件。对话框保持打开状态并且NavController
退一步。如果它已经在起点,当我们返回 false 时,对话框将关闭。
您现在可以在对话框内创建嵌套图,而不必关心外部图。要显示带有嵌套图的对话框,请使用:
val dialog = ModalBottomSheetDialogFragment()
dialog.show(childFragmentManager, "filter-menu")
您还可以添加ModalBottomSheetDialogFragment
as <dialog>
目的地在main_nav
,但我没有对此进行测试。该功能目前仍处于 alpha 版本,并在 navigation 2.1.0-alpha03 中引入。由于这仍处于 alpha 阶段,API 可能会发生变化,我个人会使用上面的代码来显示对话框。一旦超出 alpha/beta,请使用以下目的地:main_nav.xml
应该是首选方式。从用户的角度来看,显示对话框的不同方式没有什么区别。
我使用您的导航结构创建一个示例应用程序在 GitHub 上 https://github.com/crysxd/NestedNavHostsExample。它具有两个独立图表的两个级别的工作返回导航。你可以看到它正在运行在 YouTube 上 https://youtu.be/lnyDB7DH9KM。我使用底部栏作为主导航,但您可以用抽屉替换它。