简体   繁体   English

如何处理多个 NavHosts/NavControllers?

[英]How to handle multiple NavHosts/NavControllers?

I'm having a problem when dealing with multiple NavHosts.我在处理多个 NavHost 时遇到问题。 This issue is very similar to the one asked here .这个问题与这里提出的问题非常相似。 I think the solution for this question would help me as well, but it's a post from 2017 and it still has no answer.我认为这个问题的解决方案对我也有帮助,但它是 2017 年的帖子,仍然没有答案。 Android Developers Documentation doesn't help and searching through the web shows absolutely nothing that could possibly help. Android 开发人员文档无济于事,通过网络搜索绝对没有任何可能有帮助的内容。

So basically I have one Activity and two Fragments.所以基本上我有一个活动和两个片段。 Let's call them FruitsActivity, FruitListFragment, FruitDetailFragment, where FruitsActivity has no relevant code and its xml layout is composed by a <fragment> tag, serving as NavHost , like that:让我们称它们为 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"/>

The FruitListFragment is the startDestination of my NavGraph, it handles a list of fruits that will come from the server. FruitListFragment 是我的 NavGraph 的 startDestination,它处理来自服务器的水果列表。 The FruitDetailFragment shows details about the Fruit selected in the list displayed by FruitListFragment. FruitDetailFragment 显示有关在 FruitListFragment 显示的列表中选择的 Fruit 的详细信息。

So far we have Activity -> ListFragment -> DetailFragment.到目前为止,我们有 Activity -> ListFragment -> DetailFragment。

Now I need to add one more Fragment, called GalleryFragment.现在我需要再添加一个 Fragment,称为 GalleryFragment。 It's a simple fragment that displays many pictures of the Fruit selected and it's called by FruitDetailFragment when clicking a button.这是一个简单的片段,显示所选水果的许多图片,并在单击按钮时由 FruitDetailFragment 调用。

The problem here is: In Portrait mode I simply use findNavController().navigate(...) and I navigate through the Fragments like I want.这里的问题是:在纵向模式下,我只需使用findNavController().navigate(...)并按照我想要的方式浏览 Fragment。 but when I'm using a Tablet in Landscape mode, I'm using that Master Detail Flow to display List and Details on the same screen.但是当我在横向模式下使用平板电脑时,我正在使用主详细信息流在同一屏幕上显示列表和详细信息。 There is an example of how it works here , and I want the GalleryFragment to replace the FruitDetailFragment, sharing the screen with the list of fruits, but so far I could only manage to make it replace the "main navigation" flow, occupying the entire screen and sending the FruitListFragment to the Back Stack.还有就是它是如何工作的例子在这里,我想GalleryFragment更换FruitDetailFragment,水果列表共享屏幕,但到目前为止,我只能设法使其代替“主导航”流动,占据整个screen 并将 FruitListFragment 发送到 Back Stack。

I already tried to play around with findNavController() method, but no matter from where I call it I can only get the same NavController all the time, and it always navigates in the same linear way.我已经尝试使用findNavController()方法,但是无论我从哪里调用它,我都只能始终获得相同的NavController ,并且它始终以相同的线性方式导航。 I tried to implement my own NavHost, but I get and error "class file for androidx.navigation.NavHost not found".我试图实现我自己的 NavHost,但我得到并错误“找不到 androidx.navigation.NavHost 的类文件”。

This is the xml of my FruitsActivity:这是我的 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>

This is the xml of the FruitListActivity in Landscape mode (in portrait mode there is just the RecyclerView):这是横向模式下 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>

