简介
Navigation是一个可简化的Android导航的库和插件,换句话说,Navigation是用来规范管理Fragment的切换的,并且是通过可视化的方式来进行管理的。
![[Pasted image 20230210083913.png]]
正常的跳转方法(其实有kotlin以后,代码精简了不少, 代码量与navigation没有太大区别)
// 跳转 activity
val intent = Intent(this, SecondActivity::class.java)
intent.putExtra("key", "value")
startActivity(intent)
// 跳转 fragment
supportFragmentManager.commit {
addToBackStack(null)
val args = Bundle().apply { putString("key", "value") }
replace<WelComeFragment>(R.id.container, args = args)
}
曾经可能遇到的问题,在Navigation中可以清晰的解决
- 如何在每个界面快捷加上跳转动画?
- 当你接手一个较大的项目,如何能快速理清界面间的跳转关系?
- 在单 activity 的项目中,如何控制几个相关的 fragment 有着相同的 ViewModel 作用域,而不是整个 activity 共享的viewmodel ?
从基本使用来了解Navigation的各项优点
1.引入依赖
dependencies {
def nav_version = "2.5.3"
// Java
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"
}
1.创建导航图
优势之一导航图,清晰理清页面之间联系
导航图是一种资源文件,其中包含所有目的地和操作。该图表会显示应用的所有导航路径。
示例:
![[navigation-graph_2x-callouts.png]]
1. “目的地”是指应用中的不同内容区域。
2. “操作”是指目的地之间的逻辑连接,表示用户可以采取的路径
如需向项目添加导航图,请执行以下操作:
1. 在Project
窗口中,右键点击res
目录,然后依次选择 New > Android Resource File
。此时系统会显示 New Resource File
对话框。
2. 在 File name
字段中输入名称,例如“nav_graph”。
3. 从 Resource type
下拉列表中选择 Navigation
,然后点击 OK
。
我们创建三个Fragment来演示效果,并梳理跳转逻辑
- WelcomeFragment
- LoginFragment
- RegisterFragment
添加图表后,在 Navigation Editor 中,可以直观地修改导航图,或直接修改底层 XML代码。
![[nav-editor-2x.png]]
1. **Component Tree**:列出了导航宿主和目前位于 **Graph Editor** 中的所有目的地。
2. **Graph Editor**:包含导航图的视觉表示形式。可以在 **Design** 视图和 **Text** 视图中的底层 XML 表示形式之间切换。
3. **Attributes**:显示导航图中当前所选项的属性。在此面板中可以进行多种操作:
- 创建Fragment
- 拖动设置跳转关系
- 页面切换动画
- 页面之间传递参数
- 页面之间跳转关系与处理
xml代码中,创建的元素标签分别对应
** fragment > Fragment **
** activity > Activity **
** dialog > DialogFragment **
将某个屏幕指定为起始目的地
Navigation Editor 使用房子图标 表示起始目的地。
连接目的地
-
在 Design 标签页中,将鼠标悬停在目的地的右侧,该目的地为希望用户从中导航出来的目的地。该目的地右侧上方会显示一个圆圈。
-
点击希望用户导航到的目的地,并将光标拖动到该目的地的上方,然后松开。这两个目的地之间生成的线条表示
"操作"
。
-
点击箭头以突出显示该操作。此时 Attributes 面板中会显示以下属性:
- ID 字段包含该操作的 ID。
- Destination 字段包含目的地 Fragment 或 Activity 的 ID。
- 同时还可以设置跳转时候的动画, 有一些默认的可以选择, 也可以自定义
2. 向 Activity 添加 NavHost
导航宿主是 Navigation 组件的核心部分之一。导航宿主是一个空容器,用户在应用中导航时,目的地会在该容器中交换进出。
导航宿主必须派生于 NavHost
。Navigation 组件的默认 NavHost
实现 (NavHostFragment
) 负责处理 fragment 目的地的交换。
通过 XML 添加 NavHostFragment
新建MainActivity的xml中创建如下代码
<?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/nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp" android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:defaultNavHost="true" app:navGraph="@navigation/nav_graph"/>
</androidx.constraintlayout.widget.ConstraintLayout>
android:name
属性包含NavHost
实现的类名称,使用NavHostFragment
就可以。app:navGraph
属性将NavHostFragment
与导航图相关联。app:defaultNavHost="true"
确保NavHostFragment
会拦截系统返回按钮。
3. 页面之间的跳转与传参
导航到目的地是使用 NavController
完成的, 如需检索 Fragment、Activity 或视图的 NavController
,请使用以下某种方法:
Kotlin:
Java:
NavHostFragment.findNavController(Fragment)
Navigation.findNavController(Activity, @IdRes int viewId)
Navigation.findNavController(View)
例如在WelcomeFragment中,点击button跳转到LoginFragment
view.findViewById<Button>(R.id.go_login).setOnClickListener {
//携带参数跳转
val args = Bundle()
args.putString("safeArg","dataFromWelComeFragment") findNavController().navigate(R.id.action_welcomeFragment_to_loginFragment,args)
// 普通跳转
val action = WelcomeFragmentDirections.actionWelcomeFragmentToLoginFragment()
findNavController().navigate(action)
//使用safe arg跳转
val action = WelcomeFragmentDirections.actionWelcomeFragmentToLoginFragment(safeArg = "dataFromWelComeFragment")
findNavController().navigate(action)
}
元素共享
先看示例图,大家肯定见过这种效果,在页面切换时,一个动画使一个元素动画过渡到第二个页面
![[GIF.gif]]
起始页面和目标页面的xml中增加ImageView
增加transitionName
参数
<ImageView
android:id="@+id/userAvatarIv"
android:transitionName="userAvatarTn"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@mipmap/ic_launcher"
android:layout_gravity="center"/>
在目标Fragment中的onCreate()
中设置sharedElementEnterTransition
sharedElementEnterTransition = TransitionInflater.from(requireContext()).inflateTransition(R.transition.shared_image)
xml中新建transition
文件夹与xml文件
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<autoTransition android:duration="400"/>
</transitionSet>
Fragment 目的地的共享元素过渡
val extras = FragmentNavigatorExtras(userAvatarIv to "userAvatarTn") //首先创建一个extras
view.findNavController().navigate(
R.id.confirmationAction,
null, // Bundle of args
null, // NavOptions
extras)
Activity 目的地的共享元素过渡
import android.util.Pair as UtilPair
...
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, UtilPair.create(userAvatarIv, "userAvatarTn"))
val extras = ActivityNavigatorExtras(options)
view.findNavController().navigate(
R.id.details,
null, // Bundle of args
null, // NavOptions
extras)
DeepLink
创建显式深层链接
通常用于闹钟,通知
显式深层链接是深层链接的一个实例,该实例使用 PendingIntent
将用户转到应用内的特定位置。例如,您可以在通知或应用 widget 中显示显式深层链接。
当用户通过显式深层链接打开您的应用时,任务返回堆栈会被清除,并被替换为相应的深层链接目的地。当嵌套图表时,每个嵌套级别的起始目的地(即层次结构中每个 <navigation>
元素的起始目的地)也会添加到相应堆栈中。也就是说,当用户从深层链接目的地按下返回按钮时,他们会返回到相应的导航堆栈,就像从入口点进入您的应用一样。
您可以使用 NavDeepLinkBuilder
类构造 PendingIntent
,如以下示例所示。请注意,如果提供的上下文不是 Activity
,构造函数会使用 PackageManager.getLaunchIntentForPackage()
作为默认 activity 启动(如有)。
val pendingIntent = NavDeepLinkBuilder(context)
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.android)
.setArguments(args)
.createPendingIntent()
默认情况下,NavDeepLinkBuilder
会将显式深层链接启动到应用清单中声明的默认启动 Activity
。如果您的 NavHost
在其他 activity 中,则您必须在创建深层链接建立工具时指定其组件名称:
val pendingIntent = NavDeepLinkBuilder(context)
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.android)
.setArguments(args)
.setComponentName(DestinationActivity::class.java)
.createPendingIntent()
创建隐式深层链接
- 进入导航图**
Design
>Deep Links
** 点击+
添加 - 填入对应参数,生成代码,注意参数可以对应
<argument>
标签
<fragment
android:id="@+id/registerFragment"
android:name="com.example.navigationdemo.normal.RegisterFragment"
android:label="fragment_register"
tools:layout="@layout/fragment_register">
<argument
android:name="name"
android:defaultValue="defaultValue name"
app:argType="string" />
<argument
android:name="age"
android:defaultValue="defaultValue 1"
app:argType="string" />
<deepLink
app:mimeType="*/*"
app:uri="https://aa.bb.com/rgsFragment/{name}/{age}" />
</fragment>
- 在
Manifest.xml
中,在添加设置的fragment所属的Activity下,增加代码<nav-graph android:value="@navigation/nav_graph"/>
<activity
android:name=".normal.LoginActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="" />
<nav-graph android:value="@navigation/nav_graph"/>
</activity>
例如在html中可以跳转app相对应页面,并且保持返回时,按照设置的栈顺序返回
<!DOCTYPE html>
<html>
<head>
<title>跳转测试</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
</head>
<body>
<a href="https://aa.bb.com/rgsFragment/tom/25" style="font-size: 60px;"> 点击跳转</a>
</body>
</html>
ps:官网的介绍是这样,但是我没有跳转成功,没有找到问题点
全局操作
可以使用全局操作来创建可由多个目的地共用的通用操作。例如,您可能想要不同目的地中的多个按钮导航到同一应用主屏幕。
在 Navigation Editor 中,全局操作由一个指向相关联目的地的小箭头表示,如图 1 所示。
创建全局操作
如需创建全局操作,请执行以下操作:
- 在 Graph Editor 中,点击一个目的地,使其突出显示。
- 右键点击该目的地,以显示上下文菜单。
- 依次选择 Add Action > Global。此时系统会在该目的地左侧显示一个箭头 ()。
- 点击 Text 标签页,以转到 XML 文本视图。全局操作的 XML 文本大致如下所示:
![[Pasted image 20230209163701.png]]
使用全局操作
view.findNavController().navigate(R.id.action_global_mainFragment)
就是调用这个最外层的action事件
返回
Android 会维护一个返回堆栈,其中包含您之前访问过的目的地。当用户打开您的应用时,应用的第一个目的地就放置在堆栈中。每次调用 navigate()
方法都会将另一目的地放置到堆栈的顶部。点按向上或返回会分别调用 NavController.navigateUp()
和 NavController.popBackStack()
方法,用于移除(或弹出)堆栈顶部的目的地。
会返回一个布尔值,表明它是否已成功返回到另一个目的地。当返回 false
时,最常见的情况是手动弹出图的起始目的地。
如果popBackStack()
方法返回 false
,不会保留栈内的信息,则 NavController.getCurrentDestination()
会返回 null
。
如果navigateUp()
方法返回 false
,会在栈内保留最后一个不清空,则 NavController.getCurrentDestination()
不会返回 null
。
popUpTo 和 popUpToInclusive
首先看图
![[navigation-getting-started-pop.png]]
这是一种使用场景,比如 ** 首页 > 登录 > 注册 > 再到首页**
在注册成功回到首页时, 需要干掉登录和注册页
但是正常的跳转会是堆栈堆叠, 对战中会有多个重复页面
这时可以这样操作
<fragment
android:id="@+id/注册"
android:name="com.example.myapplication.注册"
android:label="fragment_注册"
tools:layout="@layout/fragment_注册">
<action
android:id="@+id/action_注册_to_首页"
app:destination="@id/首页"
app:popUpTo="@+id/首页"
app:popUpToInclusive="true"/>
</fragment>
popUpTo
首页,也就是说我们会在导航过程中从堆栈中移除 登录 和 注册。利用 app:popUpToInclusive="true"
,我们还会将第一个 首页 从堆栈上弹出,从而有效地清除它。请注意,如果不使用 app:popUpToInclusive
,则返回堆栈会包含目的地 首页 的两个实例。
safe Args
什么是 Safe Args?
定义
它是一个 Gradle 插件,可以根据 navigation 文件生成代码,帮助开发者安全地在 Destination(目的地)
之间传递数据
那么为什么要设计这样一个插件呢?
我们知道使用 bundle 或者 intent 传递数据时,如果出现类型不匹配或者其他异常,是在 Runtime 中才能发现的(传递了string,接收时误写成int)
而 Safe Args
把校验转移到了编译期
先看看简单的使用
使用 Safe Args
需要手动引入插件
buildscript {
repositories {
google()
}
dependencies {
def nav_version = "2.3.0-alpha06"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
}
并且需要加入
plugins {
//kotlin
id 'androidx.navigation.safeargs.kotlin'
//java
id 'androidx.navigation.safeargs'
}
1.配置参数
在<navigation>-<fragment>-<action>
标签中配置,我们加入<argument>
标签
<argument
android:name="safe_arg"
android:defaultValue="123"
app:argType="string" />
argType支持以下类型
![[62a747e1aa77950de9f703c38fc96b30.png]]
<argument>
也可以放在<fragment>
标签下,也可以放在根标签下,但前提是根标签必须有id,放在根标签下通常认为是作为通用参数
2.发送方(WelcomeFragment)
val action = WelcomeFragmentDirections.actionWelcomeFragmentToLoginFragment("safeArg,success")
findNavController().navigate(action)
3.接收方(LoginFragment)
可以使用getArgument(),像普通fragment一样接收参数
arguments?.let {
val safeArg = it.getString("safe_arg")
}
如果使用的是-ktx
依赖, 还可以使用 by navArgs()
属性委托来访问参数
需要在navigation.xml
中配置一下接收方的argument数据
<fragment
android:id="@+id/loginFragment"
android:name="com.example.navigationdemos.LoginFragment"
android:label="fragment_login"
tools:layout="@layout/fragment_login">
<action
android:id="@+id/action_loginFragment_to_registerFragment"
app:destination="@id/registerFragment" />
<argument
android:name="safe_arg"
android:defaultValue="123"
app:argType="string" />
</fragment>
接收:
val args: LoginFragmentArgs by navArgs()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val safeArg = args.safeArg
}
需要注意的一个小点 当跳转
和目标
的
不匹配时,起始Fragment的Directions会把两个参数都编译进去,但是目标Fragment的Directions还是只会编译自己有的
`
归结到底,其实就是把<argument>
参数生成了一个类, 可以通过参数的方式来设置和获取,避免手写失误
至此一个简单的demo就完成了
PS: 一小些拓展问题
是否支持跳转后的callback(startActivityForResult)
目前在Navigation中没有支持,但是可以使用Fragment的方法
setFragmentResult
、 setFragmentResultListener
//起始
setFragmentResultListener("key") { key, bundle ->
Log.d(TAG, "setFragmentResultListener: $key ${bundle.getString("key123")}" )
}
//目标fragment
val bundle = Bundle()
bundle.putString("key123","resultsuccessful")
setFragmentResult("key",bundle)
嵌套Fragment
Navigation是一个可视化的逻辑清晰的"导航"依赖,不能做两个Fragment的嵌套操作
嵌套导航图
场景: 登录页面Activity需要有登录-注册-隐私协议等跳转逻辑,进入首页后,可能也希望在首页Activity可以做登录逻辑, 所以可以复用相关导航图
![[7ccbf594f7834fa6934733d71defbf91.png]]
也可以抽出来 用include
标签包含此导航
![[2432cd1570ff444abcb2928801f0ecba.png]]
是否可以不使用replace方式切换
可以自己改,但是推荐使用ViewModel+LiveData来保存数据
ViewModel作用域
全局性参数
activityViewModels
局部性参数
navGraphViewModels
navGraphViewModels的ViewModel对应范围为 graph的范围,只要存在于图范围的Fragment未被销毁,viewmodel就不会被销毁
private val viewModel: ShareViewModel by navGraphViewModels(R.id.nav_graph)