【Android Jetpack】新一代导航管理——Navigation

2023-10-31

前言

不知道小伙伴们是否注意到,用AS创建一个默认的新项目后,MainActivity已经有了很大的不同,最大的区别就是新增加了两个Fragment,同时我们注意到这两个Fragment之间跳转的时候并没有使用之前FragmentTransaction这种形式,而是使用了NavController和NavHostFragment,这就是新一代导航管理————Navigation。

项目中依赖Navigation:

implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'

使用Navigation

先来看看如果使用Navigation

创建导航视图

新建一个Android Resource File,类型选择Navigation即可,输入名称后我们就创建了一个导航视图。

添加页面

在导航试图中,我们可以通过添加activity/fragment等标签手动添加页面,也支持在Design页面中通过界面添加,如下:

B88C2B4A-4900-41DF-9BDC-7972F73190D2.png

注意:这样添加后手动修改一下label。如果我们将Navigation与ToolBar连接,会在标题栏这个label。

示例中添加了两个页面,添加后代码如下:

<?xml version="1.0" encoding="utf-8"?>
<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">

    <fragment
        android:id="@+id/FirstFragment"
        android:name="com.xxx.xxx.FirstFragment"
        android:label="@string/first_fragment_label"
        tools:layout="@layout/fragment_first">
    </fragment>
    <fragment
        android:id="@+id/SecondFragment"
        android:name="com.xxx.xxx.SecondFragment"
        android:label="@string/second_fragment_label"
        tools:layout="@layout/fragment_second">
    </fragment>
</navigation>

除了添加Fragment和Activity,Google还提供了一个占位符placeholder,添加加完代码如下:

<fragment android:id="@+id/placeholder" />

用于暂时占位以便后面可以替换为Fragment和Activity

添加导航

添加完页面后,我们还需要添加页面之间的导航,可以手动添加action标签,当然也可以通过拖拽来实现,如下:

ABE13B79-D136-4450-A454-B4C905733284.png

这样我们就添加了一个从FirstFragment导航到SecondFragment的动作,我们再添加一个逆向的动作,最终的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<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">

    <fragment
        android:id="@+id/FirstFragment"
        android:name="com.xxx.xxx.FirstFragment"
        android:label="@string/first_fragment_label"
        tools:layout="@layout/fragment_first">

        <action
            android:id="@+id/action_FirstFragment_to_SecondFragment"
            app:destination="@id/SecondFragment" />
    </fragment>
    <fragment
        android:id="@+id/SecondFragment"
        android:name="com.xxx.xxx.SecondFragment"
        android:label="@string/second_fragment_label"
        tools:layout="@layout/fragment_second">

        <action
            android:id="@+id/action_SecondFragment_to_FirstFragment"
            app:destination="@id/FirstFragment" />
    </fragment>
</navigation>

注意占位符placeholder同样支持添加导航。

这样就实现了两个页面间的导航,最后还需要为这个navigation设置id和默认页面startDestination,如下:

<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">

这样导航视图就创建完成了。可以看到Google力图通过可视化工具来简化开发工作,这对我们开发者来说非常有用,可以省去大量编写同质化代码的时间。

添加导航宿主

下一步我们需要向Activity中添加导航宿主,导航宿主是一个空页面,必须实现NavHost接口,我们使用Navigation提供的默认NavHost————NavHostFragment即可。如下:

<fragment
    android:id="@+id/nav_host_fragment_content_main"
    android:name="androidx.navigation.fragment.NavHostFragment"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:defaultNavHost="true"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:navGraph="@navigation/nav_graph" />

在Activity的视图中添加一个fragment标签,

  • android:name设置为实现类,即NavHostFragment;
  • app:navGraph设置为刚才新建的导航视图。

注意app:defaultNavHost=“true”,设置为true后表示将这个NavHostFragment设置为默认导航宿主,这样就会拦截系统的返回按钮事件。同一布局中如果有多个导航宿主(比如双窗口)则必须制定一个为默认的导航宿主。

这时候我们运行应用,就可以发现Activity中已经可以展示FirstFragment了。

实现导航

我们还需要为两个fragment添加按钮,是其点击跳转到另外一个页面,代码如下:

binding.buttonFirst.setOnClickListener {
    findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
}

示例中是FirstFragment中的一个按钮,点击时执行了id为action_FirstFragment_to_SecondFragment的动作,这个是我们之前在导航视图中配置好的,会导航到SecondFragment。

