Android Navigation的四大要点你都知道吗?
迪丽瓦拉
2025-06-01 01:28:26
0

在JetPack中有一个组件是Navigation,顾名思义它是一个页面导航组件,相对于其他的第三方导航,不同的是它是专门为Fragment的页面管理所设计的。它对于单个Activity的App来说非常有用,因为以一个Activity为架构的App页面的呈现都是通过不同的Fragment来展示的。所以对于Fragment的管理至关重要。通常的实现都要自己维护Fragment之间的栈关系,同时要对Fragment的Transaction操作非常熟悉。为了降低使用与维护成本,所以就有了今天的主角Navigation。

对于Navigation的使用,主要有以下四点:

Navigation的基本配置

在使用之前需要引入Navigation的依赖,然后我们需要为Navigation创建一个配置文件,它将位于res/navigation/nav_graph.xml。

再来编写它们的配置文件👇

        ...

页面标签主要包含navigation、fragment与action

  • navigation:定义导航栈,可以进行嵌套定义,各个navigation相互独立。它有一个属性startDestination用来定义导航栈的根入口fragment
  • fragment: 顾名思义fragment页面。通过name属性来定义关联的fragment
  • action:意图,可以理解为Intent,即跳转的行为。通过destination来关联将要跳转的目标fragment。

以上是nav_graph.xml的基本配置。

在配置完之后,我们还需要将其关联到Activity中。因为所有的Fragment都离不开Activity。

Navigation为我们提供了两个配置参数: defaultNavHost与navGraph,所以在Activity的xml中需要如下配置👇



  • defaultNavHost: 将设备的回退操作进行拦截,并将其交给Navigation进行管理。
  • navGraph: Navigation的配置文件,即上面我们配置的nav_graph.xml文件

除此之外,fragment的name属性必须为NavHostFragment,因为它会作为我们配置的所有fragment的管理者。具体通过内部的NavController中的NavigationProvider来获取Navigator抽象实例,具体实现类是FragmentNavigator,所以最终通过它的navigate方法进行创建我们配置的Fragment,并且添加到NavHostFragment的FrameLayout根布局中。

此时如果我们直接运行程序后发现已经可以看到入口页面WelcomeFragment

在这里插入图片描述

但点击register等操作你会发现点击跳转无效,所以接下来我们需要为其添加跳转

Navigation的跳转与数据传递

由于我们之前已经在nav_graph.xml中定义了action,所以跳转的接入非常方便,每一个action的关联跳转只需一行代码👇

class WelcomeFragment : Fragment() {override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {return inflater.inflate(R.layout.fragment_welcome, container, false).apply {register_bt.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_go_to_register_page))stroll_bt.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_go_to_order_list_page))}}
}

代码中的id就是配置的action的id,内部原理是先获取到对应的NavController,通过点击的view来遍历找到最外层的parent view,因为最外层的parent view会在配置文件导入时,即NavHostFragment中的onViewCreated方法中进行关联对应的NavController👇

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);if (!(view instanceof ViewGroup)) {throw new IllegalStateException("created host view " + view + " is not a ViewGroup");}Navigation.setViewNavController(view, mNavController);// When added programmatically, we need to set the NavController on the parent - i.e.,// the View that has the ID matching this NavHostFragment.if (view.getParent() != null) {View rootView = (View) view.getParent();if (rootView.getId() == getId()) {Navigation.setViewNavController(rootView, mNavController);}}
}

然后再调用navigate进行页面跳转处理,最终通过FragmentTransaction的replace进行Fragment替换👇

