导航图可以包含以下任何组合
这种灵活性使您能够将较小的导航图组合在一起以形成应用的完整导航图,即使这些较小的导航图由单独的 模块 提供。
在本主题的示例中,每个 功能模块 都专注于一个 功能,并提供一个封装实现该功能所需的所有目标的单个导航图。在生产应用中,您可能在较低级别有许多子模块,这些子模块是此高级功能模块的实现细节。这些功能模块中的每一个都直接或间接地包含在您的 app
模块 中。本文档中使用的示例 多模块应用 具有以下结构
每个功能模块都是一个自包含的单元,具有自己的导航图和目标。app
模块依赖于每个模块,将其作为实现细节添加到其 build.gradle
文件中,如下所示
Groovy
dependencies { ... implementation project(":feature:home") implementation project(":feature:favorites") implementation project(":feature:settings")
Kotlin
dependencies { ... implementation(project(":feature:home")) implementation(project(":feature:favorites")) implementation(project(":feature:settings"))
app
模块的作用
app
模块负责提供应用的完整图,并将 NavHost
添加到您的 UI 中。在 app
模块的导航图中,您可以使用 <include>
来引用库图。虽然使用 <include>
在功能上与使用嵌套图相同,但 <include>
支持来自其他项目模块或库项目的图,如下例所示
<?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/home_nav_graph">
<include app:graph="@navigation/home_navigation" />
<include app:graph="@navigation/favorites_navigation" />
<include app:graph="@navigation/settings_navigation" />
</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"
app:startDestination="@id/home_nav_graph">
<include app:graph="@navigation/home_navigation" />
<include app:graph="@navigation/favorites_navigation" />
<include app:graph="@navigation/settings_navigation" />
<fragment
android:id="@+id/random_fragment"
android:name="com.example.android.RandomFragment"
android:label="@string/fragment_random" >
<!-- Launch into Settings Navigation Graph -->
<action
android:id="@+id/action_random_fragment_to_settings_nav_graph"
app:destination="@id/settings_nav_graph" />
</fragment>
</navigation>
当多个特性模块需要引用一组通用的目标(例如登录图)时,您**不应**将这些通用目标包含到每个特性模块的导航图中。相反,将这些通用目标添加到您的 app
模块的导航图中。然后,每个特性模块都可以 跨特性模块导航 以导航到这些通用目标。
在前面的示例中,操作指定了 @id/settings_nav_graph
的导航目标。此 ID 指的是在包含的图 @navigation/settings_navigation
中定义的目标。
应用模块中的顶级导航
导航组件包含一个 NavigationUI
类。此类包含管理与应用顶部应用栏、导航抽屉和底部导航的导航的静态方法。如果应用的顶级目标由特性模块提供的 UI 元素组成,则 app
模块是放置顶级导航和 UI 元素的自然位置。由于应用模块依赖于协作的特性模块,因此所有目标都可从应用模块中定义的代码访问。这意味着,如果项目的 ID 与目标的 ID 匹配,则可以使用 NavigationUI
将 目标绑定到菜单项。
在图 2 中,示例 app
模块在其主活动中定义了一个 BottomNavigationView
。菜单中的菜单项 ID 与库图的导航图 ID 匹配
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@id/home_nav_graph"
android:icon="@drawable/ic_home"
android:title="Home"
app:showAsAction="ifRoom"/>
<item
android:id="@id/favorites_nav_graph"
android:icon="@drawable/ic_favorite"
android:title="Favorites"
app:showAsAction="ifRoom"/>
<item
android:id="@id/settings_nav_graph"
android:icon="@drawable/ic_settings"
android:title="Settings"
app:showAsAction="ifRoom" />
</menu>
要让 NavigationUI
处理 底部导航,请从主活动类中的 onCreate()
调用 setupWithNavController()
,如下例所示
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment val navController = navHostFragment.navController findViewById<BottomNavigationView>(R.id.bottom_nav) .setupWithNavController(navController) }
Java
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); NavHostFragment navHostFragment = (NavHostFragment) supportFragmentManager.findFragmentById(R.id.nav_host_fragment); NavController navController = navHostFragment.getNavController(); BottomNavigationView bottomNav = findViewById(R.id.bottom_nav); NavigationUI.setupWithNavController(bottomNav, navController); }
有了这段代码,当用户点击底部导航项时,NavigationUI
会导航到相应的库图。
请记住,让应用模块硬依赖于特性模块导航图中深度嵌套的特定目标通常是不好的做法。在大多数情况下,您希望应用模块只了解任何嵌入式或包含的导航图的入口点(这同样适用于特性模块之外的情况)。如果您需要链接到库导航图中深层嵌套的目标,首选的方法是使用 深层链接。深层链接也是库导航到另一个库导航图中目标的唯一方法。
跨特性模块导航
在编译时,独立的特性模块无法相互查看,因此您无法使用 ID 导航到其他模块中的目标。相反,使用深层链接 直接导航到与 隐式深层链接 关联的目标。
继续前面的示例,假设您需要从 :feature:home
模块中的按钮导航到嵌套在 :feature:settings
模块中的目标。您可以通过向设置导航图中的目标添加深层链接来实现,如下所示
<?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/settings_nav_graph"
app:startDestination="@id/settings_fragment_one">
...
<fragment
android:id="@+id/settings_fragment_two"
android:name="com.example.google.login.SettingsFragmentTwo"
android:label="@string/settings_fragment_two" >
<deepLink
app:uri="android-app://example.google.app/settings_fragment_two" />
</fragment>
</navigation>
然后,将以下代码添加到主页片段中按钮的 onClickListener
中
Kotlin
button.setOnClickListener { val request = NavDeepLinkRequest.Builder .fromUri("android-app://example.google.app/settings_fragment_two".toUri()) .build() findNavController().navigate(request) }
Java
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { NavDeepLinkRequest request = NavDeepLinkRequest.Builder .fromUri(Uri.parse("android-app://example.google.app/settings_fragment_two")) .build(); NavHostFragment.findNavController(this).navigate(request); } });
与使用操作或目标 ID 导航不同,您可以导航到任何图中的任何 URI,即使跨模块也是如此。
使用 URI 导航时,不会重置返回堆栈。此行为与 显式深层链接导航 不同,在显式深层链接导航中,导航时会替换返回堆栈。