Android实现底部导航栏方法(Navigation篇)

Navigation实现底部导航栏

  • 前言
  • 导入和基本使用
    • 导入
    • 基础使用
      • 创建nav文件
      • 编辑Nav文件
        • 添加页面(代码版)
        • 添加页面(图解版)
      • 创建导航动作 action
        • 创建action(代码版)
        • 创建action(图解版)
      • 编辑action参数
        • launchSingleTop
        • popUpTo
        • popUpToInclusive
        • popUpToSaveState
        • restoreState
      • 使用nav文件
      • 跳转Fragment
  • 底部导航栏实现方法
    • 创建nav文件
    • 点击导航
  • 结语

前言

底部导航栏一直是大部分App不可缺失的一部分
最近注意到Jetpack中的Navigation支持Fragment的切换操作
特此浅研究一下

导入和基本使用

选择性跳过

导入

此处使用Google开发者文档中介绍

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"

  // 多模块使用
  implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"

  // 测试使用
  androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"

  // Jetpack Compose使用
  implementation "androidx.navigation:navigation-compose:$nav_version"
}

基础使用

使用nav文件配合 FragmentContainerView组件 实现Fragment的切换操作

创建nav文件

导入后,在项目的res文件夹下,右键选择Android Resource File,弹出弹窗在这里插入图片描述Resource type下拉选择Navigation即可,剩下的就是填写文件名
完成后会在res文件下创建一个navigation文件夹 ,里面就存放着nav文件

编辑Nav文件

打开Nav文件,可以看到顶部有一排按钮
在这里插入图片描述

分别是

  • 添加页面 (Fragment、Activiry、include)
  • 创建分组 (选择一个或多个页面进行分组)
  • 设置初始页 (选择一个页面,设置为初始页,即默认页,设置为默认页的页面左上角会出现一个房子图标)
  • 创建depplink (选择一个页面创建深层链接)
  • 添加 action (选择一个页面 添加跳转动作)
  • 整理布局 (全部页面重排,优化布局)

首先使用添加页面 添加三个已经写好的Fragment
当然使用写代码的方式也是可以的

添加页面(代码版)
<?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"
    app:startDestination="默认页id">

	<!--例-->
    <fragment
        android:id="@+id/标识此fragment的id"
        android:name="fragment类文件路径"
        android:label="fragment名称"
        tools:layout="fragment对应的layout" />

</navigation>
添加页面(图解版)

在这里插入图片描述
可以通过搜索框搜索,点击需要添加的页面即可
此时在代码处会生成一个fragment标签

在这里插入图片描述
如图所示 成功添加了一个页面
如果需要添加页面预览 则在fragment处添加标签 layout 值为 fragment对应的layout
在这里插入图片描述

创建导航动作 action

action是跳转到fragment的关键要素

创建action(代码版)

最基本的写法

<action
        android:id="@+id/标识此action的id"
        app:destination="@id/目的地的fragment Id" />
创建action(图解版)

在这里插入图片描述点击起始的fragment,右边有一个可以拖动的箭头,将箭头拖至目的地fragment即可
上述操作完成后会在 testFragment 中生成一段action标签
当然action的内容不止这些

编辑action参数

通过查看NavAction的源码参数 可以看到action有数个标签可以定义
(按住Ctrl+鼠标点击action中的destination属性即可)

 <declare-styleable name="NavAction">
        <attr name="android:id"/>
        <!-- destination  目的地的id  -->
        <attr format="reference" name="destination"/>
        <attr format="boolean" name="launchSingleTop"/>
        <attr format="boolean" name="restoreState"/>
        <attr format="reference" name="popUpTo"/>
        <attr format="boolean" name="popUpToInclusive"/>
        <attr format="boolean" name="popUpToSaveState"/>
        <attr format="reference" name="enterAnim"/>
        <attr format="reference" name="exitAnim"/>
        <attr format="reference" name="popEnterAnim"/>
        <attr format="reference" name="popExitAnim"/>
    </declare-styleable>

同时可以通过右侧Attributes 页进行参数查看

