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系统 属于单进程…...

格式化数据恢复怎么做?超实用的3种方法在这!
案例:格式化数据怎么恢复 【我的电脑前段时间中病毒了,无奈之下我只能将其格式化,但是很多重要的文件和图片之类的也一起被删除了,有什么方法可以恢复这些格式化的数据吗?非常着急!】 格式化数据恢复&…...

【Java|golang】1105. 填充书架---动态规划
给定一个数组 books ,其中 books[i] [thicknessi, heighti] 表示第 i 本书的厚度和高度。你也会得到一个整数 shelfWidth 。 按顺序 将这些书摆放到总宽度为 shelfWidth 的书架上。 先选几本书放在书架上(它们的厚度之和小于等于书架的宽度 shelfWidt…...

linux基础命令
linux基础命令 一、linux命令 熟悉账务linux命令对运维的好处是巨大的,只有熟悉了命令咱们在运维的操作上才能如鱼得水。 系统信息 arch #显示机器的处理器架构(1) uname -m #显示机器的处理器架构(2) uname -r #显示正在使用的内核版本 dmidecode -q …...

【三十天精通Vue 3】 第十八天 Vue 3的国际化详解
✅创作者:陈书予 🎉个人主页:陈书予的个人主页 🍁陈书予的个人社区,欢迎你的加入: 陈书予的社区 🌟专栏地址: 三十天精通 Vue 3 文章目录 引言一、Vue 3 国际化概述1.1 国际化的概念1.2 国际化的作用1.3 V…...

02 - 学会提问
学会提问 一、引言 1.1 GPT简介 GPT(Generative Pre-trained Transformer)是一种基于Transformer架构的大型预训练语言模型。 凭借其强大的文本生成、理解和处理能力,GPT已在诸如自然语言处理、机器翻译、文本摘要等多个领域取得了显著的…...

Java经典的Main方法面试题
mian方法是做什么用的? main方法是Java程序的入口方法,JVM在运行的时候会首先查找main方法不用main方法如何运行一个类? 不行,没有main方法我们不能运行Java类 在Java7之前,你可以通过使用静态初始化运行Java类。但是&…...

世界大学电子电气工程TOP10,国内大学哪家强?
EE究竟是什么专业 ? 在中国,工程系中跟电相关的专业,一般都切分得非常细。有电子工程、电气工程、通信工程、信息工程、自动化、测控仪器等。但在国外,一般把这些领域都归类到 Electrical Engineering 中,也就是我们常说的EE。 …...

5.3 牛顿-科茨公式
学习目标: 理解微积分基础知识,例如导数和微分的概念。学习牛顿-科茨公式的推导过程。这个公式实际上是使用泰勒公式对被积函数进行展开,并使用微积分的基本原理进行简化得到的。学习如何使用牛顿-科茨公式进行数值积分。这通常涉及到将被积…...

全注解下的SpringIoc 续2-bean的生命周期
spring中bean的生命周期 上一个小节梳理了一下Spring Boot的依赖注入的基本知识,今天来梳理一下spring中bean的生命周期。 下面,让我们一起看看bean在IOC容器中是怎么被创建和销毁的。 bean的生命周期大致分为四个部分: #mermaid-svg-GFXNEU…...

【VQ-VAE代码实战】Neural Discrete Representation Learning
【VQ-VAE代码实战】Neural Discrete Representation Learning 0、前言1、简介2、Basic IdeaLoss3、代码Load DataVector Quantizer LayerEncoder & Decoder ArchitectureTrainPlot LossView ReconstructionsView EmbeddingReference0、前言 论文地址:基于神经网络的,离散…...