Android jetpack : Navigation 导航 路由 、 单个Activity嵌套多个Fragment的UI架构方式
Android Navigation 如何动态的更换StartDestination &&保存Fragment状态
Navigation(一)基础入门
google 官网 : Navigation 导航 路由
讨论了两年的 Navigation 保存 Fragment 状态问题居然被关闭了
Navigation是一种导航的概念,即把Activity和fragment当成一个个的目的地Destination,各目的地形成一张导航图NavGraph,由导航控制器NavController来统一调度跳转
单个Activity嵌套多个Fragment的UI架构方式,已被大多数Android工程师所接受和采用。但是,对Fragment的管理一直是一个比较麻烦的事情,工程师需要通过FragmentManager和FragmentTransaction来管理Fragment之间的切换。这其中还包括了对应用程序的App bar的管理,Fragment间的切换动画,Fragment间的参数传递,总之,使用起来不是特别友好。
为此,Android Jetpack提供的一个名为Navigation的UI架构组件。旨在方便我们管理Fragment页面。它具体有以下优势:
- 可视化的页面导航图,类似xcode中的StoryBoard,便于我们看清页面之间的关系
- 通过destination和action来完成页面间的导航
- 方便的页面切换动画
- 页面间类型安全的参数传递
- 通过NavigationUI类,对菜单,底部导航,抽屉菜单导航进行方便统一的管理
- 深层链接
注意:在Android Studio3.2及以上版本才能支持Navigation特性。
本文所说的“页面”包括了Fragment和Activity,但主要是Fragment,因为Navigation组件的主要目地就是方便我们在一个Activity中对多个Fragment进行管理。
首先,我们需要先对Navigation有一个大致的了解。
Navigation Graph
这是一种新型的XML资源文件,里面包含了应用程序所有的页面及页面之间的关系
NavHostFragment
这是一个特殊的布局文件,Navigation Graph中的页面通过该Fragment展示
NavController
这是一个Java/Kotlin对象,用于在代码中完成Navigation Graph中具体的页面切换
当你想要切换页面的时候,使用NavController对象,告诉它你想要去Navigation Graph中的哪个页面,NavController会将相关的页面展示在NavHostFragment中。
创建工程,引入依赖,
android {compileSdkVersion 30buildToolsVersion "30.0.3"defaultConfig {applicationId "com.xq.mybottomnavigation"minSdkVersion 29targetSdkVersion 30versionCode 1versionName "1.0"}buildFeatures {viewBinding true}
}dependencies {implementation 'androidx.appcompat:appcompat:1.2.0'implementation 'com.google.android.material:material:1.2.1'implementation 'androidx.constraintlayout:constraintlayout:2.0.1'testImplementation 'junit:junit:4.+'androidTestImplementation 'androidx.test.ext:junit:1.1.2'androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'implementation 'androidx.navigation:navigation-fragment:2.0.0'implementation 'androidx.navigation:navigation-ui:2.0.0'
}
MainActivity和布局 :
import android.os.Bundle;import com.google.android.material.bottomnavigation.BottomNavigationView;import androidx.appcompat.app.AppCompatActivity;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;import com.xq.mybottomnavigation.databinding.ActivityMainBinding;public class MainActivity extends AppCompatActivity {private ActivityMainBinding binding;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);binding = ActivityMainBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());BottomNavigationView navView = findViewById(R.id.nav_view);// Passing each menu ID as a set of Ids because each// menu should be considered as top level destinations.//获取App bar配置:AppBarConfigurationAppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications).build();NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main);//将NavController和AppBarConfiguration进行绑定NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);//将需要交互的App barUI与NavController和AppBarConfiguration进行绑定NavigationUI.setupWithNavController(binding.navView, navController);}}
-
navigation pop 和 push的时候 对Fragment的 操作是 replace,所以会导致生命周期重新走一遍
-
其实Navigation使用很简单,navigation和activity(确切的说是Fragment)绑定之后,使用两个方法就行,一个是
navigate,就是跳转,一个是navigateUp,就是返回。
如果想要跳转到新页面时,在Fragment中使用:
NavHostFragment.findNavController(this).navigate(destinationID, bundle);
NavHostFragment.findNavController(this).navigateUp();
布局:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:id="@+id/container"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingTop="?attr/actionBarSize"><com.google.android.material.bottomnavigation.BottomNavigationViewandroid:id="@+id/nav_view"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginStart="0dp"android:layout_marginEnd="0dp"android:background="?android:attr/windowBackground"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:menu="@menu/bottom_nav_menu" /><fragmentandroid:id="@+id/nav_host_fragment_activity_main"android:name="androidx.navigation.fragment.NavHostFragment"android:layout_width="match_parent"android:layout_height="match_parent"app:defaultNavHost="true"app:layout_constraintBottom_toTopOf="@id/nav_view"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent"app:navGraph="@navigation/mobile_navigation" /></androidx.constraintlayout.widget.ConstraintLayout>
NavHostFragment:
android:name="androidx.navigation.fragment.NavHostFragment"
这句话是在告诉系统,这是一个特殊的Fragment 。
app:defaultNavHost=“true”:
app:defaultNavHost="true"
将defaultNavHost属性设置为true,则该Fragment会自动处理系统返回键,即,当用户按下手机的返回按钮时,系统能自动将当前的Fragment推出。
app:navGraph=“@navigation/nav_graph”:
app:navGraph="@navigation/nav_graph"
设置该Fragment对应的导航图 。
导航图文件 :@navigation/mobile_navigation
<?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"android:id="@+id/mobile_navigation"app:startDestination="@+id/navigation_home"><fragmentandroid:id="@+id/navigation_home"android:name="com.xq.mybottomnavigation.ui.home.HomeFragment"android:label="@string/title_home"tools:layout="@layout/fragment_home" /><fragmentandroid:id="@+id/navigation_dashboard"android:name="com.xq.mybottomnavigation.ui.dashboard.DashboardFragment"android:label="@string/title_dashboard"tools:layout="@layout/fragment_dashboard" /><fragmentandroid:id="@+id/navigation_notifications"android:name="com.xq.mybottomnavigation.ui.notifications.NotificationsFragment"android:label="@string/title_notifications"tools:layout="@layout/fragment_notifications" />
</navigation>
fragment
三个fragment类似
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;import com.xq.mybottomnavigation.R;
import com.xq.mybottomnavigation.databinding.FragmentHomeBinding;public class HomeFragment extends Fragment {private HomeViewModel homeViewModel;private FragmentHomeBinding binding;public View onCreateView(@NonNull LayoutInflater inflater,ViewGroup container, Bundle savedInstanceState) {homeViewModel =new ViewModelProvider(this).get(HomeViewModel.class);binding = FragmentHomeBinding.inflate(inflater, container, false);View root = binding.getRoot();final TextView textView = binding.textHome;homeViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() {@Overridepublic void onChanged(@Nullable String s) {textView.setText(s);}});return root;}@Overridepublic void onDestroyView() {super.onDestroyView();binding = null;}}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"android:layout_height="match_parent"tools:context=".ui.home.HomeFragment"><TextViewandroid:id="@+id/text_home"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginStart="8dp"android:layout_marginTop="8dp"android:layout_marginEnd="8dp"android:textAlignment="center"android:textSize="20sp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
其他
Activity中导航到指定fragment
//方式一 、 通过NavController
NavController controller = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main);
binding.btn1.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {controller.navigate(R.id.navigation_notifications);}
});//方式二 、 通过NavHostFragment
NavHostFragment fragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment_activity_main);
binding.btn2.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (fragment != null) {NavHostFragment.findNavController(fragment).navigate(R.id.navigation_notifications);}}
});
NotificationsFragment 返回上一级
textView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {NavHostFragment.findNavController(NotificationsFragment.this).navigateUp();}
});
动态设置 navGraph,传参数
MainActivity : 发送数据
NavController controller = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main);
NavInflater navInflater = controller.getNavInflater();
NavGraph navGraph = navInflater.inflate(R.navigation.mobile_navigation);
navGraph.setStartDestination(R.id.navigation_notifications);//初始界面Bundle args = new Bundle();
args.putBoolean("6no6", true);//传参navController.setGraph(navGraph, args);
或者
//动态加载setGraph
FragmentManager manager = getSupportFragmentManager();
NavHostFragment hostFragment = (NavHostFragment) manager.findFragmentById(R.id.nav_host_fragment_activity_main);
NavController controller = null;
if (hostFragment != null) {controller = hostFragment.getNavController();
}
navController.setGraph(R.navigation.mobile_navigation);
NotificationsFragment : 接收数据
Bundle arguments = getArguments();
if (arguments != null) {boolean aBoolean = arguments.getBoolean("6no6");textView.setTextColor(aBoolean ? Color.RED : Color.BLUE);
}
导航监听:
navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {@Overridepublic void onDestinationChanged(@NonNull @NotNull NavController controller,@NonNull @NotNull NavDestination destination,@Nullable Bundle arguments) {CharSequence label = destination.getLabel();Log.e(TAG, "onDestinationChanged: ===="+label);}
});
切换时使Fragment保存状态
在进行跳转时 直接使用了replace,所以导致当前页面会调用 onDestroyView,即fragment变为 inactive,当进行pop操作时,fragment重新进入 active状态时,会重新调用 onViewCreated 等方法,导致页面重新绘制,
其实在这种情况下,我们可以直接用ViewModel和LiveData对数据进行保存,但是这次想尝试一下新的解决办法。
在知道原因后就好办了,直接继承FragmentNavigator把方法重写
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {// ft.replace(mContainerId, frag);// change to if(mFragmentManager.getFragments().size()>0){ft.hide(mFragmentManager.getFragments().get(mFragmentManager.getFragments().size()-1));ft.add(mContainerId, frag);}else {ft.replace(mContainerId, frag);}}
KeepStateFragmentNavigator 使用如下:
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
Fragment navHostFragment = getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
navController.getNavigatorProvider().addNavigator(new KeepStateFragmentNavigator(this, navHostFragment.getChildFragmentManager(), R.id.nav_host_fragment));
NavHostFragment.findNavController(this).navigate(destinationID, bundle);
相关文章:
Android jetpack : Navigation 导航 路由 、 单个Activity嵌套多个Fragment的UI架构方式
Android Navigation 如何动态的更换StartDestination &&保存Fragment状态 Navigation(一)基础入门 google 官网 : Navigation 导航 路由 讨论了两年的 Navigation 保存 Fragment 状态问题居然被关闭了 Navigation是一种导航的概念,即把Activ…...
【react】在react中祖父、父亲、孙子组件层层解构其余属性props时报错children.forEach is not function
起因 报错children.forEacht is not function 分析原因 由于地址组件本身存在options,此时父组件又传递…otherProps,且解构了父级组件的otherProps,其中others解构后的属性就有options,因此产生了属性冲突,导致属性…...
P9831 [ICPC2020 Shanghai R] Gitignore
P9831 [ICPC2020 Shanghai R] Gitignore - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 只看题意翻译这道题是做不出来的,还要去看英文里面的规定(这里就不放英文了),主要问题是不要公用子文件夹。 例如: 1 / a / 2 2 / a / 3…...
LinkList集合方法(自写)
在使用以下方法时需要定义一个LinkNode类来定义变量,new一个新对象进行调用,输出时需要定义输出方法 public class ListNode {int value;ListNode next;//public ListNode(int value) {this.value value;}public String toString(){return "ListN…...
Ansible playbook自动化运维工具详解
Ansible playbook自动化运维工具详解 一、playbook的相关知识1.1、playbook 的简介1.2、playbook的 各部分组成 二、基础的playbook剧本编写实例三、 playbook的定义、引用变量3.1、基础变量的定义与引用3.2、引用fact信息中的变量 四、playbook中的when条件判断和变量循环使用…...
图像切分:将一张长图片切分为指定长宽的多张图片
1.需求 比如有一张很长的图片其大小为宽度779,高度为122552,那我想把图片切分为779乘以1280的格式。 步骤如下: 使用图像处理库(如PIL或OpenCV)加载原始图片。确定子图片的宽度和高度。计算原始图片的宽度和高度&am…...
ROS学习笔记(5):ros_control
1.ros_control简介 ros_control - ROS Wiki ros_control是为ROS提供的机器人控制包,包含一系列控制器接口、传动装置接口、控制器工具箱等,有效帮助机器人应用功能包快速落地,提高开发效率。 2.ros_control框架 ros_control总体框架: 针对…...
《008.Springboot+vue之自习室选座系统》
[火]《008.Springbootvue之自习室选座系统》 项目简介 [1]本系统涉及到的技术主要如下: 推荐环境配置:DEA jdk1.8 Maven MySQL 前后端分离; 后台:SpringBootMybatisredis; 前台:vueElementUI; [2]功能模块展示: 前端…...
道可云元宇宙每日资讯|5G数智新时代元宇宙发展论坛在厦门举办
道可云元宇宙每日简报(2023年11月6日)讯,今日元宇宙新鲜事有: 5G数智新时代元宇宙发展论坛在厦门举办 3日,由2023年中国金鸡百花电影节执委会主办、厦门电影节有限公司协办的“5G数智新时代元宇宙发展论坛暨‘中国白德…...
使用 Go 写入文件
在本教程中,我们将学习如何使用 Go 将数据写入文件。我们还将学习如何同时写入文件。 本教程有以下部分 将字符串写入文件将字节写入文件逐行将数据写入文件附加到文件同时写入文件 由于 Playground 不支持文件操作,请在本地系统中运行本教程的所有程…...
调用DeleteLocalRef的正确姿势
做安卓jni相关开发的总会在涉及到jni变量释放时怀疑人生,what? where? when? who? why? how? how much? 最近碰到一个比较奇怪的问题,有一个jni方法的耗时在随着调用次数的增加而上涨,但是没有明显的内存泄漏,经过我缜密分…...
抖音小店从0到1起店流程,实操经验分享!
我是电商珠珠 很多人在开店之后,并不知道怎么做。往往会有人跑来问我说,开店之后怎么做啊,流程方面我还不是很熟悉啊等等。 这份起店流程备好了,将来对你有用。 第一步,店铺基础设置 在店铺开好之后,不…...
MySQL权限
权限 MySQL 允许客户端用户连接到服务器并访问服务器管理数据,MySQL 用户权限系统的主要功能是对给定主机连接的用户进行身份验证,并将该用户与数据库的权限相关联。 在 MySQL8 之前,授权表使用 MyISAM 并且是非事务性的,在 MyS…...
Nginx服务器安装证书并启用SSL(acme.sh)
前提 您已购置vps服务器,例如阿里云全球站ecs、AWS EC2、Azure VM、GCP Compute等安全组已开启80、443端口,且访问源设置为0.0.0.0/0域名已设置A记录指向当前操作服务器,若您使用aws ec2,有公有 IPv4 DNS,可供使用 安…...
c++实现观察者模式
前言 我觉得这是最有意思的模式,其中一个动,另外的自动跟着动。发布-订阅,我觉得很巧妙。 代码 头文件 #pragma once #include<vector> #include<string> #include<iostream>// 抽象观察者 class Aobserver { public:v…...
C 语言左移位操作在kernel驱动子系统中的特殊用途
文章目录 前言一、C语言左移位操作介绍1. 左移位二、左移位操作在kernel 驱动子系统中的应用1. 左移位操作在 V4L2, Media 子系统中的应用实例2.左移位操作在 DRM 子系统中的应用实例2.1 左移位操作在struct drm_crtc 中的应用2.2 左移位操作在struct drm_encoder 中的应用总结…...
kafka3.6.0集群部署
环境准备 机器环境 系统主机名IP地址centos7.9kafka01192.168.200.51centos7.9kafka02192.168.200.52centos7.9kafka03192.168.200.53 所需软件 jdk-8u171-linux-x64.tar.gzapache-zookeeper-3.8.3-bin.tar.gz https://dlcdn.apache.org/zookeeper/zookeeper-3.8.3/apache-zook…...
JAVA客户端使用账号密码调用influxdb2报错:{“code“:“unauthorized“,“message“:“Unauthorized“}
问题:JAVA客户端访问influxdb2报错 说明:当前influxdb版本:2.6.1 使用依赖: <dependency><groupId>org.influxdb</groupId><artifactId>influxdb-java</artifactId><version>2.10</vers…...
Mysql查询今天到期、n天即将到期、还有n天过期相关sql
超级治愈的一段话 其实你已经很幸福了,吃饱穿暖,没病没灾,隔三岔五还能吃顿好的,偶尔还能睡到自然醒,肥嘟嘟的一身福气。人这一辈子,要是能够逃过天灾,躲过战乱,不遇歹人,不生大病,就已经是非常幸运了,要是还能家庭和谐,收人稳定,三五知己,那更是天大的福泽。 -…...
【漏洞复现】Apache Log4j Server 反序列化命令执行漏洞(CVE-2017-5645)
感谢互联网提供分享知识与智慧,在法治的社会里,请遵守有关法律法规 文章目录 1.1、漏洞描述1.2、漏洞等级1.3、影响版本1.4、漏洞复现1、基础环境2、漏洞扫描3、漏洞验证 1.5、深度利用1、反弹Shell 说明内容漏洞编号CVE-2017-5645漏洞名称Log4j Server …...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...
理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
AGain DB和倍数增益的关系
我在设置一款索尼CMOS芯片时,Again增益0db变化为6DB,画面的变化只有2倍DN的增益,比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析: 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...
Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...
Windows安装Miniconda
一、下载 https://www.anaconda.com/download/success 二、安装 三、配置镜像源 Anaconda/Miniconda pip 配置清华镜像源_anaconda配置清华源-CSDN博客 四、常用操作命令 Anaconda/Miniconda 基本操作命令_miniconda创建环境命令-CSDN博客...
Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?
Pod IP 的本质与特性 Pod IP 的定位 纯端点地址:Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址(如 10.244.1.2)无特殊名称:在 Kubernetes 中,它通常被称为 “Pod IP” 或 “容器 IP”生命周期:与 Pod …...
springboot 日志类切面,接口成功记录日志,失败不记录
springboot 日志类切面,接口成功记录日志,失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...
C++_哈希表
本篇文章是对C学习的哈希表部分的学习分享 相信一定会对你有所帮助~ 那咱们废话不多说,直接开始吧! 一、基础概念 1. 哈希核心思想: 哈希函数的作用:通过此函数建立一个Key与存储位置之间的映射关系。理想目标:实现…...