launchSingleTop

默认false,类似Activity的singleTop
设为true后 activity的singleTop会判断顶部的activity是否为当前activity,是则复用,否则新建
navigation的singleTop会判断顶部的fragment是的为目的地fragment ,是则销毁顶部,重新创建放置在顶部
可见图 唯一的区别就是 执行了action动作后有无删除旧fragment
在这里插入图片描述
此处使用了以下action

 <action
        android:id="@+id/action_testFragment_self_singleTop"
        app:launchSingleTop="true"
        app:destination="@id/testFragment" />
 <action
        android:id="@+id/action_testFragment_self"
        app:destination="@id/testFragment" />
popUpTo

默认为空
设为某个fragment的id后 执行此action 会挨个出栈 直到出栈的fragment为popUpTo指定的fragment (此fragment不出)然后再创建 目的地fragment
以下为图解在这里插入图片描述主要看右下角处 2->1 popUpTo = 1
当popUpTo指定1后 会把所有不是 1 的fragment出栈,再在旧的1上面入栈新的1
如下图,即使多个1存在,只会弹出最上层的1之上的fragment
在这里插入图片描述

popUpToInclusive

默认false
结合popUpTo使用,当popUpToInclusive为true的时候,会把旧的1也出栈
如下图,区别与上图 本次连黄色的1都出栈了
在这里插入图片描述

popUpToSaveState

默认false
结合popUpTo使用
设为true后 popUpTo操作弹出的fragment 都会保存状态
以便restoreState 恢复操作

restoreState

默认false
结合popUpToSaveState使用
设为true后 还原目的地fragment的状态
如果之前没有保存状态 此参数不起效
在这里插入图片描述
如图 当popUpToSaveState为true后 弹出的fragment会保存到一个Map内
之后再调用action
action中restoreState为true
action的目的地为2
此时就会取出map中ID为2的fragment 重新放进栈中
取出顺序为 先popup的后入栈 也就先显示 顺序和popup前一样
需要注意的是 这样子的状态保存实际上需要view model配合使用 ,当fragment销毁(onDestroy)后,fragment绑定的viewmodel没有跟着销毁
此时恢复状态,fragment依旧会onCreate,就需要从view model中获取数据,所以数据需要保存在viewmodel才是最优选

  • 当 popUpTo和起始idfragment不同时,会发生不同情况
  • popUpToInclusive 会影响状态恢复
    在这里插入图片描述
    如图,当fragment3跳转至fragment4时 弹出2以上的所有fragment,此时两个fragment3都会保存状态,直到fragment4跳至fragment2,并使用restoreState=true属性后,会把两个fragment3恢复,这时就与之前冲突了。
    这时再把popUpToInclusive改为true,就会发生以下情况
    在这里插入图片描述
    可以看到,把popUpToInclusive设为true后,弹出了fragment2以上所有页面,包括fragment2自己,在随后的恢复中,原先的fragment3被置顶,明明是4->2却显示3!
    原因未知,如果上面描述有错误,或者有更好的见解,欢迎评论区讨论。

使用nav文件

这里需要使用官方的组件进行fragment的显示,具体步骤如下 在activity的layout中添加

   <androidx.fragment.app.FragmentContainerView
        android:id="@+id/main_fragment_container"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav文件名" />

这里有两个属性需要说明

app:defaultNavHost:设为false时,返回就退出Activity 设为true时,返回就是fragment出栈
app:navGraph:设置nav文件

跳转Fragment

引用官方文档中的话

使用 FragmentContainerView 创建 NavHostFragment,或通过 FragmentTransaction 手动将 NavHostFragment 添加到您的 Activity 时,尝试通过 Navigation.findNavController(Activity, @IdRes int) 检索 Activity 的 onCreate() 中的 NavController 将失败。您应改为直接从 NavHostFragment 检索 NavController。

简单来说就是,先尝试下列方法获取控制器
Kotlin:

Fragment.findNavController()
View.findNavController()
Activity.findNavController(viewId: Int)

Java:

