
作为Android Jetpack组件之一的Navigation,专为处理页面跳转而生。它不但提供了实现上述操作的统一标准,而且还增添了很多安全、易用的特性,比如提供了传值时的类型安全保障、提供页面跳转的概览图等。
implementation "androidx.navigation:navigation-fragment-ktx:2.7.7"
implementation "androidx.navigation:navigation-ui-ktx:2.7.7"
页面导航原则
- 向上和返回按钮的逻辑:从 A 界面跳转到 B 界面时,点击向上或者返回按钮都会回到 A 界面,不同的是 向上按钮不允许用户退出当前 app,而返回按钮允许
- 导航堆栈的设计:当用户启动一个App时,首先显示的页面应与用户通过返回键退出程序前的最后一个页面一致
- 深层链接跳转的处理:在跳转到商品详情页后,若用户点击向上或返回按钮时,会发生什么呢?答案是返回发生跳转前的位置。当发生深层链接跳转时,App原先的返回堆栈会被替换。
页面导航文件
使用 Navigation 前我们需要添加页面导航配置文件。首先我们需要在 res 目录上右击,在弹出的菜单中依次选择 New → Android Resource File,将弹出New Resource File对话框,如上图所示。
创建好页面导航配置文件后,我们就可以在文件中定义跳转逻辑了。在配置文件中,我们放入我们创建好的 activity 、Fragment、DialogFragment,其中 activity 只能作为目的地来结束。我们以一个最常见的登录注册的流程为例,如下图所示:
可以看到我们在文件中定义了从 LoginFragment 登录界面跳转到 RegisterFragment 注册界面 或者 ResetPasswordFragment 重置密码界面的路径,同时也定义了从这些界面跳回到登录界面的路径。最后登录完成后,就跳转到 MainActivity 主界面这个目的地。
使用 Navigation 导航
配置好页面导航文件后,我们就可以在代码中实现跳转逻辑了。首先在宿主 Activity 的 xml 文件中加入如下代码。 需要注意,宿主 Activity 不能是 ComponentActivity,不然会导致应用 Crash
<fragment
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/navigation_demo"
app:defaultNavHost="true"
/>
加好上面的 xml 后启动你的 app,就可以看到 LoginFragment 的界面了。为什么 app 会默认启动你的 LoginFragment 的界面呢?这是因为你在导航配置文件中定义了 startDestination 为 loginFragment 。导航配置文件的代码如下:
<?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"
android:id="@+id/navigation_demo"
// 这里定义了起始的 Fragment
app:startDestination="@id/loginFragment">
<fragment
android:id="@+id/loginFragment"
android:name="com.example.testgradleapp.fragment.LoginFragment"
android:label="LoginFragment" >
<action
android:id="@+id/action_loginFragment_to_registerFragment"
app:destination="@id/registerFragment" />
...
</fragment>
...
</navigation>
如果你想跳转到其他界面,只需要使用 findNavController().navigate 方法即可跳转,代码示例如下:
// 在 Activity 中跳转
findNavController(R.id.my_nav_host_fragment).navigate(R.id.loginFragment)
// 在 Fragment 中跳转
findNavController().navigate(R.id.registerFragment)
// 在 View 中跳转
findNavController().navigate(R.id.registerFragment)
navigate 方法可以传 fragment 标签的id,如 loginFragment;也可以传 action 标签的id,如 action_loginFragment_to_registerFragment。
如果你想回退到上一个界面,可以使用 popBackStack 和 navigateUp 方法。这两个方法都可以回退到上一个界面,区别是navigateUp 方法不允许用户退出当前 app,而popBackStack允许。同时 popBackStack 也支持回退到指定的界面,代码示例如下:
// 回退到上一个界面
findNavController().popBackStack()
// 回退到指定的界面
// 第一个参数是目的id,这里不能使用 action id;第二个参数是值是否需要把目的地也回退
findNavController().popBackStack(R.id.registerFragment, false)
如何传递参数
在 Navigation 组件中有两种方式在不同界面传递参数,第一种是 Bundle,另一种是 Safe Args。需要注意,这里讲的参数传递是针对比较小的数据,如果是大量的数据传递,Google 推荐的是使用共享的 ViewModel 来获取,而不是上面的两种方式。
使用 Bundle
使用 Bundle 传递参数非常简单,只需要将组装好的 Bundle 对象传入 navigate 方法就可以了。
val bundle = bundleOf("username" to username)
findNavController().navigate(R.id.loginFragment, bundle)
然后我们就可以在对应的 Fragment 中获取到对应的参数了。
val username = arguments?.getString("username")
使用 Safe Args
Safe Args 是 Gradle 的插件。这个插件的作用是生成对象和构建器类,用来在目的地之间实现类型安全(非可空对象)的导航。要使用这个插件,我们需要先进行一些配置,代码如下所示:
// 在项目的 build.gradle 中添加如下代码
buildscript {
repositories {
google()
}
dependencies {
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.7.7"
}
}
// 在 app 模块下的 build.gradle 中加入如下插件
plugins {
id 'androidx.navigation.safeargs.kotlin'
}
完成上面的步骤后,我们就可以使用 Safe Args 了。相对于 Bundle,Safe Args 的使用就比较麻烦了。第一步,我们需要先在导航文件中定义参数,代码示例如下:
<fragment
android:id="@+id/registerFragment"
android:name="com.example.fragment.RegisterFragment"
android:label="RegisterFragment" >
<argument
android:name="username"
app:argType="string" />
</fragment>
如果你需要从 LoginFragment 跳转到 RegisterFragment 界面,并携带上参数。你就需要在 LoginFragment 获取到 Gradle 生成的 LoginFragmentDirections 来获取 NavDirections, 最后通过 NavDirections 对象传递数据,代码示例如下:
// 在 LoginFragment 中传递数据
val action = LoginFragmentDirections.actionLoginFragmentToRegisterFragment("姓名")
findNavController().navigate(action)
// 在 RegisterFragment 中接受数据
val safeArgs: RegisterFragmentArgs by navArgs()
safeArgs.username
deeplink
深层链接(deeplink)一般用于用户点击某个链接打开对应的界面。在 Navigation 中,使用 deeplink 只需要在导航文件中定义 deepLink 标签就可以了。如果链接上需要带上参数,则需要创建 argument 标签,代码如下所示:
<argument android:name="userName" android:defaultValue="name"/>
<deepLink app:uri="www.example.com/user/{userName}" />
对于 deeplink 传递过来的值,则可以通过 Fragment 的 arguments 来获取,代码示例如下:
val name = arguments?.getString("userName") ?: ""