-------------- NavController ------------------private void navigate(@NonNull NavDestination node, @Nullable Bundle args,@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {boolean popped = false;if (navOptions != null) {if (navOptions.getPopUpTo() != -1) {popped = popBackStackInternal(navOptions.getPopUpTo(),navOptions.isPopUpToInclusive());}}Navigator navigator = mNavigatorProvider.getNavigator(node.getNavigatorName());Bundle finalArgs = node.addInDefaultArgs(args);# ---- 关键代码 -------NavDestination newDest = navigator.navigate(node, finalArgs,navOptions, navigatorExtras);....
}-------------- FragmentNavigator ------------------public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {if (mFragmentManager.isStateSaved()) {Log.i(TAG, "Ignoring navigate() call: FragmentManager has already"+ " saved its state");return null;}String className = destination.getClassName();if (className.charAt(0) == '.') {className = mContext.getPackageName() + className;}final Fragment frag = instantiateFragment(mContext, mFragmentManager,className, args);frag.setArguments(args);final FragmentTransaction ft = mFragmentManager.beginTransaction();int enterAnim = navOptions != null ? navOptions.getEnterAnim() : -1;int exitAnim = navOptions != null ? navOptions.getExitAnim() : -1;int popEnterAnim = navOptions != null ? navOptions.getPopEnterAnim() : -1;int popExitAnim = navOptions != null ? navOptions.getPopExitAnim() : -1;if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {enterAnim = enterAnim != -1 ? enterAnim : 0;exitAnim = exitAnim != -1 ? exitAnim : 0;popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;popExitAnim = popExitAnim != -1 ? popExitAnim : 0;ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);}# ------ 关键代码 ------ft.replace(mContainerId, frag);ft.setPrimaryNavigationFragment(frag);...
}

源码就分析到这里了,以上是页面的无参跳转,那么对于有参跳转又该如何呢?

大家想到的应该都是bundle,将传递的数据填入到bundle中。没错Navigator提供的navigate方法可以进行传递bundle数据👇

findNavController().navigate(R.id.action_go_to_shop_detail_page, bundleOf("title" to "I am title"))

这种传统的方法在传递数据类型上并不能保证其一致性,为了减少人为精力上的错误,Navigation提供了一个Gradle插件,专门用来保证数据的类型安全。

使用它的话需要引入该插件,方式如下👇