注意首先通过 findNavController() 来获取一个NavController对象,然后调用它的navigate函数即可,当然这个函数有多种重载,比如可以传递参数,如下:

public void navigate(@IdRes int resId, @Nullable Bundle args) {

这里不一一列举了,大家自行查看源码即可。

可以看到使用Navigation代码精简了很多,只需要一行代码执行一个函数即可。

原理解析

我们重点来看看 findNavController() ,它是一个扩展函数,如下:

fun Fragment.findNavController(): NavController =
        NavHostFragment.findNavController(this)

实际上是NavHostFragment的一个静态函数findNavController:

@NonNull
public static NavController findNavController(@NonNull Fragment fragment) {
    ...
    View view = fragment.getView();
    if (view != null) {
        return Navigation.findNavController(view);
    }

    // For DialogFragments, look at the dialog's decor view
    Dialog dialog = fragment instanceof DialogFragment
            ? ((DialogFragment) fragment).getDialog()
            : null;
    if (dialog != null && dialog.getWindow() != null) {
        return Navigation.findNavController(dialog.getWindow().getDecorView());
    }

    throw new IllegalStateException("Fragment " + fragment
            + " does not have a NavController set");
}

通过源码可以看到最终是执行了Navigation的 findNavController 函数,它的代码如下:

@NonNull
public static NavController findNavController(@NonNull View view) {
    NavController navController = findViewNavController(view);
    ...
    return navController;
}

这里是通过 findViewNavController 函数来获取NavController的,它的代码如下:

@Nullable
private static NavController findViewNavController(@NonNull View view) {
    while (view != null) {
        NavController controller = getViewNavController(view);
        if (controller != null) {
            return controller;
        }
        ViewParent parent = view.getParent();
        view = parent instanceof View ? (View) parent : null;
    }
    return null;
}

这里可以看到通过view来获取NavController,如果没有则向上层查找(父view)直到找到或到根结点。 getViewNavController 代码如下:

@Nullable
private static NavController getViewNavController(@NonNull View view) {
    Object tag = view.getTag(R.id.nav_controller_view_tag);
    NavController controller = null;
    if (tag instanceof WeakReference) {
        controller = ((WeakReference<NavController>) tag).get();
    } else if (tag instanceof NavController) {
        controller = (NavController) tag;
    }
    return controller;
}

看到这里获取view中key为 R.id.nav_controller_view_tag 的tag,这个tag就是NavController,那么这个tag又从哪来的?

NavHostFragment

其实就是上面我们提到导航宿主————NavHostFragment,在他的 onViewCreated 中可以看到如下代码:

@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) {
        mViewParent = (View) view.getParent();
        if (mViewParent.getId() == getId()) {
            Navigation.setViewNavController(mViewParent, mNavController);
        }
    }
}

这里的 mNavController 是在NavHostFragment的 onCreate 中创建出来的,是一个NavHostController对象,它继承NavController,所以就是NavController。

可以看到 onViewCreated 中调用了Navigation的 setViewNavController 函数,它的代码如下:

public static void setViewNavController(@NonNull View view,
        @Nullable NavController controller) {
    view.setTag(R.id.nav_controller_view_tag, controller);
}

这样就将NavController加入tag中了,通过 findNavController() 就可以得到这个NavController来执行导航了。

注意在 onViewCreated 中不仅为Fragment的View添加了tag,同时还为其父View也添加了,这样做的目的是在Activity中也可以获取到NavController,这点下面就会遇到。

支持ToolBar

Google提供了Navigation与ToolBar连接的功能,代码如下:

val navController = findNavController(R.id.nav_host_fragment_content_main)
appBarConfiguration = AppBarConfiguration(navController.graph)
setupActionBarWithNavController(navController, appBarConfiguration)

上面我们提到,如果Navigation与ToolBar连接,标题栏会自动显示在导航视图中设定好的label。

注意这里的 findNavController 是Activity的扩展函数,它最终一样会调用Navigation的对应函数,所以与Fragment的流程是一样的。而上面我们提到了,在NavHostFragment中给上层View也设置了tag,所以在这里才能获取到NavController。

除了这个,我们还可以发现当在切换页面的时候,标题栏的返回按钮也会自动显示和隐藏。当导航到第二个页面SecondFragment,返回按钮显示;当回退到首页时,返回按钮隐藏。

但是此时返回按钮点击无效,因为我们还需要重写一个函数:

