如何处理多个NavHosts/NavControllers?

4
我在处理多个NavHosts时遇到了问题。这个问题与此处所问的问题非常相似。我认为该问题的解决方案也适用于我,但这是2017年的帖子,目前仍然没有得到答复。 Android开发文档并没有提供相应帮助,搜索互联网也没有找到任何可能有用的信息。
基本上,我有一个Activity和两个Fragment。我们称它们为FruitsActivity、FruitListFragment和FruitDetailFragment。其中FruitsActivity没有相关代码,并且其xml布局由<fragment>标签组成,作为NavHost使用,如下所示:
<fragment
            android:id="@+id/fragmentContainer"
            android:name="androidx.navigation.fragment.NavHostFragment"
            app:navGraph="@navigation/fruits_nav_graph"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

FruitListFragment是我NavGraph的startDestination,它处理从服务器获取的水果列表。FruitDetailFragment显示有关FruitListFragment中选择的水果的详细信息。

到目前为止,我们有Activity -> ListFragment -> DetailFragment。

现在我需要添加另一个名为GalleryFragment的Fragment。这是一个简单的fragment,用于显示所选水果的许多图片,并且在单击按钮时由FruitDetailFragment调用。

问题在于:在纵向模式下,我只需使用findNavController().navigate(...)即可导航到所需的Fragment。但是当我在横向模式下使用平板电脑时,我使用主细节流在同一屏幕上显示列表和详细信息。可以在此处看到其工作示例。我希望GalleryFragment替换FruitDetailFragment,与水果列表共享屏幕,但到目前为止,我只能使它替换“主要导航”流,占据整个屏幕并将FruitListFragment发送到后堆栈。

我已经尝试在findNavController()方法中做出更改,但无论我在哪里调用它,我都只能每次获得相同的NavController,并且它总是以相同的线性方式进行导航。我尝试实现自己的NavHost,但出现错误“找不到androidx.navigation.NavHost的类文件”。

这是我的FruitsActivity的xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <fragment
            android:id="@+id/fragmentContainer"
            android:name="androidx.navigation.fragment.NavHostFragment"
            app:navGraph="@navigation/listing_nav_graph"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </FrameLayout>

</layout>

这是在横向模式下FruitListActivity的XML文件(在纵向模式下只有RecyclerView):
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/rvFruits"
                android:layout_weight="3"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_margin="8dp"/>

            <FrameLayout
                android:layout_weight="1"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
                <fragment
                    android:id="@+id/sideFragmentContainer"
                    android:name="fruits.example.FruitDetailFragment"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"/>
            </FrameLayout>

        </LinearLayout>

    </RelativeLayout>

</layout>

现在我想调用GalleryFragment,并使它仅替换id为'sideFragmentContainer'的<fragment>而不是整个屏幕(即不替换Activity xml中id为fragmentContainer<fragment>)。

我没有找到如何处理多个NavHosts或<fragment>内部有<fragment>的任何说明。

基于此,是否可以使用导航架构在另一个片段中显示一个片段? 我需要多个NavHosts吗,还是有其他方法?


3
我在一篇关于如何在平板电脑和手机的不同布局中使用导航主机的博客文章中看到过类似的内容。请查看此处。这可能不是您正在寻找的完全相同的内容,但很接近。我今天晚些时候会有时间测试一些东西(目前正在工作)。 - jsmyth886
@jsmyth886 确实,这很接近我想要实现的目标。 我会尽快查看它。 非常感谢! - Rver
1个回答

9

根据jsmyth886的建议,这篇博客文章指引了我正确的方向。

关键是要使用findFragmentById()从片段容器中直接获取NavHost(在这种情况下,它与其余主/详情屏幕共享)。这样我就可以访问正确的NavController并按预期进行导航。

创建第二个NavGraph也很重要。

因此,快速的步骤如下:

  • 创建主要的NavGraph,以执行所有常规导航(与不使用主/详情流程时的情况相同);
  • 创建一个包含仅主/详情片段将访问的可能Destinations的次要NavGraph。没有连接它们的Actions,只有Destinations
  • 在主要的<fragment>容器中,设置属性如下:

    <fragment
                android:id="@+id/fragmentContainer"
                android:name="androidx.navigation.fragment.NavHostFragment"
                app:navGraph="@navigation/main_nav_graph"
                app:defaultNavHost="true"
                android:layout_width="match_parent"
                android:layout_height="match_parent"/>

  • app:defaultNavHost="true" 很重要。这样,主从布局的外观将会是这样的:

    <?xml version="1.0" encoding="utf-8"?>
        <layout xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto">

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                <LinearLayout
                    android:orientation="horizontal"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent">

                    <androidx.recyclerview.widget.RecyclerView
                        android:id="@+id/rvFruits"
                        android:layout_weight="3"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:layout_margin="8dp"/>

                    <FrameLayout
                        android:layout_weight="1"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent">
                        <fragment
                            android:id="@+id/sideFragmentContainer"
                            android:name="androidx.navigation.fragment.NavHostFragment"
                            app:navGraph="@navigation/secondary_nav_graph"
                            app:defaultNavHost="false"
                            android:layout_width="match_parent"
                            android:layout_height="match_parent"/>
                    </FrameLayout>

                </LinearLayout>

            </RelativeLayout>

        </layout>

  • 再次提醒,属性app:defaultNavGraph很重要,在这里将其设置为false。
  • 在代码部分,您应该有一个布尔标志来验证您的应用程序是否在平板电脑上运行(答案开头提供的链接解释了如何执行此操作)。在我的情况下,我将其作为ViewModel中的MutableLiveData,这样我就可以观察它并相应地更改布局。
  • 如果不是平板电脑(即遵循正常导航流程),只需调用findNavController().navigate(R.id.your_action_id_from_detail_to_some_other_fragment)。主要的导航将使用主NavController进行;
  • 如果是平板电脑使用主从布局,您必须通过找到包含它的<fragment>来找到正确的NavHostNavController,像这样:
val navHostFragment = childFragmentManager.findFragmentById(R.id. sideFragmentContainer) as NavHostFragment
  • 最后,您可以通过调用navHostFragment.navController.navigate(R.id.id_of_the_destination)来导航到要分割屏幕与主细节屏幕其余部分的片段。请注意,这里我们不调用Actions,而是直接调用Destination

就是这样,比我想象中简单。感谢Lara Martín的博客文章和jsmyth886指引我正确的方向!


不错,很高兴能帮到你 :) - jsmyth886
@Rver,我在我的项目中遇到了类似的问题,想知道你是否有Github仓库可以查看你的代码? - gallosalocin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接