如何处理多个NavHosts/NavControllers? [英] How to handle multiple NavHosts/NavControllers?
问题描述
在处理多个NavHost时遇到问题.这个问题与 FruitListFragment是我的NavGraph的startDestination,它处理来自服务器的水果列表.
FruitDetailFragment显示有关在FruitListFragment显示的列表中选择的水果的详细信息. 到目前为止,我们有活动-> ListFragment-> DetailFragment. 现在,我需要再添加一个片段,称为GalleryFragment.这是一个简单的片段,其中显示了许多所选水果的图片,并且在单击按钮时被FruitDetailFragment调用. 这里的问题是:在人像模式下,我只使用 我已经尝试过使用 这是我的FruitsActivity的xml: 这是横向模式下FruitListActivity的xml(在纵向模式下只有RecyclerView): 现在我要调用GalleryFragment并使其仅替换id为"sideFragmentContainer"的 我没有找到有关如何在 因此,基于此,是否可以使用导航体系结构并在另一个Fragment中显示一个Fragment?我是否需要多个NavHost,还是还有其他方法? 根据 jsmyth886 的建议,此博客文章为我指明了正确的方向.
技巧是 一个快速的分步操作:findNavController().navigate(...)
,就可以随意浏览片段.但是当我在横向模式下使用平板电脑时,我使用的是主详细信息流"在同一屏幕上显示列表"和详细信息". 此处有一个示例,我希望GalleryFragment能够替换FruitDetailFragment,与水果列表共享屏幕,但是到目前为止,我只能设法使其替换主导航"流程,占据整个屏幕,并将FruitListFragment发送到Back Stack.findNavController()
方法,但是无论从哪里调用它,我始终只能得到相同的NavController
,并且始终以相同的线性方式导航.
我尝试实现自己的NavHost,但是却收到错误消息找不到androidx.navigation.NavHost的类文件".<?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>
<?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>
<fragment>
而不是整个屏幕(而不是替换Activity的xml中id为fragmentContainer
的<fragment>
). /p>
<fragment>
中处理多个NavHost或<fragment>
的任何解释.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>
来找到正确的NavHost
和NavController
,
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 possibleDestinations
that the Master Detail fragment will access. NoActions
connecting then, just theDestinations
. - 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 myViewModel
, 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 mainNavController
; - If is tablet using the Master Detail Flow, you must find the correct
NavHost
andNavController
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 callActions
, we call theDestination
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屋!