一、Navigation简介
Navigation 直接翻译即为导航,它是 Android Jetpack 组件之一。
导航是指支持用户导航、进入和退出应用中不同内容片段的交互。Android Jetpack 的导航组件可帮助您实现导航,无论是简单的按钮点击,还是应用栏和抽屉式导航栏等更为复杂的模式,该组件均可应对。导航组件还通过遵循一套既定原则来确保一致且可预测的用户体验。
Navigation 让单 Activity 应用成为首选架构。应用内Fragment页面的跳转则由 Navigation 来处理,开发者无需在处理 FragmentTransaction 的复杂性以及相关的转场动画。
二、Navigation使用
1、在app的gradle.build中添加依赖
dependencies {
def nav_version = "2.5.0"
// 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"
// Feature module Support
implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"
// Testing Navigation
androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"
// Jetpack Compose Integration
implementation "androidx.navigation:navigation-compose:$nav_version"
}
2、创建导航图xml文件
在需要创建的module中选择res,创建 Android Resource File
2.png
在弹出的窗口中。设置名称。选择 Navigation 即可在 res/navigation下创建出一个开发者设置的导航图
3.png创建的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"
android:id="@+id/nav_graph">
</navigation>
3、创建导航图内容
4.png通过 Navigation Graph (导航图) 提供的快捷方式去添加。
点击添加目的地小图标 Create new desination 即可创建新的 desination 即(目的地), 当然还可以从列表直接选择之前已经创建好的。
5.png
<?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/homeFragment">
<fragment
android:id="@+id/homeFragment"
android:name="com.alan.mynavigation.HomeFragment"
android:label="fragment_home"
tools:layout="@layout/fragment_home" />
<fragment
android:id="@+id/secondFragment"
android:name="com.alan.mynavigation.SecondFragment"
android:label="fragment_second"
tools:layout="@layout/fragment_second" />
</navigation>
xml字段含义
navigation标签
字段 | 含义 |
---|---|
android:id="@+id/nav_graph" | 创建的 xml 名称 |
app:startDestination | 表示起始导航图,会显示哪个界面,会有一个小房子标志 |
fragment标签
字段 | 含义 |
---|---|
android:name | 目的地对应的类 |
android:lable | 包含该目的地的 XML 布局文件的名称 |
tools:layout | 设置可以预览导航图目的地布局文件 |
4、将导航图添加到布局当中
在MainActivity的xml中添加 FragmentContainerView
<?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=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fcv_nav"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
FragmentContainerView标签
字段 | 含义 |
---|---|
android:id | 必填,否则报错 |
android:name | 属性包含 NavHost 实现的类名称。 |
app:navGraph | 属性将 NavHostFragment 与导航图相关联 |
app:defaultNavHost="true" | 确保您的 NavHostFragment 会拦截系统返回按钮。请注意,只能有一个默认 NavHost。若为true,需要的Activity中重写onSupportNavigateUp方法。 |
override fun onSupportNavigateUp(): Boolean {
//调用扩展函数findNavController
return findNavController(R.id.nav_host_fragment).navigateUp()
}
5、普通跳转并传参数
1.创建跳转行为
切换到Design 标签页中,将鼠标悬停在目的地的右侧,该目的地右侧上方会显示一个圆圈,点击您希望用户导航到的目的地,并将光标拖动到该目的地的上方,然后松开。这两个目的地之间生成的线条表示操作完成,并在xml 中会在起点的fragment下生成一个<action>标签,其中 app:destination表示目的地,design页面右边属性也可看到详情。
<fragment
android:id="@+id/homeFragment"
android:name="com.alan.mynavigation.HomeFragment"
android:label="fragment_home"
tools:layout="@layout/fragment_home" >
<action
android:id="@+id/action_homeFragment_to_secondFragment"
app:destination="@id/secondFragment" />
</fragment>
12.gif
2.无参跳转
- Fragment.findNavController().navigate(ResId)
//点击事件
btn_second.setOnClickListener {
//通过传入action来实现导航
findNavController().navigate(R.id.action_homeFragment_to_secondFragment)
}
- View.findNavController().navigate(ResId)
//点击事件
btn_second.setOnClickListener {
//通过传入action来实现导航
it.findNavController().navigate(R.id.action_homeFragment_to_secondFragment)
}
- Activity.findNavController().navigate(ResId)
//点击事件
btn_second.setOnClickListener {
//通过传入action来实现导航
findNavController().navigate(R.id.action_homeFragment_to_secondFragment)
}
其中的 resId 是上面我们创建的<action>标签中的id
获取NavController方式不同
//注意:Kotlin使用和Java中获取NavController方式不同
//Kotlin:
Fragment.findNavController()
View.findNavController()
Activity.findNavController(viewId: Int)
//Java:
NavHostFragment.findNavController(Fragment)
Navigation.findNavController(Activity, @IdRes int viewId)
Navigation.findNavController(View)
//使用 FragmentContainerView 创建 NavHostFragment,或通过 FragmentTransaction 手动将 NavHostFragment 添加到您的 Activity 时,尝试通过 Navigation.findNavController(Activity, @IdRes int) 检索 Activity 的 onCreate() 中的 NavController 将失败。您应改为直接从 NavHostFragment 检索 NavController。
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
3.带参跳转
不推荐的方式。推荐的方式可以参考 safe ages跳转传参。
//点击事件
btn_second.setOnClickListener {
val bundle = Bundle()
bundle.putString("username", "alan")
bundle.putInt("age", 18)
//通过传入action来实现导航
it.findNavController().navigate(R.id.action_homeFragment_to_secondFragment,bundle)
}
获取的时候和Fragment以前的方式一样,使用 arguments 去获取。
6、safe ages跳转传参
Navigation 组件具有一个名为 Safe Args 的 Gradle 插件,该插件可以生成简单的 object 和 builder 类,以便以类型安全的方式浏览和访问任何关联的参数。我们强烈建议您将 Safe Args 用于导航和数据传递,因为它可以确保类型安全。
1.配置
在根目录build.gradle中先添加 plugin
buildscript {
repositories {
google()
}
dependencies {
def nav_version = "2.3.2"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
}
必须应用以下两个可用插件之一,请将以下行添加到应用或模块的 build.gradle 文件中
plugins {
//生成适用于 Java 模块或 Java 和 Kotlin 混合模块的 Java 语言代码
id("androidx.navigation.safeargs.kotlin")
//生成仅适用于 Kotlin 模块的 Kotlin 语言代码
id("androidx.navigation.safeargs.kotlin")
}
配置完成以后记得要clean rebuild 一下,会生成对应的Directions类。
image.png
启用 Safe Args 后,生成的代码会为每个操作包含以下类型安全的类和方法,以及每个发送和接收目的地。
1.为生成操作的每一个目的地创建一个类。该类的名称是在源目的地的名称后面加上“Directions”。例如,如果源目的地是名为 SpecifyAmountFragment 的 Fragment,则生成的类的名称为 SpecifyAmountFragmentDirections。
2.该类会为源目的地中定义的每个操作提供一个方法。
对于用于传递参数的每个操作,都会创建一个 inner 类,该类的名称根据操作的名称确定。例如,如果操作名称为 confirmationAction,,则类名称为 ConfirmationAction。如果您的操作包含不带 defaultValue 的参数,则您可以使用关联的 action 类来设置参数值。
3.为接收目的地创建一个类。该类的名称是在目的地的名称后面加上“Args”。例如,如果目的地 Fragment 的名称为 ConfirmationFragment,,则生成的类的名称为 ConfirmationFragmentArgs。可以使用该类的 fromBundle() 方法检索参数。
2.无参跳转
btn_second.setOnClickListener {
val action = HomeFragmentDirections.actionHomeFragmentToSecondFragment()
findNavController().navigate(action)
}
3.有参跳转
第一步:创建参数
Design 模式下,点击启示位置(注意是启示位置)布局信息,看到他变成蓝色以后。看到右边面板上有 Arguments
image.png
创建了两个字段。name age 对应的导航图中也可以看到其信息。
<fragment
android:id="@+id/homeFragment"
android:name="com.alan.mynavigation.HomeFragment"
android:label="fragment_home"
tools:layout="@layout/fragment_home" >
<action
android:id="@+id/action_homeFragment_to_secondFragment"
app:destination="@id/secondFragment" />
<argument
android:name="name"
app:argType="string"
android:defaultValue="alan" />
<argument
android:name="age"
app:argType="integer"
android:defaultValue="18" />
</fragment>
argument标签
字段 | 含义 |
---|---|
android:name | 参数名称 |
android:defaultValue | 为默认值 |
app:argType | 为参数类型 |
app:nullable | 为标记是否可空 |
第二步:生成Args类
创建完成clean rebuild 一下,插件会给我们生成一个 Args结尾的类。
image.png
第三步:带参数跳转
btn_second.setOnClickListener {
val bundle = HomeFragmentArgs("张三",20).toBundle()
findNavController().navigate(R.id.action_homeFragment_to_secondFragment,bundle)
}
第四步:接收参数
//方式1:使用 fromBundle
arguments?.apply {
val fragmentArgs = HomeFragmentArgs.fromBundle(this)
val username = fragmentArgs.name
val age = fragmentArgs.age
tv_info.text = "$username $age"
}
//方式2: 使用委托的方式
val args by navArgs<HomeFragmentArgs>()
tv_info.text = "$username $age"
4.使用Action方式有参跳转
从上个列子可以看出虽然携带参数跳转了,但使用的是id
findNavController().navigate(R.id.action_homeFragment_to_secondFragment,bundle)
插件为我们生成的HomeFragmentDirections不可以携带参数,这是因为需要跳转双方都声明相同的参数。然后clean rebuild一下生成新的带参数的HomeFragmentDirections。
image.png
btn_second.setOnClickListener {
val action = HomeFragmentDirections.actionHomeFragmentToSecondFragment("张三", 20)
findNavController().navigate(action)
}
7、Deep Links
深层链接是指将用户直接转到应用内特定目的地的链接。
比如点击一下通知就要跳转到对应的消息详情界面。
1.显式深层链接
btn_second.setOnClickListener {
val bundle = Bundle().apply {
putString("username", "alan")
putInt("age", 18)
}
val pendingIntent = NavDeepLinkBuilder(requireActivity())
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.secondFragment)
.setArguments(bundle)
//默认情况下,NavDeepLinkBuilder 会将显式深层链接启动到应用清单中声明的默认启动 Activity。如果您的 NavHost 在其他 activity 中,则您必须在创建深层链接建立工具时指定其组件名称:
.setComponentName(DetailActivity::class.java)
.createPendingIntent()
//通知
val notificationManager = requireActivity().getSystemService(NOTIFICATION_SERVICE) as (NotificationManager)
val CHANNEL_ID = "1000"
val channel = NotificationChannel(
CHANNEL_ID, "测试",
NotificationManager.IMPORTANCE_DEFAULT
)
notificationManager.createNotificationChannel(channel);
val builder = NotificationCompat.Builder(requireActivity(), CHANNEL_ID)
.setContentTitle("测试")
.setContentText("内容")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
notificationManager.notify(1, builder.build())
}
2.创建隐式深层链接
第一步:创建隐式深层链接
可以通过 URI、intent 操作和 MIME 类型匹配深层链接。您可以为单个深层链接指定多个匹配类型,但请注意,匹配的优先顺序依次是 URI 参数、操作和 MIME 类型。例如:
<deeplink app:uri="www.example.com"
app:action="android.intent.action.MY_ACTION"
app:mimeType="type/subtype"/>
image.png
Add Deep Link 对话框中请注意以下几点:
- 没有架构的 URI 会被假定为 http 或 https。例如,
www.google.com
同时与http://www.google.com
和https://www.google.com
匹配。 - 形式为
{placeholder_name}
的路径参数占位符与一个或多个字符相匹配。例如,http://www.example.com/users/{id}
与http://www.example.com/users/4
匹配。Navigation 组件通过将占位符名称与已定义的参数(为深层链接目的地所定义)相匹配,尝试将占位符值解析为相应的类型。如果没有定义具有相同名称的参数,对参数值使用默认的String
类型。您可以使用 .* 通配符匹配 0 个或多个字符。 - 可以使用查询参数占位符代替路径参数,也可以将查询参数占位符与路径参数结合使用。例如,
http://www.example.com/users/{id}?myarg={myarg}
与http://www.example.com/users/4?myarg=28
匹配。 - 使用默认值或可为 null 的值所定义的变量的查询参数占位符无需匹配。例如,
http://www.example.com/users/{id}?arg1={arg1}&arg2={arg2}
与http://www.example.com/users/4?arg2=28
或http://www.example.com/users/4?arg1=7
匹配。不过,路径参数并非如此。例如,http://www.example.com/users?arg1=7&arg2=28
就与上述模式不匹配,因为未提供所需的路径参数。 - 多余的查询参数不会影响深层链接 URI 匹配。例如,即使 URI 模式中未定义
extraneousParam
,http://www.example.com/users/{id}
也与http://www.example.com/users/4?extraneousParam=7
匹配。 - (可选)选中 Auto Verify 可要求 Google 验证您是相应 URI 的所有者。
<fragment
android:id="@+id/secondFragment"
android:name="com.alan.mynavigation.SecondFragment"
android:label="fragment_second"
tools:layout="@layout/fragment_second">
<argument
android:name="name"
android:defaultValue="alan"
app:argType="string" />
<argument
android:name="age"
android:defaultValue="18"
app:argType="integer" />
<deepLink
android:id="@+id/deepLink"
app:uri="http://www.alan.com/second/${name}" />
</fragment>
第二步:添加nav-graph 声明
应用的manifest.xml 文件中,将一个 <nav-graph> 元素添加到指向现有导航图的 Activity标签中。
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<nav-graph android:value="@navigation/nav_graph" />
</activity>
构建项目时,Navigation 组件会将 <nav-graph> 元素替换为生成的 <intent-filter> 元素,以匹配导航图中的所有深层链接。
在触发隐式深层链接时,返回堆栈的状态取决于是否使用Intent.FLAG_ACTIVITY_NEW_TASK标志启动隐式 Intent:
- 如果该标志已设置,任务返回堆栈就会被清除,并被替换为相应的深层链接目的地。与显式深层链接一样,当嵌套图表时,每个嵌套级别的起始目的地(即层次结构中每个
<navigation>
元素的起始目的地)也会添加到相应堆栈中。也就是说,当用户从深层链接目的地按下返回按钮时,他们会返回到相应的导航堆栈,就像从入口点进入您的应用一样。 - 如果该标记未设置,您仍会位于触发隐式深层链接时所在的上一个应用的任务堆栈中。在这种情况下,如果按下返回按钮,您会返回到上一个应用;如果按下向上按钮,就会在导航图中的父级目的地上启动应用的任务。
使用 Navigation 时,强烈建议您始终使用默认launchMode:standard。使用standard启动模式时,Navigation 会调用handleDeepLink()来处理Intent中的任何显式或隐式深层链接,从而自动处理深层链接。但是,如果在使用备用 singleTop
等备选 launchMode
时重复使用了相应 Activity
,则这不会自动发生。在这种情况下,有必要在 onNewIntent()
中手动调用 handleDeepLink()
,如以下示例所示:
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
navController.handleDeepLink(intent)
}
第三步:使用隐式深层链接
btn_second.setOnClickListener {
val name = "李四"
findNavController().navigate("http://www.alan.com/second/${name}".toUri())
}
8、添加动画效果
<?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/homeFragment">
<fragment
android:id="@+id/homeFragment"
android:name="com.alan.mynavigation.HomeFragment"
android:label="fragment_home"
tools:layout="@layout/fragment_home" >
<action
android:id="@+id/action_homeFragment_to_secondFragment"
app:destination="@id/secondFragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
<argument
android:name="name"
app:argType="string"
android:defaultValue="alan" />
<argument
android:name="age"
app:argType="integer"
android:defaultValue="18" />
</fragment>
<fragment
android:id="@+id/secondFragment"
android:name="com.alan.mynavigation.SecondFragment"
android:label="fragment_second"
tools:layout="@layout/fragment_second">
<argument
android:name="name"
android:defaultValue="alan"
app:argType="string" />
<argument
android:name="age"
android:defaultValue="18"
app:argType="integer" />
<deepLink
android:id="@+id/deepLink"
app:uri="http://www.alan.com/second/{name}" />
</fragment>
</navigation>
网友评论