buildscript {repositories {google()}dependencies {def nav_version = "2.1.0"classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"}
}

最后再到app下的build.gradle中引入该插件👇

apply plugin: "androidx.navigation.safeargs.kotlin"

而它的使用方式也很简单,首先参数需要在nav_graph.xml中进行配置。👇


现在我们从ShopListFragment跳转到ShopDetailFragment,需要在ShopListFragment的对应action中添加argument,声明对应的参数类型与参数名,也可以通过defaultValue定义参数的默认值与nullable标明是否可空。对应的ShopDetailFragment接收参数也是一样。

另外popUpTo与popUpToInclusive属性是为了实现跳转到CartFragment时达到SingleTop效果。

下面我们直接看在代码中如何使用这些配置的参数,首先是在ShopListFragment中👇

holder.item.setOnClickListener(Navigation.createNavigateOnClickListener(ShopListFragmentDirections.actionGoToShopDetailPage(shopList[position])))

还是创建一个createNavigateOnClickListener,只不过现在传递的不再是跳转的action id,而是通过插件自动生成的ShopListFragmentDirections.actionGoToShopDetailPage方法。一旦我们如上配置了argument,插件就会自动生成一个以[类名]+Directions的类,而自动生成的类本质是做了跳转与参数的封装,源码如下👇

class ShopListFragmentDirections private constructor() {private data class ActionGoToShopDetailPage(val title: String) : NavDirections {override fun getActionId(): Int = R.id.action_go_to_shop_detail_pageoverride fun getArguments(): Bundle {val result = Bundle()result.putString("title", this.title)return result}}companion object {fun actionGoToShopDetailPage(title: String): NavDirections = ActionGoToShopDetailPage(title)}
}

本质是将action id与argument封装成一个NavDirections,内部通过解析它来获取action id与argument,从而执行跳转。

而对于接受方ShopDetailFragment,插件页面自动帮我们生成一个ShopDetailFragmentArgs,以[类名]+Args的类。所以我们需要做的也非常简单👇

class ShopDetailFragment : Fragment() {private val args by navArgs()override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {return inflater.inflate(R.layout.fragment_shop_detail, container, false).apply {title.text = args.titleadd_cart.setOnClickListener(Navigation.createNavigateOnClickListener(ShopDetailFragmentDirections.actionGoToCartPage()))}}}

通过navArgs来获取ShopDetailFragmentArgs对象,它其中包含了传递过来的页面数据。

Navigation的页面动画

在action中不仅可以配置跳转的destination,还可以定义对应页面的转场动画,使用非常简单👇


...

对应四个动画配置参数

  • enterAnim: 配置进场时目标页面动画
  • exitAnim: 配置进场时原页面动画
  • popEnterAnim:配置回退pop时目标页面动画
  • popExitAnim: 配置回退pop时原页面动画

通过上面的配置你可以看到如下效果👇
在这里插入图片描述

Navigation的deepLink

我们回想一下对于多个Activity我需要实现deepLink效果,应该都是在AndroidManifest.xml中进行配置scheme、host等。而对于单个Activity也需要实现类似的效果,Navigation也提供了对应的实现,而且操作更简单。

Navigation提供的是deepLink标签,可以直接在nav_graph.xml进行配置,例如👇

 

上面通过deepLink我配置了一个跳转到注册页RegisterFragment,写法非常简单,直接配置uri即可;同时还可以通过占位符配置传递参数,例如👇


这时我们就可以在注册页面通过argument获取key为id的数据。

当然要实现上面的效果,我们还需要一个前提,需要在AndroidManifest.xml中将我们的deepLink进行配置,在Activity中使用nav-graph标签👇

 ...

现在只需将文章中的demo安装到手机上,再点击下面的link

jump to register api

之后就会启动App,并定位到注册界面。是不是非常简单呢?

最后我们再来看下效果👇
在这里插入图片描述

有关Navigation暂时就到这里,通过这篇文章,希望你能够熟悉运用Navigation,并且发现单Activity的魅力。
如果这篇文章对你有所帮助,你可以顺手点赞、关注一波,这是对我最大的鼓励!

相关资料:

Android进阶学习笔记:docs.qq.com/doc/DWHFqVHBMVEJPWUx
Android工程师面试题钢:docs.qq.com/doc/DWGZIRFh5VEtYWE1D
Android音视频入门到高阶学习笔记:docs.qq.com/doc/DWFFWZHNPTHZVdHFX
Android开源框架设计思想解读:docs.qq.com/doc/DWHlGYUdseVhsSUda

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
A.机器学习入门算法(三):基... 机器学习算法(三):K近邻(k-nearest neigh...
数字温湿度传感器DHT11模块... 模块实例https://blog.csdn.net/qq_38393591/article/deta...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
Redis 所有支持的数据结构... Redis 是一种开源的基于键值对存储的 NoSQL 数据库,支持多种数据结构。以下是...
win下pytorch安装—c... 安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch...
MySQL基础-多表查询 文章目录MySQL基础-多表查询一、案例及引入1、基础概念2、笛卡尔积的理解二、多表查询的分类1、等...
keil调试专题篇 调试的前提是需要连接调试器比如STLINK。 然后点击菜单或者快捷图标均可进入调试模式。 如果前面...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
IHome主页 - 让你的浏览... 随着互联网的发展,人们越来越离不开浏览器了。每天上班、学习、娱乐,浏览器...
TCP 协议 一、TCP 协议概念 TCP即传输控制协议(Transmission Control ...
营业执照的经营范围有哪些 营业执照的经营范围有哪些 经营范围是指企业可以从事的生产经营与服务项目,是进行公司注册...
C++ 可变体(variant... 一、可变体(variant) 基础用法 Union的问题: 无法知道当前使用的类型是什...
血压计语音芯片,电子医疗设备声... 语音电子血压计是带有语音提示功能的电子血压计,测量前至测量结果全程语音播报࿰...
MySQL OCP888题解0... 文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3...
【2023-Pytorch-检... (肆十二想说的一些话)Yolo这个系列我们已经更新了大概一年的时间,现在基本的流程也走走通了,包含数...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
记录--我在前端干工地(thr... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前段时间接触了Th...
43 openEuler搭建A... 文章目录43 openEuler搭建Apache服务器-配置文件说明和管理模块43.1 配置文件说明...