NavHostFragment.findNavController(Fragment)
Navigation.findNavController(Activity, @IdRes int viewId)
Navigation.findNavController(View)

如果报错,获取不了,应该改为

   val navHostFragment =
            supportFragmentManager.findFragmentById(R.id.main_fragment_container) as
                    NavHostFragment
  val controller = navHostFragment.navController
      NavHostFragment f = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.main_fragment_container);
        NavController controller;
        if (f != null) {
            controller = f.getNavController();
        }

Activity所继承的必须为 AppCompatActivity
拿到控制器后,只需要

 controller.navigate( action的ID )

即可完成页面跳转,例:

    <fragment
        android:id="@+id/testFragment"
        android:name="com.a.demo.ui.nav.TestFragment"
        android:label="TestFragment"
        tools:layout="@layout/fragment_test">
        <action
            android:id="@+id/action_testFragment_to_test2Fragment"
            app:destination="@id/test2Fragment" />
    </fragment>
    <fragment
        android:id="@+id/test2Fragment"
        android:name="com.a.demo.ui.nav.Test2Fragment"
        android:label="Test2Fragment"
        tools:layout="@layout/fragment_test2">
    </fragment>
controller.navigate(R.id.action_testFragment_to_test2Fragment)

这样就完成了从testFragment跳转至test2Fragment的操作

底部导航栏实现方法

假如现在底部导航栏有五个按钮和五个fragment

创建nav文件

	<fragment
        android:id="@+id/mainFragment1"
        android:name="com.a.demo.ui.activity.main.MainFragment1"
        android:label="MainFragment1"
        tools:layout="@layout/fragment_1" />
    <fragment
        android:id="@+id/mainFragment2"
        android:name="com.a.demo.ui.activity.main.MainFragment2"
        android:label="MainFragment2" />
    <fragment
        android:id="@+id/mainFragment3"
        android:name="com.a.demo.ui.activity.main.MainFragment3"
        android:label="MainFragment3" />
    <fragment
        android:id="@+id/mainFragment4"
        android:name="com.a.demo.ui.activity.main.MainFragment4"
        android:label="MainFragment4" />
    <fragment
        android:id="@+id/mainFragment5"
        android:name="com.a.demo.ui.activity.main.MainFragment5"
        android:label="MainFragment5" />

点击导航

在activity处,设置五个点击事件 分别对应五个按钮(此处不展示详细代码)

//获取控制器
 		val navHostFragment = supportFragmentManager.findFragmentById(R.id.main_fragment_container) as NavHostFragment
        val controller = navHostFragment.navController
		//设置导航配置
        val builder = NavOptions.Builder().setLaunchSingleTop(true).setRestoreState(true)
        builder.setPopUpTo(
            controller.graph.findStartDestination().id,
            inclusive = false,
            saveState = true
        )
        //设置点击事件
        vb.but1.setOnClickListener {
			controller.navigate(R.id.mainFragment1,null,builder.build())
        }
        vb.but2.setOnClickListener {
           controller.navigate(R.id.mainFragment2,null,builder.build())
        }
        vb.but3.setOnClickListener {
           controller.navigate(R.id.mainFragment3,null,builder.build())
        }
        vb.but4.setOnClickListener {
          controller.navigate(R.id.mainFragment4,null,builder.build())
        }
        vb.but5.setOnClickListener {
           controller.navigate(R.id.mainFragment5,null,builder.build())
        }

解释一下上面代码

navigate方法可以传fragment的id直接跳转,而不使用action ID,这时等同于 当前fragment->传递的fragment 在这里插入图片描述
NavOptions.Builder是导航配置,等同于action中其他参数 ,但有更高的自定义程度,相当于动态控制
controller.graph.findStartDestination().id 可以拿到当前当前fragment ID

此时的activity的布局需要修改

app:defaultNavHost=“false”

这种导航栏方式,fragment1始终被压在栈底,如果将返回键交予fragment分发,就会出现先退到fragment1再退出activity的情况

  <androidx.fragment.app.FragmentContainerView
        android:id="@+id/main_fragment_container"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="false"
        app:navGraph="@navigation/nav_main" />

