我有一个案例,希望通过拱形导航组件来实现它。例如,我有 2 个导航图(主图和嵌套图)。我可以从嵌套调用主图吗?如何调用?
5 回答
关键是要获得在NavController
正确图表中导航的权利。我们以这个场景为例:
MainActivity
|- MainNavHost
|- NavBarFragment
| |- NestedNavHost
| | |-NestedContentFragment1
| | |-NestedContentFragment2
| |
| |- BottomNavigationView
|
|- LoginFragment
主图和嵌套图在单独的 xml 文件中:据我了解,这是必需的,因为导航针对不同的布局区域,因此它们需要两个不同NavHost
的 s。每个Navhost
都需要通过 id 引用其图形,这要求它们位于不同的资源文件中。
关键是要在特定的图表中导航,我们必须获得对正确图表所有者的引用:为此,在调用 时Navigation.findNavController(view)
,view
参数至关重要。
医生说
NavHostFragments 在其视图子树的根目录中注册其导航控制器,以便任何后代都可以通过 Navigation 辅助类的方法获取控制器实例
例如,如果NavBarFragment
我们在里面写
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
navController = Navigation.findNavController(view)
}
这view
是(即嵌套的)的父级,而不是后代,这意味着它将在树的上游搜索并返回's 。NestedNavHost
NavHostFragment
findNavController
MainNavHost
NavController
相反,如果我们写
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val nestedNavHostFragment = childFragmentManager.findFragmentById(R.id.nestedNavHostFragment) as? NavHostFragment
navController = nestedNavHostFragment?.navController
}
nestedNavHostFragment
布局中的 id 在哪里FragmentContainerView
,我们得到了正确的NestedNavHost
. 注意使用childFragmentManager
,不是parentFragmentManager
。
如果您仍在使用已弃用的 xml<fragment>
标签,您可以编写
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val fragmentContainer = view.findViewById<View>(R.id.nestedNavHostFragment)
navController = Navigation.findNavController(fragmentContainer)
}
标签nestedNavHostFragment
的id在哪里。<fragment>
我们现在得到了正确的引用NestedNavHost
,因为我们传递给的视图findNavController
属于NestedNavHost
的子树。
同样,如果您需要从 a 中获取对 main 的引用,NavController
我们NestedContentFragment
可以这样做:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// we can get the innermost NavController using this view,
// because we are inside its subtree:
nestedNavController = Navigation.findNavController(view)
// we can find the outer NavController passing the owning Activity
// and the id of a view associated to that NavController,
// for example the NavHostFragment id:
mainNavController = Navigation.findNavController(activity!!, R.id.mainNavHostFragment)
}
实际上,您可以使用全局操作从嵌套导航图目标导航到主导航图目标。
创建从嵌套导航图到主导航图中所需目的地的全局操作(在下图中突出显示)
例子:
<navigation android:id="@+id/main_nav_graph"
... >
<fragment android:id="@+id/fragStart" .../>
<fragment .../>
<fragment .../>
<navigation android:id="@+id/nested_nav_graph">
...
<!-- Global Action -->
<action
android:id="@+id/action_global_start"
app:destination="@id/fragStart" />
</navigation>
</navigation>
要导航到主图目标,请使用
findNavController().navigate(R.id.action_global_start)
我使用提供的信息 devrocca 创建了一个答案。这是从头开始的完整答案,如果有人需要,我没有跳过任何内容。
这是导航的主要片段。相机是没有任何嵌套图的直接目的地,仪表板有它自己的嵌套图,但它被添加到相同的 backstack 相机片段中。Home 有 3 个片段和它自己的导航主机
MainActivity
|- MainNavHost
|- HomeNavHostFragment
| |- NestedNavHost
| |-HomeFragment1
| |-HomeFragment2
| |-HomeFragment3
|
|- nav_graph_dashboard
|
|- CameraFragment
这是导航文件
主导航nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/main_dest">
<!-- MainFragment-->
<fragment
android:id="@+id/main_dest"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.MainFragment"
android:label="MainFragment"
tools:layout="@layout/fragment_main">
<!-- Camera -->
<action
android:id="@+id/action_main_dest_to_cameraFragment"
app:destination="@id/cameraFragment" />
<!-- Home NavGraph -->
<action
android:id="@+id/action_main_dest_to_nav_graph_home"
app:destination="@id/nav_graph_home" />
<!-- Dashboard NavGraph-->
<action
android:id="@+id/action_main_dest_to_nav_graph_dashboard"
app:destination="@id/nav_graph_dashboard" />
</fragment>
<!-- Camera -->
<fragment
android:id="@+id/cameraFragment"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.CameraFragment"
android:label="CameraFragment" />
<!-- Home-->
<include app:graph="@navigation/nav_graph_home" />
<!-- Dashboard-->
<include app:graph="@navigation/nav_graph_dashboard" />
<!-- Global Action Start -->
<action
android:id="@+id/action_global_start"
app:destination="@id/main_dest"
app:popUpTo="@id/main_dest"
app:popUpToInclusive="true" />
</navigation>
仪表板嵌套导航图
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph_dashboard"
app:startDestination="@id/dashboard_dest">
<fragment
android:id="@+id/dashboard_dest"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.DashboardFragment1"
android:label="DashboardFragment1"
tools:layout="@layout/fragment_dashboard1">
<action
android:id="@+id/action_dashboardFragment1_to_dashboardFragment2"
app:destination="@id/dashboardFragment2" />
</fragment>
<fragment
android:id="@+id/dashboardFragment2"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.DashboardFragment2"
android:label="DashboardFragment2"
tools:layout="@layout/fragment_dashboard2">
</fragment>
</navigation>
并使用自己的 NavHost nav_graph_home 嵌套导航图
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph_home"
app:startDestination="@id/home_dest">
<fragment
android:id="@+id/home_dest"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.HomeNavHostFragment"
android:label="HomeHost"
tools:layout="@layout/fragment_home_navhost" />
<fragment
android:id="@+id/homeFragment1"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.HomeFragment1"
android:label="HomeFragment1"
tools:layout="@layout/fragment_home1">
<action
android:id="@+id/action_homeFragment1_to_homeFragment2"
app:destination="@id/homeFragment2" />
</fragment>
<fragment
android:id="@+id/homeFragment2"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.HomeFragment2"
android:label="HomeFragment2"
tools:layout="@layout/fragment_home2">
<action
android:id="@+id/action_homeFragment2_to_homeFragment3"
app:destination="@id/homeFragment3" />
</fragment>
<fragment
android:id="@+id/homeFragment3"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.HomeFragment3"
android:label="HomeFragment3"
tools:layout="@layout/fragment_home3" />
</navigation>
布局,我只添加必要的布局,其他是带有按钮的简单布局,我为示例项目添加链接,其中包含其他导航组件示例。
MainActivity
<?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">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="@style/ThemeOverlay.AppCompat.ActionBar" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/main_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>
主片段,这是图像中显示的第一个片段,用作主导航的开始
<?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">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/parentLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btnDestCam"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Destination Camera"
app:layout_constraintBottom_toTopOf="@+id/btnNavGraphHome"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintLeft_toRightOf="parent"
app:layout_constraintRight_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btnNavGraphHome"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nested NavHost Graph Home"
app:layout_constraintBottom_toTopOf="@+id/btnNavGraphDashboard"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintLeft_toRightOf="parent"
app:layout_constraintRight_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btnDestCam" />
<Button
android:id="@+id/btnNavGraphDashboard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nested Graph Dashboard"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintLeft_toRightOf="parent"
app:layout_constraintRight_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btnNavGraphHome" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
包含内部NavHostFragment
导航的布局
<?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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nested_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:defaultNavHost="false"
app:navGraph="@navigation/nav_graph_home" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
MainActivity 用于检查主导航返回堆栈,这里重要的是
supportFragmentManager 后栈在您导航时不会更新 childFragmentManager 即使对于主导航,即使您只有一个
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Get NavHostFragment
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.main_nav_host_fragment)
// ChildFragmentManager of NavHostFragment
val navHostChildFragmentManager = navHostFragment?.childFragmentManager
navHostChildFragmentManager?.addOnBackStackChangedListener {
val backStackEntryCount = navHostChildFragmentManager.backStackEntryCount
val fragments = navHostChildFragmentManager.fragments
}
}
}
包含主页导航主机的片段
class HomeNavHostFragment : BaseDataBindingFragment<FragmentHomeNavhostBinding>() {
override fun getLayoutRes(): Int = R.layout.fragment_home_navhost
private var navController: NavController? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val nestedNavHostFragment =
childFragmentManager.findFragmentById(R.id.nested_nav_host_fragment) as? NavHostFragment
navController = nestedNavHostFragment?.navController
navController?.navigate(R.id.homeFragment1)
listenBackStack()
}
private fun listenBackStack() {
// Get NavHostFragment
val navHostFragment =
childFragmentManager.findFragmentById(R.id.nested_nav_host_fragment)
// ChildFragmentManager of the current NavHostFragment
val navHostChildFragmentManager = navHostFragment?.childFragmentManager
navHostChildFragmentManager?.addOnBackStackChangedListener {
val backStackEntryCount = navHostChildFragmentManager!!.backStackEntryCount
val fragments = navHostChildFragmentManager!!.fragments
Toast.makeText(
requireContext(),
"HomeNavHost backStackEntryCount: $backStackEntryCount, fragments: $fragments",
Toast.LENGTH_SHORT
).show()
}
val callback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
val backStackEntryCount = navHostChildFragmentManager!!.backStackEntryCount
Toast.makeText(
requireContext(),
"HomeNavHost backStackEntryCount: $backStackEntryCount",
Toast.LENGTH_SHORT
).show()
if (backStackEntryCount == 1) {
OnBackPressedCallback@ this.isEnabled = false
requireActivity().onBackPressed()
} else {
navController?.navigateUp()
}
}
}
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
}
}
有一件事我不知道它是否在图形或代码中使用嵌套的 NavHostFragment 进行了改进
如果您设置 nav_graph_home 的起始目的地HomeFragment1
而不是HomeNavHostFragment
它作为仪表板工作,它会忽略嵌套的 NavHost 并添加到片段的主后台堆栈中。
由于您在内部 NavHostFragment findNavController() 在任何主片段中返回内部
class HomeFragment3 : BaseDataBindingFragment<FragmentHome3Binding>() {
override fun getLayoutRes(): Int = R.layout.fragment_home3
private var count = 0
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
dataBinding.btnIncrease.setOnClickListener {
dataBinding.tvTitle.text = "Count: ${count++}"
}
val mainNavController =
Navigation.findNavController(requireActivity(), R.id.main_nav_host_fragment)
dataBinding.btnGoToStart.setOnClickListener {
// Using destination belong to main_nav_host with nested navHost causes app to crash
// findNavController().navigate(R.id.action_global_start)
mainNavController.navigate(R.id.action_global_start)/**/
}
}
}
您也可以使用全局操作,但这不是必需的,因为如果您不使用OnBackPressed
.
如果您有兴趣,请链接以获取完整示例和其他导航组件示例。
实际上正在工作,使用
val host: NavHostFragment? = (childFragmentManager.findFragmentById(R.id.main_app_fragment_container) as NavHostFragment?)
我可以从主要片段导航
我找到了解决内部 NavController 被覆盖问题的临时解决方案。您可以使用自定义 NavHostFragment,它为您提供所需的 navController。我的代码:
<androidx.fragment.app.FragmentContainerView
...
android:name="MyNavHostFragment"
app:defaultNavHost="false"
app:navGraph="@navigation/inner_nav">
...
</androidx.fragment.app.FragmentContainerView>
...
class MyNavHostFragment: NavHostFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
MainFragment.innerNavController = navController
}
}
...
class MainFragment : Fragment() {
companion object{
lateinit var innerNavController: NavController
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val bottomNavigationView =
view!!.findViewById<BottomNavigationView>(R.id.bottom_navigation_view)
bottomNavigationView.setupWithNavController(innerNavController)
}
}