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

news2024/9/22 5:00:15

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
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux的进程信号

注意&#xff1a;首先需要提醒一个事情&#xff0c;本节提及的进程信号和下节的信号量没有任何关系&#xff0c;请您区分对待。 1.信号概念 1.1.生活中的信号 我们在生活中通过体验现实&#xff0c;记忆了一些信号和对应的处理动作&#xff0c;这意味着信号有以下相关的特点&…

上位机图像处理和嵌入式模块部署(统计函数执行时间)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 和pc上位机相比较&#xff0c;嵌入式设备的计算资源很多时候都是不足的。但是&#xff0c;嵌入式设备胜在稳定性和成本上面&#xff0c;这方面又是…

【XR806开发板试用】xr806使用tcp socket与手机通信

本文为极术社区XR806开发板活动试用文章。 参考&#xff1a;基于星辰处理器的全志XR806开源鸿蒙开发板上手体验 搭建环境。并成功编译。 项目源码 &#xff1a; https://gitee.com/kingwho/smart-home 在同一个局域网中&#xff0c;手机与xr806连接后&#xff0c;手机 APP 每隔…

lnmp一键安装包+wordpress

理论知识 1. LNMP组成介绍​ LNMP代表的是Linux系统下NginxMySQLPHP组成的动态网站系统解决方案。如图所示&#xff0c;Linux是目前最流行的免费操作系统&#xff1b;Nginx性能稳定、功能丰富、处理静态文件速度快且消耗系统的资源极少&#xff1b;MySQL是一个性能卓越、服务稳…

手拉手Vue3+vite引入echarts

技术栈springboot3hutool-alloshi-coreVue3viteechartsTailwindCSS软件版本IDEAIntelliJ IDEA 2022.2.1JDK17Spring Boot3.1hutool-all5.8.18oshi-core6.4.1Vue35.0.10vite5.0.10axios1.6.7echarts5.4.3 ECharts是一个使用 JavaScript 实现的开源可视化库&#xff0c;可以流畅…

awd总结

总结&#xff1a; 由于是第一次参加AWD比赛&#xff0c;各方面经验都不足&#xff0c;在参赛的前几天也是疯狂搜集各种脚本、框架、工具等&#xff0c;同时也参考b站的视频进行学习&#xff0c;我发现就是还是实操才能更快的学习 我觉得就是我前期的准备工作不足&#xff0c;…

stm32软件安装以及创建工程

文章目录 前言一、软件安装软件破解 二、创建工程三、创建项目创建组配置启动文件添加到组 为项目添加头文件路径创建源文件&#xff08;main函数文件&#xff09;使用寄存器配置引脚拼接好STLINK与stm32最小电路板的接线编写程序配置STLink下载程序配置寄存器配置13号端口&…

智能化运维发展现状?智能化运维方向有哪些?

智能运维方向主要包括人工运维、自动运维和智能运维三个阶段。从以下几个方面可以简要介绍智能运维的发展情况&#xff1a;  市场参与者众多&#xff1a;我国智能运维领域参与者众多&#xff0c;市场份额相对较低。华为、浪潮云、联想等硬件制造商在市场上占有很大份额。  …

c语言游戏实战(3):三子棋

前言&#xff1a; 三子棋是一种民间传统游戏&#xff0c;又叫九宫棋、圈圈叉叉棋、一条龙、井字棋等。游戏规则是双方对战&#xff0c;双方依次在9宫格棋盘上摆放棋子&#xff0c;率先将自己的三个棋子走成一条线就视为胜利。但因棋盘太小&#xff0c;三子棋在很多时候会出现和…

vulnhub中Beelzebub靶机

渗透思路 一.信息收集1.网段探测2.端口探测3.常见漏洞扫描4.目录扫描5.web页面分析 二.渗透继续目录扫描ssh连接提权提权&#xff0c;flag 一.信息收集 1.网段探测 ┌──(root㉿kali)-[~] └─# nmap -Pn 192.168.0.0/24 --min-rate 10000 Starting …

day28打卡