结语

到此,Nav自定义导航栏已经实现,基本使用的模块来源日常使用经验。
至于底部导航栏,网上大部分人都推荐使用 BottomNavigationView 配合使用

  <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/main_bottomNavigationView"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_50"
        app:menu="@menu/menu_main" />

好用,但自定义样式比较难,然后就只能翻BottomNavigationView的源码,看它是怎么实现切换页面而不销毁fragment
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/385124.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构】链表OJ面试题3《判断是否有环》(题库+解析)

1.前言 前五题在这http://t.csdnimg.cn/UeggB 后三题在这http://t.csdnimg.cn/gbohQ 记录每天的刷题&#xff0c;继续坚持&#xff01; 2.OJ题目训练 9. 给定一个链表&#xff0c;判断链表中是否有环。 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成…

算法沉淀——链表(leetcode真题剖析)

算法沉淀——链表 01.两数相加02.两两交换链表中的节点03.重排链表04.合并 K 个升序链表05.K个一组翻转链表 链表常用技巧 1、画图->直观形象、便于理解 2、引入虚拟"头节点" 3、要学会定义辅助节点&#xff08;比如双向链表的节点插入&#xff09; 4、快慢双指针…

树状菜单(利用映射-bootstrap+jQuery实现折叠功能)

效果&#xff08;默认全部展开&#xff09;&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><…

一、Docker部署MySQL

Docker部署MySQL 一、安装Docker二、拉取MySQL镜像1.选择拉取版本2.拉取镜像 三、启动MySQL1.确定好挂载目录2.启动3.查看是否启动4.开启远程访问权限 一、安装Docker 安装教程&#xff1a;https://qingsi.blog.csdn.net/article/details/131270071 二、拉取MySQL镜像 1.选择…

【C语言】实现双向链表

目录 &#xff08;一&#xff09;头文件 &#xff08;二&#xff09; 功能实现 &#xff08;1&#xff09;初始化 &#xff08;2&#xff09;打印链表 &#xff08;3&#xff09; 头插与头删 &#xff08;4&#xff09;尾插与尾删 &#xff08;5&#xff09;指定位置之后…

html的格式化标签和图片(img)标签

格式化标签 加粗: strong标签和b标签倾斜: em标签和i标签删除线: del标签和s标签下划线: ins标签和u标签 <strong>stong 加粗</strong><b>b 加粗</b><em>倾斜</em><i>倾斜</i><del>删除线</del><s>删除线…

nodejs学习计划--(十)会话控制及https补充

一、会话控制 1.介绍 所谓会话控制就是 对会话进行控制 HTTP 是一种无状态的协议&#xff0c;它没有办法区分多次的请求是否来自于同一个客户端&#xff0c; 无法区分用户 而产品中又大量存在的这样的需求&#xff0c;所以我们需要通过 会话控制 来解决该问题 常见的会话控制…

论文阅读:GamutMLP A Lightweight MLP for Color Loss Recovery

这篇文章是关于色彩恢复的一项工作&#xff0c;发表在 CVPR2023&#xff0c;其中之一的作者是 Michael S. Brown&#xff0c;这个老师是加拿大 York 大学的&#xff0c;也是 ISP 领域的大牛&#xff0c;现在好像也在三星研究院担任兼职&#xff0c;这个老师做了很多这种类似的工…

苹果Mac键盘如何将 F1 到 F12 取消按Fn

苹果电脑安装了Win10操作系统之后&#xff0c;F1到F12用不了怎么办的解决方法。本文将介绍一些解决方法&#xff0c;帮助您解决无法使用F1到F12功能键的问题。 使用 Mac系统的人都知道&#xff0c;Mac系统默认是没有开启 F1-F12 的使用的&#xff0c;平时我们使用的系统都可以使…

线性时间非比较类排序之基数排序

