Jectpack -- Navigation了解与简单使用

Navigation是一个用于简化Android中Fragment管理和切换的库,通过可视化导航图实现清晰的界面跳转逻辑。它支持动画、参数传递、共享元素过渡和DeepLink等功能,提供NavHostFragment进行导航宿主设置,并使用NavController处理页面间跳转。SafeArgs插件则提供了编译时安全的数据传递机制。
摘要由CSDN通过智能技术生成

简介

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中可以清晰的解决

  1. 如何在每个界面快捷加上跳转动画?
  2. 当你接手一个较大的项目,如何能快速理清界面间的跳转关系?
  3. 在单 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 使用房子图标 表示起始目的地。

连接目的地

  1. Design 标签页中,将鼠标悬停在目的地的右侧,该目的地为希望用户从中导航出来的目的地。该目的地右侧上方会显示一个圆圈。

  2. 点击希望用户导航到的目的地,并将光标拖动到该目的地的上方,然后松开。这两个目的地之间生成的线条表示 "操作"

  3. 点击箭头以突出显示该操作。此时 Attributes 面板中会显示以下属性:

  • ID 字段包含该操作的 ID。
  • Destination 字段包含目的地 Fragment 或 Activity 的 ID。
  1. 同时还可以设置跳转时候的动画, 有一些默认的可以选择, 也可以自定义

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

例如在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()
创建隐式深层链接
  1. 进入导航图**Design > Deep Links** 点击 + 添加
  2. 填入对应参数,生成代码,注意参数可以对应<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>
  1. 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 所示。

创建全局操作

如需创建全局操作,请执行以下操作:

  1. Graph Editor 中,点击一个目的地,使其突出显示。
  2. 右键点击该目的地,以显示上下文菜单。
  3. 依次选择 Add Action > Global。此时系统会在该目的地左侧显示一个箭头 ()。
  4. 点击 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的方法
setFragmentResultsetFragmentResultListener

//起始
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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值