override fun onSupportNavigateUp(): Boolean {
    val navController = findNavController(R.id.nav_host_fragment_content_main)
    return navController.navigateUp(appBarConfiguration)
            || super.onSupportNavigateUp()
}

这样当点击标题栏的返回按钮时,会执行NavController的 navigateUp 函数,就会退回到上一页面。

总结

可以看出通过Google推出的这个Navigation,可以让开发者更加优雅管理导航,同时也简化了这部分的开发工作,可视化功能可以让开发者更直观的进行管理。除此之外,Google还提供了Safe Args Gradle插件,该插件可以生成简单的对象和构建器类,这些类支持在目的地之间进行类型安全的导航和参数传递。关于这个大家可以参考官方文档即可。

更多JetPack文章:
【Jetpack】ActivityResult介绍及原理分析

Android Jetpack:利用Palette进行图片取色

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Android Jetpack】新一代导航管理——Navigation 的相关文章

  • 如何为ScrollView放置固定图像背景?

    我应该想要滚动视图滚动 而不是背景中的图像 将图像添加到滚动视图框架之前的视图层次结构的较高位置
  • 找不到参数的方法 dependencyResolutionManagement()

    我正在尝试使用老师给我的一个项目 但它显示了一个错误 Settings file Users admin AndroidStudioProjects HTTPNetworking settings gradle line 1 A probl
  • 如何在 Android 中保存相机的临时照片?

    在尝试从相机拍照并将其保存到应用程序的缓存文件夹中时 我没有得到任何可见的结果 应用程序不会崩溃 但在 LogCat 上 当我尝试将 ImageView src 字段设置为刚刚获取的文件的 URI 时 我收到此消息 09 17 14 03
  • 卸载后 Web 应用程序不显示“添加到主屏幕”

    这是我第一次创建网络应用程序 我设法解决了这个问题 所以我得到了实际的 chrome 提示 将其添加到主屏幕 然后我从手机上卸载了该网络应用程序 因为我想将其展示给我的同事 但是 屏幕上不再出现提示 问题 这是有意为之的行为还是我的应用程序
  • Android 30+ 中的视频捕获意图 - 只有所有者才能与待处理项目交互

    我正在尝试在我的应用程序上捕获视频 它可以在 android API 30 以下运行 但不能在 30 以上运行 似乎在 sdk 30 之后 android 不允许完全读取外部存储 作用域存储 我目前遇到这个错误 java lang Ille
  • Android 后退按钮无法与 Flutter 选项卡内的导航器配合使用

    我需要在每个选项卡内有一个导航器 因此当我推送新的小部件时 选项卡栏会保留在屏幕上 代码运行得很好 但是 android 后退按钮正在关闭应用程序而不是运行 Navigator pop import package flutter mate
  • java.lang.NoClassDefFoundError:org.apache.batik.dom.svg.SVGDOMImplementation

    我在链接到我的 Android LibGDX 项目的 Apache Batik 库时遇到了奇怪的问题 但让我们从头开始 在 IntelliJ Idea 中我有一个项目 其中包含三个模块 Main Android 和 Desktop 我强调的
  • android xamarin 中的 reCaptcha

    我想在 Xamarin android 应用程序中实现验证码 我抓住了这个在 Android 中集成 googles reCaptcha 验证 https www c sharpcorner com article how to integ
  • Android Activity 生命周期函数基础知识

    我正在测试这段代码 它显示活动所处的状态 public class Activity101Activity extends Activity String tag Lifecycle Called when the activity is
  • Android MediaExtractor seek() 对 MP3 音频文件的准确性

    我在使用 Android 时无法在eek 上获得合理的准确度MediaExtractor 对于某些文件 例如this one http www archive org download emma solo librivox emma 01
  • 原色(有时)变得透明

    我正在使用最新的 SDK 版本 API 21 和支持库 21 0 2 进行开发 并且在尝试实施新的材料设计指南时遇到了麻烦 材料设计说我需要有我的primary color and my accent color并将它们应用到我的应用程序上
  • 如何在PreferenceActivity中添加工具栏

    我已经使用首选项创建了应用程序设置 但我注意到 我的 PreferenceActivity 中没有工具栏 如何将工具栏添加到我的 PreferenceActivity 中 My code 我的 pref xml
  • 在两个活动之间传输数据[重复]

    这个问题在这里已经有答案了 我正在尝试在两个不同的活动之间发送和接收数据 我在这个网站上看到了一些其他问题 但没有任何问题涉及保留头等舱的状态 例如 如果我想从 A 类发送一个整数 X 到 B 类 然后对整数 X 进行一些操作 然后将其发送
  • Android Studio 0.4.3 Eclipse项目没有gradle

    在此版本之前 在 Android Studio 中按原样打开 Eclipse 项目似乎很容易 无需任何转换 我更喜欢 Android Studio 环境 但我正在开发一个使用 eclipse 作为主要 IDE 的项目 我不想只为这个项目下载
  • 字符串数组文本格式化

    我有这个字符串 String text Address 1 Street nr 45 Address 2 Street nr 67 Address 3 Street nr 56 n Phone number 000000000 稍后将被使用
  • Android Studio - Windows 7 上的 Android SDK 问题

    我对 Google i o 2013 上发布的最新开发工具 Android Studio 有疑问 我已经成功安装了该程序并且能够正常启动 我可以导入现有项目并对其进行编辑 但是 当我尝试单击 SDK 管理器图标或 AVD 管理器图标时 或者
  • 如何根据 gradle 风格设置变量

    我想传递一个变量test我为每种风格设置了不同的值作为 NDK 的定义 但出于某种原因 他总是忽略了最后味道的价值 这是 build gradle apply plugin com android library def test andr
  • 一次显示两条Toast消息?

    我希望在一个位置显示一条 Toast 消息 并在另一位置同时显示另一条 Toast 消息 多个 Toast 消息似乎总是按顺序排队和显示 是否可以同时显示两条消息 是否有一种解决方法至少可以提供这种外观并且不涉及扰乱活动布局 Edit 看来
  • 实现滚动选择 ListView 中的项目

    我想使用 ListView 您可以在其中滚动列表来选择一个项目 它应该像一个 Seekbar 但拇指应该是固定的 并且您必须使用该栏来调整它 我面临的一个问题是 我不知道这种小部件是如何调用的 这使得我很难搜索 所以我制作了下面这张图片 以
  • Crashlytics 出现 Android Studio 构建错误

    我正在尝试将 CrashLytics 与 Android Studio 和 gradle 一起使用 但出现一个令人困惑的错误 java lang NoSuchMethodError 我的 build gradle 是 buildscript

