我可以在导航组件中使用多个NavHostFragments吗?

AnE*_*Bug 17 android android-architecture-components android-architecture-navigation

如果您难以理解以下段落,请查看我制作的流程图.

我目前正在制作一个有3个顶级目的地的笔记应用程序.其中一个顶级目标(NotesList)显示用户创建的注释列表.NotesList有一个过滤器按钮,它会打开一个带有FilterMenu目标的底部模态表.FilterMenu有一个搜索按钮,点击后,用一个搜索目的地替换工作表的内容,一个名为tags的按钮,点击后,用一个包含与所有注释相关的标签列表的片段替换工作表的内容(TagList)目的地).

在此输入图像描述

蓝色的一切都是顶级目的地.紫色的所有东西都存在于模态表中.

FilterMenu,Search和TagList显示在模式表中.这意味着NotesList 包含这些片段,而不是由它们替换.它们存在于小于NotesList的屏幕区域中.如果我使用导航,片段将互相替换.

我可以使用两个NavHosts吗?一个用于顶级目的地,另一个用于模态表中的东西?如果是这样,我将如何实现它?如果没有,在这种情况下建议做什么?

cry*_*sxd 5

您可以创建两个导航图来实现所需的行为。一个用于顶级目的地,第二个用于模式表。它们需要独立,并且彼此之间没有任何链接。您不能仅使用一个导航图,因为“导航表面”是不同的。对于主导航,是活动,对于模式底部,是底部窗口(在BottomSheetDialogFragment实际上是另一个窗口的情况下)。

从理论上讲,这很容易实现:

  • main_nav.xml持有SettingsNoteListTrash
  • filter_nav.xml持有FilterMenuSearchTagList

如果您不希望在顶层进行反向导航,则甚至可以在没有导航控制器使用片段事务的情况下进行顶层导航。

因此,基本上,您需要一个(BottomSheet)DialogFragment需要NavHost独立于main / other的独立组件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"/>
Run Code Online (Sandbox Code Playgroud)

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
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我们在这里有两个技巧:

  1. 我们需要手动创建NavHost片段。如果我们直接将其放入XML中,由于已经使用了ID,它将在第二次打开对话框时崩溃

  2. 我们需要覆盖对话框的后退导航。一个对话是对你的活动的顶部一个单独的窗口,所以ActivityonBackPressed()被不叫。相反,我们添加一个OnKeyListener,当释放后退按钮(ACTION_UP)时,我们检查NavController是否可以弹出后退堆栈(返回)。如果它可以弹出后退堆栈,则我们返回true,从而消耗back事件。对话框保持打开状态,并向NavController后退一步。如果已经在起点,则对话框将关闭,因为我们返回false。

现在,您可以在对话框内部创建嵌套图,而不必关心外部图。要显示带有嵌套图的对话框,请使用:

val dialog = ModalBottomSheetDialogFragment()
dialog.show(childFragmentManager, "filter-menu")
Run Code Online (Sandbox Code Playgroud)

您也可以在中添加ModalBottomSheetDialogFragmentas作为<dialog>目的地main_nav,尽管我没有对此进行测试。此功能当前仍在Alpha中,并已在导航2.1.0-alpha03中引入。因为它仍然处于Alpha状态,所以API可能会更改,我个人将使用上面的代码来显示对话框。一旦超出alpha / beta,main_nav.xml则首选使用in目的地。从用户的角度来看,显示对话框的不同方法没有区别。

在GitHub上使用您的导航结构创建了一个示例应用程序。它具有两个独立图形的两个级别的反向导航。您可以在Youtube上看到它的运行情况。我在主导航中使用了底部栏,但您可以将其替换为抽屉。

  • 实际上,我从Ivan Milisavljevic那里得到了其他建议,请参阅他的演示https://github.com/milis92/Navigation-example,我将他对&lt;dialog&gt;和模式片段类的使用与您的运行时navhostfragment实例化相结合,并且似乎运行良好。也许您可以检查出他的所作所为并将其合并到您的答案中,所以我们不需要`dialog.show()`例如 (2认同)