day28打卡 93. 复原 IP 地址 见注释 class Solution { public:vector<string> ret;vector<string> restoreIpAddresses(string s) {string ip;dfs(s, 0, ip);return ret;}//n记录小数点个数void dfs(string s, int n, string ip){//n为4if(n 4){//如果字符s没有…

2024Node.js零基础教程(小白友好型),nodejs新手到高手,(四)NodeJS入门——网络基础概念

041_网络基础概念_IP的介绍 hello&#xff0c;大家好&#xff0c;我们来一起认识一下IP。 在开始介绍 IP 之前&#xff0c;我们首先来介绍一个场景&#xff0c;方便大家去理解 IP 这个概念。比如这会儿强哥正在成都&#xff0c;然后还有另外一个小伙伴&#xff0c;谁呢&#x…

数据库分库分表:提升系统性能的必由之路

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 数据库分库分表&#xff1a;提升系统性能的必由之路 前言为什么分库分表是必要的分库分表的基本概念和原理性能提升和负载均衡 前言 在数字化时代&#xff0c;数据被认为是企业最宝贵的资产之一。然而…

STA双WiFi连接

STA双WiFi连接 1、STA/STA双WiFi开关1.1 相关属性1.2 STA/STA支持判断 2、STA双WiFi命令测试2.1 adb shell cmd wifi add-suggestion guest_5G wpa3 12345678 -p2.2 adb shell cmd wifi remove-suggestion guest_5G2.3 查看dumpsys wifi信息WifiConfigStore 3、STA双WiFi连接流…

Graal编译器和GraalVM虚拟机

文章目录 说明Java程序执行流程JVM的语言无关性JVM的执行流程执行引擎的两种行为&#xff1a;解释执行和编译热点代码和热点代码探测方式热点代码热点代码探测方式热点代码探测方式流程 HotSpotVM内嵌两个JIT编译器Graal编译器GraalVMGraalVM虚拟机安装和体验GraalVM的下载和安…

【JavaEE】_传输层协议UDP与TCP

目录 1. 开发中常见的数据组织格式 1.1 XML 1.2 JSON 1.3 Protobuf 2. 端口号 3. UDP协议 4. TCP协议 4.1 特点 4.2 TCP报文格式 4.3 TCP可靠性机制 4.3.1 确认应答机制 4.3.2 超时重传机制 4.3.2.1 丢包的两种情况 4.3.2.2 重传时间 4.3.3 连接管理机制 4.3.3…

【Flink入门修炼】1-2 Mac 搭建 Flink 源码阅读环境

在后面学习 Flink 相关知识时&#xff0c;会深入源码探究其实现机制。因此&#xff0c;需要现在本地配置好源码阅读环境。 本文搭建环境&#xff1a; Mac M1&#xff08;Apple Silicon&#xff09;Java 8IDEAFlink 官方源码 一、 下载 Flink 源码 github 地址&#xff1a;h…

【Vue3+Vite】Vue生命周期与组件 快速学习 第三期

文章目录 一、Vue生命周期1.1 生命周期简介1.2 生命周期案例 二、Vue组件2.1 组件基础2.2 组件化入门案例2.3 组件之间传递数据2.3.1父传子2.3.2 子传父2.3.3 兄弟传参 总结 一、Vue生命周期 1.1 生命周期简介 每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤&#xf…

UE5 获得频谱让nigara随音乐律动

参考视频:UE - Niagara实现可视化音乐动态粒子效果 案例演示及教程_哔哩哔哩_bilibili 先创建一个Niagara 在Properties的Sim Target改为GPU,Calculate Bounds Mode改为Fixed模式 生成的数量改为1000 这里的BoxSize可以选择修改,具体作用是粒子初始生成的范围 Drag,阻力,用来限…

数据结构_找环,破环题-2.5

一. 判断单链表有无环 a. 错误的思路&#xff1a;遍历陷入死循环 1&#xff09;和相交的遍历思路一样&#xff0c;找指向相同。 错误点 一直在死循环。 思考点&#xff1a;如何破环 b. 个人思路&#xff1a;反转链表回首结点 1&#xff09;目前的经验&#xff0c;无非就…