And now I want to call the GalleryFragment and make it replace just the <fragment> of id 'sideFragmentContainer' instead of the whole screen (instead of replacing the <fragment> of id fragmentContainer in the Activity's xml).现在我想调用 GalleryFragment 并让它只替换 id 'sideFragmentContainer' 的<fragment>而不是整个屏幕(而不是替换 Activity xml 中 id fragmentContainer<fragment> )。

I didn't find any explanations of how to handle multiple NavHosts or <fragment> inside <fragment> .我没有找到如何处理多个NavHosts或任何解释<fragment><fragment>

So based on that, is it possible to use Navigation Architecture and display a Fragment inside another Fragment?那么基于此,是否可以使用导航架构并在另一个 Fragment 中显示一个 Fragment? Do I need multiple NavHosts for that, or is there another way?为此我需要多个 NavHost,还是有其他方法?

As suggested by jsmyth886 , this blog post pointed me to the right direction.正如jsmyth886所建议的, 这篇博文为我指明了正确的方向。 The trick was to findFragmentById() to get the NavHost directly from the fragment container (in this case, the one sharing the screen with the rest of the Master Detail screen).诀窍是findFragmentById()直接从片段容器中获取NavHost (在这种情况下,与主细节屏幕的其余部分共享屏幕的那个)。 This allowed me to access the correct NavController and navigate as expected.这使我能够访问正确的NavController并按预期进行导航。 It's important to create a second NavGraph too.创建第二个NavGraph也很重要。

So a quick step-by-step:所以一个快速的一步一步:

  • Create the main NavGraph to make all the usual navigation (how it would work without the Master Detail Flow);创建主NavGraph以进行所有常规导航(没有 Master Detail Flow 时如何工作);
  • Create a secondary NavGraph containing only the possible Destinations that the Master Detail fragment will access.创建一个NavGraph包含 Master Detail 片段将访问的可能Destinations的辅助NavGraph No Actions connecting then, just the Destinations .没有Actions连接,只有Destinations
  • In the main <fragment> container, set the attributes like that:在主<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"/>

  • The app:defaultNavHost="true" is important. app:defaultNavHost="true"很重要。 Then the Master Detail layout will look like that:然后 Master Detail 布局将如下所示:

    <?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>

  • Again, the attribute app:defaultNavGraph is important, set it to false here.同样,属性app:defaultNavGraph很重要,在这里将其设置为 false。
  • In the code part, you should have a boolean flag to verify if your app is running on a Tablet or not (the link provided in the beginning of the answer explains how to do it).在代码部分,您应该有一个布尔标志来验证您的应用程序是否在平板电脑上运行(答案开头提供的链接解释了如何操作)。 In my case, I have it as a MutableLiveData inside my ViewModel , like that I can observe it and change layouts accordingly.就我而言,我将它作为一个MutableLiveData放在我的ViewModel ,这样我就可以观察它并相应地更改布局。
  • If is not tablet (ie follows the normal navigation flow), simply call findNavController().navigate(R.id.your_action_id_from_detail_to_some_other_fragment) .如果不是平板电脑(即遵循正常导航流程),只需调用findNavController().navigate(R.id.your_action_id_from_detail_to_some_other_fragment) The main navigation will happen using the main NavController ;主导航将使用主NavController
  • If is tablet using the Master Detail Flow, you must find the correct NavHost and NavController by finding the <fragment> that contains it, like that:如果是使用 Master Detail Flow 的平板电脑,则必须通过查找包含它的<fragment>来找到正确的NavHostNavController ,如下所示:
val navHostFragment = childFragmentManager.findFragmentById(R.id. sideFragmentContainer) as NavHostFragment
  • And finally you can navigate to the Fragment that you want to appear dividing the screen with the rest of the Master Detail screen by calling navHostFragment.navController.navigate(R.id.id_of_the_destination) .最后,您可以通过调用navHostFragment.navController.navigate(R.id.id_of_the_destination)导航到要显示的 Fragment,将屏幕与主详细信息屏幕的其余部分navHostFragment.navController.navigate(R.id.id_of_the_destination) Notice that here we don't call Actions , we call the Destination directly.请注意,这里我们没有调用Actions ,而是直接调用Destination

That's it, simpler than what I thought.就是这样,比我想象的要简单。 Thank you Lara Martín for the blog post and jsmyth886 for pointing me to the right direction!感谢 Lara Martín 的博文和 jsmyth886 为我指出正确的方向!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM