如何处理多个NavHosts/NavControllers? [英] How to handle multiple NavHosts/NavControllers?

查看:101
本文介绍了如何处理多个NavHosts/NavControllers?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在处理多个NavHost时遇到问题.这个问题与

FruitListFragment是我的NavGraph的startDestination,它处理来自服务器的水果列表. FruitDetailFragment显示有关在FruitListFragment显示的列表中选择的水果的详细信息.

到目前为止,我们有活动-> ListFragment-> DetailFragment.

现在,我需要再添加一个片段,称为GalleryFragment.这是一个简单的片段,其中显示了许多所选水果的图片,并且在单击按钮时被FruitDetailFragment调用.

这里的问题是:在人像模式下,我只使用findNavController().navigate(...),就可以随意浏览片段.但是当我在横向模式下使用平板电脑时,我使用的是主详细信息流"在同一屏幕上显示列表"和详细信息". 此处有一个示例,我希望GalleryFragment能够替换FruitDetailFragment,与水果列表共享屏幕,但是到目前为止,我只能设法使其替换主导航"流程,占据整个屏幕,并将FruitListFragment发送到Back Stack.

我已经尝试过使用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>). /p>

我没有找到有关如何在<fragment>中处理多个NavHost或<fragment>的任何解释.

因此,基于此,是否可以使用导航体系结构并在另一个Fragment中显示一个Fragment?我是否需要多个NavHost,还是还有其他方法?

解决方案

根据 jsmyth886 的建议,此博客文章为我指明了正确的方向. 技巧是findFragmentById()直接从片段容器中获取NavHost(在这种情况下,是与主详细信息"屏幕的其余部分共享屏幕).这使我可以访问正确的NavController并按预期进行导航. 同样重要的是还要创建第二个NavGraph.

一个快速的分步操作:

  • 创建主要的NavGraph以进行所有常规导航(如果没有主细节流",它将如何工作);
  • 创建一个辅助NavGraph,该辅助NavGraph仅包含主详细信息"片段将访问的可能的Destinations.则没有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.
  • 在代码部分,您应该有一个布尔标志,以验证您的应用程序是否在Tablet上运行(答案开头提供的链接说明了如何执行此操作).在我的情况下,我将它作为MutableLiveData放在我的ViewModel内部,这样我就可以观察它并相应地更改布局.
  • 如果不是平板电脑(即遵循正常的导航流程),只需致电findNavController().navigate(R.id.your_action_id_from_detail_to_some_other_fragment).主导航将使用主NavController;
  • 如果平板电脑使用的是Master Detail Flow,则必须通过找到包含它的<fragment>来找到正确的NavHostNavController

val navHostFragment = childFragmentManager.findFragmentById(R.id. sideFragmentContainer) as NavHostFragment

  • 最后,您可以通过调用navHostFragment.navController.navigate(R.id.id_of_the_destination)导航到要显示的片段,以将屏幕与主详细信息"屏幕的其余部分分开.请注意,此处我们不调用Actions,而是直接调用Destination.

就是这样,比我想的要简单.感谢LaraMartín的博客文章和jsmyth886为我指出正确的方向!

I'm having a problem when dealing with multiple NavHosts. 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. Android Developers Documentation doesn't help and searching through the web shows absolutely nothing that could possibly help.

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:

<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. The FruitDetailFragment shows details about the Fruit selected in the list displayed by FruitListFragment.

So far we have Activity -> ListFragment -> DetailFragment.

Now I need to add one more Fragment, called GalleryFragment. It's a simple fragment that displays many pictures of the Fruit selected and it's called by FruitDetailFragment when clicking a button.

The problem here is: In Portrait mode I simply use findNavController().navigate(...) and I navigate through the Fragments like I want. 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.

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. I tried to implement my own NavHost, but I get and error "class file for androidx.navigation.NavHost not found".

This is the xml of my FruitsActivity:

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

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

I didn't find any explanations of how to handle multiple NavHosts or <fragment> inside <fragment>.

So based on that, is it possible to use Navigation Architecture and display a Fragment inside another Fragment? Do I need multiple NavHosts for that, or is there another way?

解决方案

As suggested by jsmyth886, this blog post pointed me to the right direction. 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). This allowed me to access the correct NavController and navigate as expected. It's important to create a second NavGraph too.

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);
  • Create a secondary NavGraph containing only the possible Destinations that the Master Detail fragment will access. No Actions connecting then, just the Destinations.
  • In the main <fragment> container, set the attributes like that:


    <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. Then the Master Detail layout will look like that:


    <?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.
  • 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.
  • If is not tablet (i.e. follows the normal navigation flow), simply call findNavController().navigate(R.id.your_action_id_from_detail_to_some_other_fragment). The main navigation will happen using the main 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:

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). Notice that here we don't call Actions, we call the Destination directly.

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!

这篇关于如何处理多个NavHosts/NavControllers?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