基数排序 基数排序是桶排序的扩展&#xff0c;因此又称“桶子法”&#xff0c;它是通过键值的部分信息&#xff0c;将要排序的元素分配至某些“桶”中&#xff0c;以达到排序的作用。 1. 算法思想 将各元素按位数切割成不同的数字&#xff0c;然后分别根据每个位数的比较结果…

在线问诊系统设计与实现的经验总结与整理

随着互联网技术的快速发展&#xff0c;在线问诊服务作为一种新兴的医疗服务模式&#xff0c;正逐渐受到人们的关注和使用。本文将介绍在线问诊系统的设计原则和关键组件&#xff0c;以及如何实现一个安全、高效和可扩展的在线医疗服务平台。 内容&#xff1a; 1. 引言 - 在…

Vulnhub靶场 DC-8

目录 一、环境搭建 二、信息收集 1、主机发现 2、指纹识别 三、漏洞复现 1、SQL注入 sqlmap工具 2、dirsearch目录探测 3、反弹shell 4、提权 exim4 5、获取flag 四、总结 一、环境搭建 Vulnhub靶机下载&#xff1a; 官网地址&#xff1a;https://download.vulnhub.com/dc/DC-…

Python算法题集_合并K个升序链表

Python算法题集_合并K个升序链表 题23&#xff1a;合并K个升序链表1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【双层循环】2) 改进版一【列表排序】3) 改进版二【堆排序】4) 改进版三【分区海选】 4. 最优算法 本文为Python算法题集之一的代…

论文介绍 FreeControl: 无需额外训练实现文本到图像的空间操控!

论文介绍 FreeControl: 无需额外训练实现文本到图像的空间操控&#xff01; 论文介绍 FreeControl: Training-Free Spatial Control of Any Text-to-Image Diffusion Model with Any Condition 关注微信公众号: DeepGo 项目地址&#xff1a;https://genforce.github.io/freeco…

【Java程序设计】【C00266】基于Springboot的超市进存销管理系统(有论文)

【Java程序设计】【C00266】基于Springboot的超市进存销管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的超市进销存系统 本系统分为登录注册模块、管理员功能模块以及员工功能模块。 登录注册模块&#…

Java学习18-- Override方法重写【★】

重点&#xff1a;super类 & 方法重写 ★看不明白多看几遍&#xff0c;记住static优先级>>高于override 重写Override methods★ 重写Override&#xff1a;child class可以覆盖father class中的method&#xff0c;即子类child class和父类father class有相同名称、…

如何部署一个高可用的 Linux 集群?

部署一个高可用的 Linux 集群需要经过多个步骤和考虑因素。以下是一个简要的指南&#xff0c;帮助您了解如何部署一个高可用的 Linux 集群&#xff1a; 确定需求和目标&#xff1a;在开始部署之前&#xff0c;您需要明确高可用性的定义和目标。对于一些组织而言&#xff0c;高…

鸿蒙开发第3篇__大数据量的列表加载性能优化

列表 是最常用到的组件 一 ForEach 渲染控制语法————Foreach Foreach的作用 遍历数组项&#xff0c;并创建相同的布局组件块在组件加载时&#xff0c; 将数组内容数据全部创建对应的组件内容&#xff0c; 渲染到页面上 const swiperImage: Resource[] {$r("app.me…

2024春晚纸牌魔术原理----环形链表的约瑟夫问题

一.题目及剖析 https://www.nowcoder.com/practice/41c399fdb6004b31a6cbb047c641ed8a?tabnote 这道题涉及到数学原理,有一般公式,但我们先不用公式,看看如何用链表模拟出这一过程 二.思路引入 思路很简单,就试创建一个单向循环链表,然后模拟报数,删去对应的节点 三.代码引…

数据库管理-第150期 Oracle Vector DB AI-02(20240212)

数据库管理150期 2024-02-12 数据库管理-第150期 Oracle Vector DB & AI-02&#xff08;20240212&#xff09;1 LLM2 LLM面临的挑战3 RAG4 向量数据库LLM总结 数据库管理-第150期 Oracle Vector DB & AI-02&#xff08;20240212&#xff09; 作者&#xff1a;胖头鱼的鱼…