随机推荐

  • C语言abs函数

    C语言编程入门教程 abs 函数是用来求整数的绝对值的 函数名 abs 功 能 求整数的绝对值 用 法 int abs int i 程序例 include
  • 树莓派换源、vim更新:树莓派更换国内可用镜像源

    参考 树莓派vim更新 树莓派更换国内可用镜像源 作者 丶PURSUING 发布时间 2021 02 01 22 40 48 网址 https blog csdn net weixin 44742824 article details 11
  • spark Dataframe数据处理常用方法总结

    以下方法使用scala df类型为Dataframe 常用的包 import org apache spark sql functions import spark implicits 1 新增一列 df df withColumn new
  • 半导体(Die及成品)产品标准大汇总包括可靠性等(IEC+JEDEC+EIAJ+AECQ+国标)

    半导体 Die及成品 产品标准大汇总包括可靠性等 IEC JEDEC EIAJ AECQ 国标 1 IEC标准 IEC 60747全系列 Semiconductor devices 半导体器件 包含全部38份最新英文标准文件 rar IEC
  • 21 个简洁的 JavaScript单行代码技巧

    JavaScript 发展至今已经变得越来越强大 且广泛用于前端和后端开发 作为一名前端程序员 不断的学习精进技巧 了解JS的最新发展也是非常必要的 而简洁的一行代码示例就是很好的方法 今天 我们有 21 个JavaScript单行代码技巧
  • 剑指offer: 最小k个元素

    思路 大顶堆 最小k个元 求前k个最小用最大堆 求前k个最大用最小堆 步骤如下 1 取数组前k个元素初始化堆 从最后一个非叶子节点开始到根节点来构建大顶堆 2 当某个元素大于堆顶元素时 直接抛弃 3 当某个元素小于堆顶元素时 替换堆顶元素
  • RecyclerView源码解析(四):RecyclerView对ViewHolder的回收

    RecyclerView源码解析 四 RecyclerView对ViewHolder的回收 导言 前面几篇文章我们已经介绍了RecyclerView绘图的三大流程和其四层缓存机制 不过对于我来说还有一个疑问 那就是RecyclerView是
  • Python正在消亡?致命弱点是否会让Python被新语言取代?

    被业界称为 瑞士军刀 的编程语言 可能会被更适合该任务的其他语言取代吗 自从1990年代初Python发布以来 它引起了很多热议 当然 编程社区花了至少20年的时间才逐渐注意到它的存在 而当它一旦开始流行起来 它的流行程度就远远超过了C C
  • 掌握Python的X篇_4+5_开发工具ipython与vscode的安装使用,作业

    本篇将会介绍两个工具的安装及使用来提高Python的编程效率 ipython 比python更好用的交互式开发环境 vscode 本身是文本编辑器 通过安装相关的插件vscode可以作为python集中开发环境使用 掌握Python的X篇
  • 【Servlet】HTTP的Servlet实现

    在JavaEE的Servlet规范中 实现HTTP协议由 javax servlet http HttpServlet类 实现 该类封装了一系列的请求和响应操作 使得开发者直接实现功能逻辑 而不必关乎网络连接 错误处理等 对于该类的众多AP
  • kafka集群搭建和使用Java写kafka生产者消费者

    转载自 http chengjianxiaoxue iteye com blog 2190488 1 kafka集群搭建 Java代码 1 zookeeper集群 搭建在110 111 112 2 kafka使用3个节点110 111 11
  • 数据挖掘背景知识2——数据挖掘可以做到什么 带给我们什么?

    本文是数据挖掘学习课堂笔记的一个补充 是一个了解级别的文章 欢迎各位大佬指出不足的地方 文章目录 数据挖掘的定义 数据挖掘与数据分析的区别 数据挖掘的应用场景 1 教育领域 2 风控领域 3 医疗领域 数据挖掘存在的问题 本文参考了CSDN
  • 什么是网络安全?

    一 什么是网络安全 1 网络安全 网络安全指网络系统中的硬件 软件以及系统中的数据受到保护 不因偶然或恶意的原因而遭到破坏 更改 泄露 系统连续可靠正常地运行 网络服务不中断 网络安全包括 网络设备安全 网络软件安全和网络信息安全 凡是涉及
  • js如何实现一个简单的节流函数?

    聚沙成塔 每天进步一点点 专栏简介 实现简单的节流函数 写在最后 专栏简介 前端入门之旅 探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅 这个专栏是为那些对Web开发感兴趣 刚刚踏
  • 解读startx

    X 的虚拟终端下的蜂鸣声可通过 xset b off 关闭 此命令 可放在 startx 的配置文件 HOME xinitrc 中 X 的虚拟终端下的蜂鸣声可通过 xset b off 关闭 此命令 可放在 startx 的配置文件 HOM
  • 07. 实战:Python正则法抓取某网站2022必看片迅雷种子

    目录 前言 URL 在评论区 URL 在评论区 URL 在评论区 目的 URL 在评论区 URL 在评论区 URL 在评论区 思路 URL 在评论区 URL 在评论区 URL 在评论区 代码实现 第一步 先导包 第二步 请求源代码 第三步
  • iwebsec靶场 SQL注入漏洞通关笔记7- 空格过滤绕过

    系列文章目录 iwebsec靶场 SQL注入漏洞通关笔记1 数字型注入 mooyuan的博客 CSDN博客 iwebsec靶场 SQL注入漏洞通关笔记2 字符型注入 宽字节注入 mooyuan的博客 CSDN博客 iwebsec靶场 SQL
  • Linux实验:Shell编程

    对思考题4 8 进行编辑 然后执行 功能 输入的所有参数逆序输出显示 对思考题 4 14 进行编辑 然后执行 功能 显示裴波那契数列前10项及它们的和 对思考题4 18 进行编辑 然后执行 功能 输出输入字符串中抽取的第n个字符至第m个字符
  • 【python】考前复习,python基础语法知识点整理

    文章目录 1 常量与表达式 2 变量和数据类型 创建变量 数据类型 动态类型 数据类型的转换 3 注释 4 字符串 字符串的定义方式 字符串的拼接 字符串的格式化 字符串格式化的精度控制 字符串的格式化 对表达式进行格式化 5 从控制台输入
  • 【Android Jetpack】新一代导航管理——Navigation

    目录 前言 使用Navigation 创建导航视图 添加页面 添加导航 添加导航宿主 实现导航 原理解析 NavHostFragment 支持ToolBar 总结 前言 不知道小伙伴们是否注意到 用AS创建一个默认的新项目后 MainAct