导航组件 官方文档:https://developer.android.google.cn/guide/navigation?hl=zh_cn
一、设置环境
在应用的build.gradle文件中添加以下依赖
dependencies {
def nav_version = "2.3.0-alpha01"
// Java language implementation
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
// Kotlin
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
// Dynamic Feature Module Support
implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"
// Testing Navigation
androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"
}
注意:如果您要在 Android Studio 中使用 Navigation 组件,则必须使用 Android Studio 3.3 或更高版本。
二、使用
2.1 创建导航图
导航发生在应用中的各个目的地(即您的应用中用户可以导航到的任意位置)之间。这些目的地是通过操作连接的。
导航图是一种资源文件,其中包含您的所有目的地和操作。该图表会显示应用的所有导航路径。
- “目的地”是指应用中的不同内容区域。
- “操作”是指目的地之间的逻辑连接,表示用户可以采取的路径。
要向项目添加导航图,请执行以下操作:
- 在“Project”窗口中,右键点击 res 目录,然后依次选择 New > Android Resource File。此时系统会显示 New Resource File 对话框。
- 在 File name 字段中输入名称,例如“login_navigation”。
- 从 Resource type 下拉列表中选择 Navigation,然后点击 OK。
当您添加首个导航图时,Android Studio 会在 res
目录内创建一个 navigation
资源目录。该目录包含您的导航图资源文件(例如 nav_graph.xml
)。
2.2 Navigation Editor
添加图表后,Android Studio 会在 Navigation Editor 中打开该图表。在 Navigation Editor 中,您可以直观地修改导航图,或直接修改底层 XML。
- Destinations panel:列出了导航宿主和目前位于 Graph Editor 中的所有目的地。
- Graph Editor:包含导航图的视觉表示形式。您可以在 Design 视图和 Text 视图中的底层 XML 表示形式之间切换。
- Attributes:显示导航图中当前所选项的属性。
点击split 查看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"
android:id="@+id/login_navigation">
</navigation>
<navigation>
元素是导航图的根元素。当您向图表添加目的地和连接操作时,可以看到相应的 <destination>
和 <action>
元素在此处显示为子元素。如果您有嵌套图表,它们将显示为子 <navigation>
元素。
2.3 向Activity添加NavHost
导航宿主是 Navigation 组件的核心部分之一。导航宿主是一个空容器,用户在您的应用中导航时,目的地会在该容器中交换进出。
导航宿主必须派生于 NavHost。Navigation 组件的默认 NavHost 实现 (NavHostFragment) 负责处理 Fragment 目的地的交换。
注意:Navigation 组件旨在用于具有一个主 Activity 和多个 Fragment 目的地的应用。主 Activity 与导航图相关联,且包含一个负责根据需要交换目的地的 NavHostFragment
。在具有多个 Activity 目的地的应用中,每个 Activity 均拥有其自己的导航图。
通过 XML 添加 NavHostFragment
以下 XML 示例显示了作为应用主 Activity 一部分的 NavHostFragment
:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.LoginActivity">
<fragment
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/login_navigation"></fragment>
</androidx.constraintlayout.widget.ConstraintLayout>
请注意以下几点:
- android:name :包含 NavHost 实现的类名称,这是一个用于放置管理 destination 的空视图。
- app:navGraph :将 NavHostFragment 与导航图相关联。导航图会在此 NavHostFragment 中指定用户可以导航到的所有目的地。
- app:defaultNavHost="true" 属性确保您的 NavHostFragment 会拦截系统返回按钮。请注意,只能有一个默认 NavHost。如果同一布局(例如,双窗格布局)中有多个主机,请务必仅指定一个默认 NavHost。
2.4 向导航图添加destination
您可以从现有的 Fragment 或 Activity 创建目的地。您还可以使用 Navigation Editor 创建新目的地,或创建占位符以便稍后替换为 Fragment 或 Activity。
在本示例中,我们来创建一个新目的地。要使用 Navigation Editor 添加新目的地,请执行以下操作:
- 在 Navigation Editor 中,点击 New Destination 图标 ,然后点击 Create new destination(也可以从下拉列表里把已经创建的fragment直接添加为目的地)。
- 在随即显示的 New Android Component 对话框中,创建您的 Fragment。如需详细了解 Fragment,请参阅 Fragment 文档。
当您返回到 Navigation Editor 中时,会发现 Android Studio 已将此目的地添加到图表中。
2.5 destination详解
点击一个目的地以将其选中,并注意 Attributes 面板中显示的以下属性:
- Type 字段指示在您的源代码中,该目的地是作为 Fragment、Activity 还是其他自定义类实现的。
- Label 字段包含该目的地的 XML 布局文件的名称。
- ID 字段包含该目的地的 ID,它用于在代码中引用该目的地。
- Class 下拉列表显示与该目的地相关联的类的名称。您可以点击此下拉列表,将相关联的类更改为其他destination类型。
点击 Text 标签页可查看导航图的 XML 视图。XML 中同样包含该目的地的 id、name、label 和 layout 属性,如下所示:
<?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/login_navigation"
app:startDestination="@id/welcomeFragment">
<fragment
android:id="@+id/welcomeFragment"
android:name="com.gozap.jetpack.ui.fragment.WelcomeFragment"
tools:layout="@layout/fragment_welcome"
android:label="WelcomeFragment" />
<fragment
android:id="@+id/loginFragment"
android:name="com.gozap.jetpack.ui.fragment.LoginFragment"
tools:layout="@layout/fragment_login"
android:label="LoginFragment" />
<fragment
android:id="@+id/registerFragment"
android:name="com.gozap.jetpack.ui.fragment.RegisterFragment"
tools:layout="@layout/fragment_register"
android:label="RegisterFragment" />
</navigation>
2.6 将某个屏幕指定位startDestination
起始目的地是用户打开您的应用时看到的第一个屏幕,也是用户退出您的应用时看到的最后一个屏幕。Navigation Editor 使用房子图标 来表示起始目的地。
所有目的地就绪后,您便可以选择startDestination,实现方法如下:
1、<navigation ... app:startDestination="@id/welcomeFragment" > </navigation>
2、在 Design 标签页中,点击相应destination,使其突出显示,再点击导航的图标。
2.7 连接destination
操作是指目的地之间的逻辑连接。操作在导航图中以箭头表示。操作通常会将一个destination连接到另一个destination,不过您也可以创建全局操作,此类操作可让您从应用中的任意位置转到特定destination。
借助操作,您可以表示用户在您的应用中导航时可以采取的不同路径。请注意,要实际导航到各个目的地,您仍然需要编写代码来执行导航操作。如需了解详情,请参阅本主题后面的导航到目的地部分。
您可以使用 Navigation Editor 将两个目的地连接起来,具体操作步骤如下:
- 在 Design 标签页中,将鼠标悬停在您希望用户从中导航出来的目的地的右侧。该目的地右侧上方会显示一个圆圈,如图 4 所示。
- 点击您希望用户导航到的目的地,并将光标拖动到该目的地的上方,然后松开。这两个目的地之间生成的线条表示操作,如图 5 所示。
<?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/login_navigation"
app:startDestination="@id/welcomeFragment">
<fragment
android:id="@+id/welcomeFragment"
android:name="com.gozap.jetpack.ui.fragment.WelcomeFragment"
android:label="WelcomeFragment"
tools:layout="@layout/fragment_welcome">
<action
android:id="@+id/action_welcomeFragment_to_loginFragment"
app:destination="@id/loginFragment" />
<action
android:id="@+id/action_welcomeFragment_to_registerFragment"
app:destination="@id/registerFragment"
app:enterAnim="@anim/common_slide_in_right"
app:exitAnim="@anim/common_slide_out_left"
app:popEnterAnim="@anim/common_slide_in_left"
app:popExitAnim="@anim/common_slide_out_right" />
</fragment>
<fragment
android:id="@+id/loginFragment"
android:name="com.gozap.jetpack.ui.fragment.LoginFragment"
android:label="LoginFragment"
tools:layout="@layout/fragment_login" />
<fragment
android:id="@+id/registerFragment"
android:name="com.gozap.jetpack.ui.fragment.RegisterFragment"
android:label="RegisterFragment"
tools:layout="@layout/fragment_register" />
</navigation>
在导航图中,操作由 <action>
元素表示。操作至少应包含自己的 ID 和用户应转到的目的地的 ID。
三、 跳转与数据传递
3.1 导航到目的地
官方文档:https://developer.android.google.cn/guide/navigation/navigation-navigate?hl=zh_cn
导航到目的地是使用 NavController
完成的,后者是一个在 NavHost
中管理应用导航的对象。每个 NavHost
均有自己的相应 NavController
。您可以使用以下方法之一检索 NavController
:
Kotlin:
Java:
NavHostFragment.findNavController(Fragment)
Navigation.findNavController(Activity, @IdRes int viewId)
Navigation.findNavController(View)
3.1.1 使用 Safe Args 确保类型安全
要在目的地之间导航,建议使用 Safe Args Gradle 插件。该插件可以生成简单的对象和构建器类,这些类支持在目的地之间进行类型安全的导航和参数传递。
顶级build.gradle文件添加以下
buildscript {
repositories {
google()
}
dependencies {
def nav_version = "2.3.0-alpha01"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
}
您还必须应用build.gradle 添加以下两个可用插件之一。
java:
apply plugin: "androidx.navigation.safeargs"
kotlin
apply plugin: "androidx.navigation.safeargs.kotlin"
根据迁移到 AndroidX 文档,您的 gradle.properties
文件 中必须具有 android.useAndroidX=true
。
启用 Safe Args 后,生成的代码会为每个操作包含以下类型安全的类和方法,以及每个发送和接收目的地。
-
为生成操作的每一个目的地创建一个类。该类的名称是在源目的地的名称后面加上“Directions”。例如,如果源目的地是名为
WelcomeFragment
的 Fragment,则生成的类的名称为WelcomeFragmentDirections
。该类会为源目的地中定义的每个操作提供一个方法。
-
对于用于传递参数的每个操作,都会创建一个 inner 类,该类的名称根据操作的名称确定。例如,如果操作名称为
confirmationAction,
,则类名称为ConfirmationAction
。如果您的操作包含不带defaultValue
的参数,则您可以使用关联的 action 类来设置参数值。 -
为接收目的地创建一个类。该类的名称是在目的地的名称后面加上“Args”。例如,如果目的地 Fragment 的名称为
RegisterFragment,
,则生成的类的名称为RegisterFragmentArgs
。可以使用该类的fromBundle()
方法检索参数。
启用 Safe Args 后,该插件会生成代码,其中包含您定义的每个操作的类和方法。对于每个操作,Safe Args 还会为每个源目的地(生成相应操作的目的地)生成一个类。生成的类的名称为 "源目的地类的名称+Directions” 组成。例如,如果目的地的名称为 WelcomeFragment
,则生成的类的名称为 WelcomeFragmentDirections
。生成的类为源目的地中定义的每个操作提供了一个静态方法。该方法会将任何定义的操作参数作为参数,并返回可传递到 navigate()
的 NavDirections
对象。
例如,假设我们的导航图包含一个操作,该操作将源目的地 WelcomeFragment
和接收目的地 LoginFragment
连接起来。
Safe Args 会生成一个 WelcomeFragmentDirections
类,其中只包含一个 action
WelcomeFragmentDirectionsToLoginFragment()
方法(该方法会返回 NavDirections
对象)。然后,您可以将返回的 NavDirections
对象直接传递到 navigate()
,如以下示例所示:
btn_login.setOnClickListener {
val action = WelcomeFragmentDirections.actionWelcomeFragmentToLoginFragment()
it.findNavController().navigate(action)
}
3.1.2 使用Id导航
navigate(int)
接受action或目的地的资源 ID 作为参数。以下代码段展示了如何导航到 RegisterFragment
:
btn_register.setOnClickListener {
//action id
it.findNavController().navigate(R.id.action_welcomeFragment_to_registerFragment)
// fragment id
it.findNavController().navigate(R.id.registerFragment)
}
注意:在使用 ID 进行导航时,我们强烈建议您尽可能使用action。action会在导航图中提供更多信息,从而直观显示目的地之间如何相互连接。通过创建action,您可以将资源 ID 替换为 Safe Args 生成的操作,从而进一步提高编译时安全性。通过使用action,您还可以在目的地之间添加动画过渡效果。如需了解详情,请参阅在目的地之间添加动画过渡效果。
对于按钮,您还可以使用 Navigation
类的 createNavigateOnClickListener()
便捷方法导航到目的地,如下例所示:
(----测试未生效 ,还未找到原因----)
button.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.next_fragment, null))
3.2 为action提供导航选项
在导航图中定义操作时,Navigation 会生成相应的 NavAction
类,其中包含为该操作定义的配置,包括如下内容:
- Destination:The resource ID of the target destination.。
- Default arguments:
android.os.Bundle
,包含target destination的默认值(如有提供)。 - Navigation options:表示为
NavOptions
。此类包含从 target destination往返的所有特殊配置,包括动画资源配置、弹出行为以及是否应在单一顶级模式下启动目的地。
3.2.1 NavOptions
login_navigation.xml
<fragment
android:id="@+id/welcomeFragment"
android:name="com.gozap.jetpack.ui.fragment.WelcomeFragment"
android:label="WelcomeFragment"
tools:layout="@layout/fragment_welcome">
<action
android:id="@+id/action_welcomeFragment_to_loginFragment"
app:destination="@id/loginFragment" />
<!--设置动画参数-->
<action
android:id="@+id/action_welcomeFragment_to_registerFragment"
app:destination="@id/registerFragment"
app:enterAnim="@anim/common_slide_in_right"
app:exitAnim="@anim/common_slide_out_left"
app:popEnterAnim="@anim/common_slide_in_left"
app:popExitAnim="@anim/common_slide_out_right"
app:popUpTo="@id/registerFragment"
app:popUpToInclusive="true"/>
</fragment>
WelcomeFragment.kt
btn_login.setOnClickListener {
//设置动画参数
val navOption= navOptions {
anim {
enter = R.anim.common_slide_in_right
exit = R.anim.common_slide_out_left
popEnter = R.anim.common_slide_in_left
popExit = R.anim.common_slide_out_right
}
}
val action = WelcomeFragmentDirections.actionWelcomeFragmentToLoginFragment()
it.findNavController().navigate(action,navOption)
}
扩充该导航图时,系统会解析这些操作,同时使用图中定义的配置生成相应的 NavAction
对象。例如,action_welcomeFragment_to_registerFragment
定义为从目的地 welcomeFragment 到目的地 registerFragment
的导航。该操作包含动画以及 popTo
行为,该行为会从返回堆栈中移除所有目的地。所有这些设置都会以 NavOptions
形式捕获并连接到 NavAction
。
要遵循此 NavAction
,请使用 NavController.navigate()
(传递action ID,不能使用destination ID ),否则无效
3.3 数据传递
官方文档:https://developer.android.google.cn/guide/navigation/navigation-pass-data?hl=zh_cn
定义destination arguments
在Navigation Editor中点击接收参数的descination 添加参数信息
xml代码如下:
<fragment
android:id="@+id/registerFragment"
android:name="com.jetpack.ui.fragment.RegisterFragment"
android:label="RegisterFragment"
tools:layout="@layout/fragment_register">
<argument
android:name="EMAIL"
android:defaultValue="[email protected]"
app:argType="string" />
</fragment>
3.3.1 使用 Safe Args 传递安全的数据
WelcomeFragment 中设置参数:
btn_register.setOnClickListener {
val action = WelcomeFragmentDirections
.actionWelcomeToRegister("[email protected]")
findNavController().navigate(action)
}
RegisterFragment中接收参数:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val safeArgs: RegisterFragmentArgs by navArgs()
val email = safeArgs.EMAIL
}
3.3.2 使用Bundle对象传递参数
WelcomeFragment中设置参数:
btn_register.setOnClickListener {
var bundle = bundleOf("EMAIL" to "[email protected]")
findNavController().navigate(R.id.action_welcome_to_register, bundle)
}
RegisterFragment中接收参数:
arguments?.getString("EMAIL")