导航到目的地是使用 NavController 完成的,它是一个在
NavHost
中管理应用导航的对象。每个NavHost
均有自己的相应NavController
。
示例项目代码: 基于 Navigation + Hilt + ViewBinding + ViewModel + LiveData
MVVM-Project-Navigationhttps://github.com/imifeng/MVVM-Project-Navigation
(欢迎讨论)
NavController
提供了几种导航到目的地的不同方式:
Kotlin:
检索 NavController
之后,您可以调用 navigate()
的某个重载,以在各个目的地之间导航。每个重载均支持多种导航场景,如以下部分所述:
(先附上示例导航图-1)
<?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/a">
<fragment android:id="@+id/a"
android:name="com.example.myapplication.FragmentA"
android:label="a"
tools:layout="@layout/a">
<action android:id="@+id/action_a_to_b"
app:destination="@id/b"/>
</fragment>
<fragment android:id="@+id/b"
android:name="com.example.myapplication.FragmentB"
android:label="b"
tools:layout="@layout/b">
...
</fragment>
</navigation>
1. 使用 ID 导航
navigate(int) 接受操作或目的地的资源 ID 作为参数。以下代码段展示了如何导航到 FragmentB
:
#方法1: 传递目的地的资源 ID
button.setOnClickListener { view ->
view.findNavController().navigate(R.id.b)
}
#方法2: 传递操作的 ID
扩充该导航图-1时,系统会解析这些操作,同时使用图中定义的配置生成相应的
NavAction
对象。例如,action_a_to_b
定义为从目的地 a 到目的地 b 的导航。该操作还可以包含动画以及popTo
行为,该行为会从返回堆栈中移除所有目的地。所有这些设置都会以NavOptions
形式捕获并连接到NavAction
。
如遵循此 NavAction
,请使用 NavController.navigate()
(传递操作的 ID):
button.setOnClickListener { view ->
view.findNavController().navigate(R.id.action_a_to_b)
}
2. 使用 DeepLinkRequest 导航
也可以使用 navigate(NavDeepLinkRequest) 直接导航到 隐式深层链接目的地 ,如下例所示:
<?xml version="1.0" encoding="utf-8"?>
<navigation ... >
<fragment
android:id="@+id/b"
android:name="com.example.myapplication.FragmentB"
android:label="b"
tools:layout="@layout/b">
<deepLink
android:id="@+id/deepLink"
app:uri="android-app://navigation/fragmentB" />
</fragment>
</navigation>
需要在FragmentB下添加 Deep links,如下导航到 FragmentB
:
val request = NavDeepLinkRequest.Builder
.fromUri("android-app://navigation/fragmentB".toUri())
.build()
findNavController().navigate(request)
为使
NavDeepLinkRequest
正确匹配隐式深层链接目的地,URI、操作和 MIME 类型必须全部与目的地中的NavDeepLink
匹配。URI 必须与模式匹配,操作必须是完全匹配,并且 MIME 类型必须相关(例如“image/jpg”与“image/*”匹配)。
⚠️ 为方便起见,您也可以使用 navigate(Uri) ,它会将 Uri
封装在 DeepLinkRequest
中。
3. 导航和返回堆栈
Android 会维护一个 返回堆栈,其中包含您之前访问过的目的地。当用户打开您的应用时,应用的第一个目的地就放置在堆栈中。每次调用 navigate() 方法都会将另一目的地放置到堆栈的顶部。
点按向上或返回会分别调用 NavController.navigateUp() 和 NavController.popBackStack() 方法,用于移除(或弹出)堆栈顶部的目的地。
当我们要返回上一个视图时,直接调用就行:
button.setOnClickListener { view ->
view.findNavController().popBackStack()
}
4. popUpTo 和 popUpToInclusive
使用操作进行导航时,您可以选择从返回堆栈上弹出其他目的地。如需在从一个目的地导航到另一个目的地时弹出目的地,请在关联的
<action>
元素中添加app:popUpTo
属性。app:popUpTo
会告知 Navigation 库在调用navigate()
的过程中从返回堆栈上弹出一些目的地。属性值是应保留在堆栈中的最新目的地的 ID。您还可以添加
app:popUpToInclusive="true"
,以表明在app:popUpTo
中指定的目的地也应从返回堆栈中移除。
简单的来说:如果您的应用具有初始登录流程,那么在用户登录后,您应将所有与登录相关的目的地从返回堆栈上弹出,这样返回按钮就不会将用户带回登录流程。
popUpTo 示例:
# 1. 假设您的应用包含三个目的地:A、B 和 C,以及从 A 到 B、从 B 到 C 再从 C 返回到 A 的操作。对应的导航图如图 1 所示:
每执行一次导航操作,都会将一个目的地添加到返回堆栈。如果您要通过此流程反复导航,则您的返回堆栈会包含多个集合,其中包含每个目的地(例如 A、B、C、A、B、C、A 等)。为了避免这种重复,您可以在从目的地 C 到目的地 A 的操作中指定 app:popUpTo
和 app:popUpToInclusive
,如下例所示:
<fragment
android:id="@+id/c"
android:name="com.example.myapplication.C"
android:label="fragment_c"
tools:layout="@layout/fragment_c">
<action
android:id="@+id/action_c_to_a"
app:destination="@id/a"
app:popUpTo="@+id/a"
app:popUpToInclusive="true"/>
</fragment>
在到达目的地 C 之后,返回堆栈包含每个目的地(A、B 和 C)的一个实例。当返回到目的地 A 时,我们也 popUpTo
A,也就是说我们会在导航过程中从堆栈中移除 B 和 C。利用 app:popUpToInclusive="true"
,我们还会将第一个 A 从堆栈上弹出,从而有效地清除它。请注意,如果您不使用 app:popUpToInclusive
,则返回堆栈会包含目的地 A 的两个实例。
# 2. 假设您的应用包含四个目的地:A、B 、C和D,以及从 A 到 B、从 B 到 C 再从 C 到 D 且要弹出(关闭) B 、C 的操作:
<fragment
android:id="@+id/c"
android:name="com.example.myapplication.C"
android:label="fragment_c"
tools:layout="@layout/fragment_c">
<!-- 导航从C到D,并且把堆栈 A 上面的目的地 B和C 都弹出去(删掉),这里不包括A本身 -->
<action
android:id="@+id/action_c_to_d"
app:destination="@id/d"
app:popUpTo="@+id/a"
app:popUpToInclusive="false"/>
// 或
<!-- 导航从C到D,并且把堆栈 B 上面的目的地 C 以及B本身 弹出去(删掉) -->
<action
android:id="@+id/action_c_to_d"
app:destination="@id/d"
app:popUpTo="@+id/b"
app:popUpToInclusive="true"/>
</fragment>
在到达目的地 D 之后,返回堆栈包含每个目的地(A、B 和 C)的一个实例。当到目的地 D 时,我们也 popUpTo
A,也就是说我们会在导航过程中从堆栈中移除 B 和 C。利用 app:popUpToInclusive="false"
,我们不会将第一个 A 从堆栈上弹出,从而有效地保留它,以便在目的地D执行popBackStack()可以直接回到A。