Android Jetpack - Navigation 组件:进行应用程序导航

一. Navigation 组件的介绍
1.1 什么是 Navigation 组件
- Navigation 组件是一种 Android Jetpack 库,它可以帮助开发者轻松地实现应用程序中的导航功能。导航组件包含多个类和组件,包括导航图、目的地、导航控制器等,可以帮助我们管理应用程序中的页面导航和任务导航。通过使用 Navigation 组件,我们可以更加方便地实现应用程序的导航功能,同时也可以提高应用程序的用户体验。在本篇文章中,我们将介绍如何使用 Navigation 组件来实现应用程序导航,并提供一些示例和更多的扩展功能。
1.2 Navigation 组件的优势
- Navigation 组件可以轻松实现应用程序中的导航,包括页面之间的转换和应用程序内部的导航。
- Navigation 组件可以提高应用程序的可维护性和可扩展性,因为它们使得应用程序的结构更加清晰,并且可以更容易地添加新的功能和页面。
- Navigation 组件可以提供一致的用户体验,因为它们使用了标准的导航模式和动画效果。
- Navigation 组件可以帮助开发人员更快地构建应用程序,因为它们提供了许多常见的导航模式和功能,可以直接使用或进行修改。
- Navigation 组件可以提高应用程序的可测试性,因为它们使得页面之间的导航和状态转换更加明确和可控。
1.3 Navigation 组件主要由3个部分组成:
- NavHost:用来嵌入导航流程的容器,一般使用
FragmentContainerView。 - NavController:负责在
NavHost内部处理导航事务的控制器,用于执行页面跳转、管理返回栈等。 - NavGraph:描述
Fragment之间导航关系的资源文件,在其中定义页面之间的转跳、动画等。一般放在res/navigation/目录下。
简而言之,Navigation组件通过在NavHost中使用NavGraph来描述Fragment导航路径与关系,然后由NavController来执行实际的导航工作,这样极大地简化了以往的页面跳转逻辑和回退栈管理流程。
二. Navigation 组件的基本使用
2.1 添加导航组件到项目中
- 在项目的 build.gradle 文件中添加以下依赖:
dependencies {def nav_version = "2.5.3"implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
}
- 在布局文件中添加 NavHostFragment:
<androidx.fragment.app.FragmentContainerViewandroid:id="@+id/nav_host_fragment"android:name="androidx.navigation.fragment.NavHostFragment"android:layout_width="match_parent"android:layout_height="match_parent"app:navGraph="@navigation/nav_graph" />
- 正确获取 NavController 对象 :
在 Activity 内使用 NavController 时,应在onCreate()中获取:
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
NavigationUI.setupActionBarWithNavController(this, navController)
而在 Fragment 内,应在onAttach()或onViewCreate()中获取:
val navHostFragment = parentFragment as NavHostFragment
val navController = navHostFragment.navController
NavHost需要指定app:navGraph属性来关联一个导航图NavGraph,这决定了其中Fragment页面之间的导航关系和跳转路径。
NavController是 Navigation组件的控制中心,用于在NavHost内执行导航操作。可以在Activity或Fragment中通过NavHostFragment的navController属性获取对应的NavController实例。
常见的导航操作有:
- 导航到目标目的地:
navController.navigate(R.id.destination_id) - 回退一个目的地:
navController.navigateUp()或navController.popBackStack() - 回退到根目的地:
navController.popBackStack(R.id.root_destination, false)
NavController还负责维护Fragment的回退栈,以及在按返回按钮时正确出栈,这大大简化了之前管理Fragment事务的复杂度。
通过NavHost和NavController的配合,Navigation组件实现了在NavGraph中声明的导航逻辑和页面切换功能。这使Fragment之间的导航变得极为简单高效。开发者只需关注于定义NavGraph,并调用NavController中的导航方法即可实现页面跳转,其余的一切尽在Navigation组件的掌控之中。
2.2 创建导航图
- 在XML文件中创建导航图:
<androidx.fragment.app.FragmentContainerViewandroid:id="@+id/nav_host_fragment"android:name="androidx.navigation.fragment.NavHostFragment"android:layout_width="match_parent"android:layout_height="match_parent"app:navGraph="@navigation/nav_graph" />
- 在res文件夹下创建一个
navigation文件夹,然后在该文件夹下创建一个nav_graph.xml文件,用于定义导航图的结构和内容:
<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/firstFragment"><fragmentandroid:id="@+id/firstFragment"android:name="com.smallmarker.jetpackpractice.navigation.fragment.FirstFragment"android:label="First"tools:layout="@layout/fragment_first"><actionandroid:id="@+id/action_firstFragment_to_secondFragment"app:destination="@+id/secondFragment" /></fragment><fragmentandroid:id="@+id/secondFragment"android:name="com.smallmarker.jetpackpractice.navigation.fragment.SecondFragment"android:label="Second"tools:layout="@layout/fragment_second" /></navigation>
在导航图中,<fragment> 元素用于定义目的地,android:id属性用于指定目的地的唯一标识符,android:name 属性用于指定目的地的类名,android:label 属性用于指定目的地在应用程序中显示的标签名称。
<action> 元素用于定义动作,android:id 属性用于指定动作的唯一标识符,app:destination 属性用于指定动作要执行的目的地。
三. Navigation 组件的高级使用
3.1 深层链接
在 Android 中,深层链接是指将用户直接转到应用内特定目的地的链接。借助 Navigation 组件,您可以创建两种不同类型的深层链接:显式深层链接和隐式深层链接。
- 创建显式深层链接:
显式深层链接是深层链接的一个实例,该实例使用 PendingIntent 将用户转到应用内的特定位置。例如,您可以在通知或应用 widget 中显示显式深层链接。
val pendingIntent = NavDeepLinkBuilder(it).setGraph(R.navigation.nav_deep_link).setDestination(R.id.deepLinkFragment).setArguments(Bundle().apply {putInt("id", 1)}).setComponentName(DeepLinkActivity::class.java).createPendingIntent()val notification = NotificationCompat.Builder(it, "my_channel").setContentTitle("Title").setContentText("测试深层链接").setSmallIcon(R.mipmap.ic_launcher).setContentIntent(pendingIntent).build()NotificationManagerCompat.from(it).notify(Random.nextInt(10), notification)
该示例使用 NavDeepLinkBuilder 类构造 PendingIntent, 添加到通知中并发送,点击通知跳转指定页面。
- 创建隐式深层链接:
<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_deep_link"app:startDestination="@id/deepLinkFragment"><fragmentandroid:id="@+id/deepLinkFragment"android:name="com.smallmarker.jetpackpractice.navigation.fragment.DeepLinkFragment"android:label="DeepLink"tools:layout="@layout/fragment_deep_link"><deepLink app:uri="example://deepLink/{id}" /></fragment></navigation>
如需启用隐式深层链接,您还必须向应用的 manifest.xml 文件中添加内容。将一个 <nav-graph> 元素添加到指向现有导航图的 activity,如以下示例所示。
<activityandroid:name=".navigation.DeepLinkActivity"android:exported="true"><nav-graph android:value="@navigation/nav_deep_link" />
</activity>
在这个例子中,我们定义了一个深度链接,它的 URI 是"example://deepLink/{id}",其中{itemId}是一个参数。当用户在浏览器或其他应用中点击这个链接时,Android 系统会自动打开我们的应用,并跳转到对应的页面,同时将参数传递给我们的应用。我们可以在目标页面中通过arguments来获取这个参数。
3.2 共享元素转场
- 共享元素转场可以实现在不同
Activity或Fragment之间共享相同元素的动画效果,比如在列表页面点击某个item进入详情页面时,可以让这个item的图片或文字在两个页面之间平滑地过渡。以下是一个简单的实现示例:
<!-- 在layout文件中定义共享元素的id --><ImageView android:id="@+id/image_view"android:layout_width="wrap_content"android:layout_height="wrap_content"android:transitionName="shared_element" />
- 到 Fragment 目的地的共享元素过渡
val extras = FragmentNavigatorExtras(view1 to "shared_element")view.findNavController().navigate(R.id.confirmationAction, null, null, extras)
- 到 Activity 目的地的共享元素过渡
val option = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, imageView, "shared_element")findNavController().navigate(R.id.shareElementDialog, null, null, ActivityNavigatorExtras(option))
共享元素以程序化方式提供,而不是通过导航 XML 文件提供。activity 和 fragment 目的地各自都有 Navigator.Extras 接口的一个子类,它接受导航的附加选项,包括共享元素。您可以在调用 navigate() 时传递这些 Extras。
3.3 导航图的动态构建- 动态构建导航图可以在运行时根据不同的条件创建不同的导航图,例如用户登录状态不同、权限不同等情况下展示不同的导航结构。
- 下面是一个简单的动态构建导航图的示例:
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragmentval navController = navHostFragment.navControllerval graph = navInflater.inflate(R.navigation.dynamic_nav_graph)if (isLoggedIn) {graph.startDestination = R.id.homeFragment} else {graph.startDestination = R.id.loginFragment}if (hasAdminPermissions) {val adminNode = NavGraphNavigator(navController.navigatorProvider.getNavigator(NavGraphNavigator::class.java)).createDestination()adminNode.id = R.id.adminFragmentadminNode.setClassName("com.example.app.AdminFragment")graph.addDestination(adminNode)graph.addEdge(R.id.homeFragment, R.id.adminFragment)}navController.graph = graph
在上面的示例中,我们首先获取到了当前的NavController和NavInflater,然后通过NavInflater.inflate方法来加载我们的动态导航图。接着,我们根据不同的条件设置了导航图的起始目的地,并且在有管理员权限的情况下动态添加了一个目的地,并且添加了一条边来连接这个目的地和主页。最后,我们将构建好的导航图设置到NavController中即可。
四. 导航组件的最佳实践
4.1 使用<include>标签
- 为每个模块定义单独的
NavGraph在大型项目中,最好为每个功能模块定义自己的NavGraph,然后在根NavGraph中使用<include>标签将每个模块的NavGraph组合起来::
<navigation xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:id="@+id/root_navigation"><include android:id="@+id/home_navigation"app:layout="@navigation/home_navigation" /><include android:id="@+id/profile_navigation"app:layout="@navigation/profile_navigation" /> </navigation>
4.1 使用 ViewModel 和 LiveData
- 在 ViewModel 中使用 LiveData 对象来处理导航事件:
class MainViewModel : ViewModel() {private val navigateTo = MutableLiveData<NavDirections>()fun getNavigateTo(): LiveData<NavDirections> {return navigateTo}fun setNavigateTo(directions: NavDirections) {navigateTo.value = directions}}
- 在 Fragment 中观察 LiveData 对象并处理导航事件:
class MainFragment : Fragment() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)viewModel.getNavigateTo().observe(this) {Navigation.findNavController(requireView()).navigate(it)}}fun clickJump() {viewModel.setNavigateTo(MainFragmentDirections.actionMainToNavigationActivity())}}
注意这里我们使用了 Safe Args 实现类型安全的导航,在目的地之间导航,官方也是建议使用 Safe Args Gradle 插件。此插件可生成简单的对象和构建器类,以便在目的地之间实现类型安全的导航。我们强烈建议您在导航以及在目的地之间传递数据时使用 Safe Args。
如需将 Safe Args 添加到您的项目,请在顶层 build.gradle 文件中包含以下 classpath:
buildscript {dependencies {def nav_version = "2.5.3"classpath("androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version")}
}
然后再将以下行添加到应用或模块的 build.gradle 文件中:
plugins {id("androidx.navigation.safeargs.kotlin")
}
五. 总结
5.1 导航组件的优势和适用场景:
| 优势 | 适用场景 |
|---|---|
| 1. 提供一致的导航体验 | 适用于需要在应用中引入多个页面的场景 |
| 2. 简化导航逻辑 | 适用于需要在应用中进行复杂的导航操作的场景 |
| 3. 可自定义外观和行为 | 适用于需要根据应用需求自定义导航栏的场景 |
| 4. 支持深层链接 | 适用于需要在应用中支持深层链接的场景 |
5.2 导航组件的最佳实践- 在使用导航组件时,应该尽量减少手动操作 Fragment 事务,而是使用导航组件提供的 API 进行操作,以避免出现不必要的错误。
- 在设计导航图时,应该尽量将功能相似的页面放在同一个导航图中,以便于管理和维护。
- 在使用 Safe Args 插件传递参数时,应该尽量使用安全的类型,以避免出现类型转换错误。
以上就是对 Android Navigation 的探索与实践的过程,上述示例 + 扩展(结合 BottomNavigationView 和 DrawerLayout)请参考 https://github.com/smallmarker/JetPackPractice
相关文章:
Android Jetpack - Navigation 组件:进行应用程序导航
一. Navigation 组件的介绍 1.1 什么是 Navigation 组件 Navigation 组件是一种 Android Jetpack 库,它可以帮助开发者轻松地实现应用程序中的导航功能。导航组件包含多个类和组件,包括导航图、目的地、导航控制器等,可以帮助我们管理应用程…...
MySQL的binlog原理和它的几种使用方法
MySQL中的二进制日志(binlog)是一种用于记录数据库操作的日志文件,它可以记录MySQL服务器接收到的所有修改数据库的语句,例如INSERT、UPDATE和DELETE等语句。二进制日志对于备份和恢复数据库、复制数据库和进行数据分析等操作非常…...
40岁以上的程序员还容易找到工作吗?聊聊我自己的亲身经历
今天我们来讨论一个比较热门的话题,那就是程序员。如果到了40岁以上还容易找到工作吗?这个问题呢,其实是一个非常现实的问题,也是我们程序员非常关心的一个问题。因为我们每一个程序员,他都会有到40岁的那一天。 首先…...
Class类
package com.hspedu.reflection.class_;import com.hspedu.Cat;import java.util.ArrayList;/*** author 韩顺平* version 1.0* 对Class类特点的梳理*/ public class Class01 {public static void main(String[] args) throws ClassNotFoundException {//看看Class类图//1. Cla…...
Python小姿势 - 可选知识点:
可选知识点: 列表推导式 列表和字典推导式 字典推导式 生成器表达式 带条件的生成器表达式 解析XML 解析JSON 使用Requests和BeautifulSoup爬虫 Python并发编程 Python多线程编程 Python多进程编程 Python异步编程 Python装饰器 Python闭包 Python模块化 Python类和…...
Javaee Spring的AOP简介
一.Spring的AOP简介 1.1 什么是AOP AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代 理实现程序功能的统一维护的一种技术。AOP 是 OOP 的延续,是软件开发中的一个热点,也是…...
基于ansible初始化linux服务器基础环境。
大家好,今天我要和大家分享一个关于搭建centos环境的新方法。 以前我们经常会看到一些文章介绍如何搭建centos环境,但很多时候都会出现一些问题。不过现在有了一种新的方法,就是使用ansible脚本来实现。 虽然这种方法仅适用于centos7&#…...
leetcode-数据库题
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 175. 组合两个表176. 第二高的薪水177. 第N高的薪水178. 分数排名181. 超过经理收入的员工182. 查找重复的电子邮箱183. 从不订购的客户 175. 组合两个表 select p…...
[元来学NVMe协议] NVMe IO 指令集(NVM 指令集)| Flush 命令
声明 主页:元存储的博客_CSDN博客 依公开知识及经验整理,如有误请留言。 个人辛苦整理,付费内容,禁止转载。 内容摘要 前言 NVMe2.0 定义的三类命令集: 管理命令集、IO命令集、Fabrics命令集 Admin Command Set (管理命令集):用于控制器的管理,如创建/销毁IO提交队列…...
信息的相关性和冗余度:信息在整个文明中的作用
文章目录 I 古埃及的象形文字1.1 罗塞塔石碑1.2 古埃及文字音节和希腊字母的对应表1.3 破解古埃及文字 I 古埃及的象形文字 1.1 罗塞塔石碑 这个石碑是在公元前196年埃及国王托勒密五世加冕一周年的诏书。 在此前大约一百年,埃及已经被来自希腊北方城邦的亚历山大…...
python数据结构与算法-动态规划(最长公共子序列)
一、最长公共子序列问题 1、问题概念 一个序列的子序列是在该序列中删去若干元素后得 到的序列。 例如:"ABCD”和“BDF”都是“ABCDEFG”的子序列。 最长公共子序列(LCS) 问题: 给定两个序列X和Y,求X和Y长度最大的公共子字列。 例:X"ABBCBDE”…...
Java版企业电子招投标系统源码 Spring Cloud+Spring Boot 电子招标采购系统功能清单
一、立项管理 1、招标立项申请 功能点:招标类项目立项申请入口,用户可以保存为草稿,提交。 2、非招标立项申请 功能点:非招标立项申请入口、用户可以保存为草稿、提交。 3、采购立项列表 功能点:对草稿进行编辑&#x…...
【c语言】函数的基本概念 | 函数堆栈调用原理
创作不易,本篇文章如果帮助到了你,还请点赞支持一下♡>𖥦<)!! 主页专栏有更多知识,如有疑问欢迎大家指正讨论,共同进步! 给大家跳段街舞感谢支持!ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ…...
Vue.prototype 详解及使用
前言: 我们可能会在很多组件里用到数据/实用工具,但是不想污染全局作用域。这种情况下,可以通过在原型上定义它们使其在每个 Vue 的实例中可用。 1. 基本示例 在main.js中添加一个变量到 Vue.prototype Vue.prototype.$appName My App这…...
音视频八股文(3)--ffmpeg常见命令(2)
07-ffplay命令播放媒体 播放本地文件 播放本地 MP4 视频文件 test.mp4 的命令,从第 2 秒位置开始播放,播放时长为 10 秒,并且在窗口标题中显示 “test time”: ffplay -window_title "test time" -ss 2 -t 10 -autoe…...
使用bert4keras出现的问题(Process finished with exit code -1073741819 (0xC0000005))
1、环境 python 3.7.12 tensorflow 1.15 keras 2.3.1 bert4keras 0.9.7 protobuf 3.19.0 numpy 1.16.5 2、出现问题 numpy版本不兼容问题所以你就直接按照我的版本就可以了(numpy 1.16.5) Process finished with exit code -1073741819 (0xC0000005) …...
python协程实战
协程简介 协程(Coroutine)又称微线程、纤程,协程不是进程或线程,其执行过程类似于 Python 函数调用,Python 的 asyncio 模块实现的异步IO编程框架中,协程是对使用 async 关键字定义的异步函数的调用; 一个进程包含多个线程,类似…...
【论文笔记】VideoGPT: Video Generation using VQ-VAE and Transformers
论文标题:VideoGPT: Video Generation using VQ-VAE and Transformers 论文代码:https://wilson1yan. github.io/videogpt/index.html. 论文链接:https://arxiv.org/abs/2104.10157 发表时间: 2021年9月 Abstract 作者提出了…...
scala之基础面向对象
scala 既是面向对象 也是函数式编程 从Java 发展而来,依赖JVM环境 一、 scala 在linux中运行 scala 模式中直接编写运行 scala文件,load执行 scala编译程序 编译 运行 scala java 二、scala 数据类型 基础数据类型 val 不可变变量 函数式编程 …...
Qt5.12实战之多线程编程概念
1.为什么要使用多线程? a. 基于线程,同时处理多个任务,软件响应更灵敏 b.充分利用CPU的多核心功能增加应用运行效率 c.多线程在同一进程间使用共享通信更加高效 d.多个线程之间进行切换比多个进程之间进行切换,线程开销更少. 2.操作系统与进程关系 a. MS-DOS系统 属于单进程…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...
html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...
深度学习习题2
1.如果增加神经网络的宽度,精确度会增加到一个特定阈值后,便开始降低。造成这一现象的可能原因是什么? A、即使增加卷积核的数量,只有少部分的核会被用作预测 B、当卷积核数量增加时,神经网络的预测能力会降低 C、当卷…...
免费数学几何作图web平台
光锐软件免费数学工具,maths,数学制图,数学作图,几何作图,几何,AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...
