当前位置: 首页 > news >正文

Android jetpack : Navigation 导航 路由 、 单个Activity嵌套多个Fragment的UI架构方式

Android Navigation 如何动态的更换StartDestination &&保存Fragment状态

Navigation(一)基础入门

google 官网 : Navigation 导航 路由

讨论了两年的 Navigation 保存 Fragment 状态问题居然被关闭了

Navigation是一种导航的概念,即把Activityfragment当成一个个的目的地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 等方法,导致页面重新绘制,
其实在这种情况下,我们可以直接用ViewModelLiveData对数据进行保存,但是这次想尝试一下新的解决办法。
在知道原因后就好办了,直接继承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 官网 &#xff1a; Navigation 导航 路由 讨论了两年的 Navigation 保存 Fragment 状态问题居然被关闭了 Navigation是一种导航的概念&#xff0c;即把Activ…...

【react】在react中祖父、父亲、孙子组件层层解构其余属性props时报错children.forEach is not function

起因 报错children.forEacht is not function 分析原因 由于地址组件本身存在options&#xff0c;此时父组件又传递…otherProps&#xff0c;且解构了父级组件的otherProps&#xff0c;其中others解构后的属性就有options&#xff0c;因此产生了属性冲突&#xff0c;导致属性…...

P9831 [ICPC2020 Shanghai R] Gitignore

P9831 [ICPC2020 Shanghai R] Gitignore - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 只看题意翻译这道题是做不出来的&#xff0c;还要去看英文里面的规定&#xff08;这里就不放英文了&#xff09;&#xff0c;主要问题是不要公用子文件夹。 例如: 1 / a / 2 2 / a / 3…...

LinkList集合方法(自写)

在使用以下方法时需要定义一个LinkNode类来定义变量&#xff0c;new一个新对象进行调用&#xff0c;输出时需要定义输出方法 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&#xff0c;高度为122552&#xff0c;那我想把图片切分为779乘以1280的格式。 步骤如下&#xff1a; 使用图像处理库&#xff08;如PIL或OpenCV&#xff09;加载原始图片。确定子图片的宽度和高度。计算原始图片的宽度和高度&am…...

ROS学习笔记(5):ros_control

1.ros_control简介 ros_control - ROS Wiki ros_control是为ROS提供的机器人控制包&#xff0c;包含一系列控制器接口、传动装置接口、控制器工具箱等,有效帮助机器人应用功能包快速落地&#xff0c;提高开发效率。 2.ros_control框架 ros_control总体框架&#xff1a; 针对…...

《008.Springboot+vue之自习室选座系统》

[火]《008.Springbootvue之自习室选座系统》 项目简介 [1]本系统涉及到的技术主要如下&#xff1a; 推荐环境配置&#xff1a;DEA jdk1.8 Maven MySQL 前后端分离; 后台&#xff1a;SpringBootMybatisredis; 前台&#xff1a;vueElementUI; [2]功能模块展示&#xff1a; 前端…...

道可云元宇宙每日资讯|5G数智新时代元宇宙发展论坛在厦门举办

道可云元宇宙每日简报&#xff08;2023年11月6日&#xff09;讯&#xff0c;今日元宇宙新鲜事有&#xff1a; 5G数智新时代元宇宙发展论坛在厦门举办 3日&#xff0c;由2023年中国金鸡百花电影节执委会主办、厦门电影节有限公司协办的“5G数智新时代元宇宙发展论坛暨‘中国白德…...

使用 Go 写入文件

在本教程中&#xff0c;我们将学习如何使用 Go 将数据写入文件。我们还将学习如何同时写入文件。 本教程有以下部分 将字符串写入文件将字节写入文件逐行将数据写入文件附加到文件同时写入文件 由于 Playground 不支持文件操作&#xff0c;请在本地系统中运行本教程的所有程…...

调用DeleteLocalRef的正确姿势

做安卓jni相关开发的总会在涉及到jni变量释放时怀疑人生&#xff0c;what? where? when? who? why? how? how much? 最近碰到一个比较奇怪的问题&#xff0c;有一个jni方法的耗时在随着调用次数的增加而上涨&#xff0c;但是没有明显的内存泄漏&#xff0c;经过我缜密分…...

抖音小店从0到1起店流程,实操经验分享!

我是电商珠珠 很多人在开店之后&#xff0c;并不知道怎么做。往往会有人跑来问我说&#xff0c;开店之后怎么做啊&#xff0c;流程方面我还不是很熟悉啊等等。 这份起店流程备好了&#xff0c;将来对你有用。 第一步&#xff0c;店铺基础设置 在店铺开好之后&#xff0c;不…...

MySQL权限

权限 MySQL 允许客户端用户连接到服务器并访问服务器管理数据&#xff0c;MySQL 用户权限系统的主要功能是对给定主机连接的用户进行身份验证&#xff0c;并将该用户与数据库的权限相关联。 在 MySQL8 之前&#xff0c;授权表使用 MyISAM 并且是非事务性的&#xff0c;在 MyS…...

Nginx服务器安装证书并启用SSL(acme.sh)

前提 您已购置vps服务器&#xff0c;例如阿里云全球站ecs、AWS EC2、Azure VM、GCP Compute等安全组已开启80、443端口&#xff0c;且访问源设置为0.0.0.0/0域名已设置A记录指向当前操作服务器&#xff0c;若您使用aws ec2&#xff0c;有公有 IPv4 DNS&#xff0c;可供使用 安…...

c++实现观察者模式

前言 我觉得这是最有意思的模式&#xff0c;其中一个动&#xff0c;另外的自动跟着动。发布-订阅&#xff0c;我觉得很巧妙。 代码 头文件 #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“}

问题&#xff1a;JAVA客户端访问influxdb2报错 说明&#xff1a;当前influxdb版本&#xff1a;2.6.1 使用依赖&#xff1a; <dependency><groupId>org.influxdb</groupId><artifactId>influxdb-java</artifactId><version>2.10</vers…...

Mysql查询今天到期、n天即将到期、还有n天过期相关sql

超级治愈的一段话 其实你已经很幸福了,吃饱穿暖,没病没灾,隔三岔五还能吃顿好的,偶尔还能睡到自然醒,肥嘟嘟的一身福气。人这一辈子,要是能够逃过天灾,躲过战乱,不遇歹人,不生大病,就已经是非常幸运了,要是还能家庭和谐,收人稳定,三五知己,那更是天大的福泽。 -…...

【漏洞复现】Apache Log4j Server 反序列化命令执行漏洞(CVE-2017-5645)

感谢互联网提供分享知识与智慧&#xff0c;在法治的社会里&#xff0c;请遵守有关法律法规 文章目录 1.1、漏洞描述1.2、漏洞等级1.3、影响版本1.4、漏洞复现1、基础环境2、漏洞扫描3、漏洞验证 1.5、深度利用1、反弹Shell 说明内容漏洞编号CVE-2017-5645漏洞名称Log4j Server …...

docker详细操作--未完待续

docker介绍 docker官网: Docker&#xff1a;加速容器应用程序开发 harbor官网&#xff1a;Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台&#xff0c;用于将应用程序及其依赖项&#xff08;如库、运行时环…...

shell脚本--常见案例

1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件&#xff1a; 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...

Debian系统简介

目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版&#xff…...

【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)

可以使用Sqliteviz这个网站免费编写sql语句&#xff0c;它能够让用户直接在浏览器内练习SQL的语法&#xff0c;不需要安装任何软件。 链接如下&#xff1a; sqliteviz 注意&#xff1a; 在转写SQL语法时&#xff0c;关键字之间有一个特定的顺序&#xff0c;这个顺序会影响到…...

Mac软件卸载指南,简单易懂!

刚和Adobe分手&#xff0c;它却总在Library里给你写"回忆录"&#xff1f;卸载的Final Cut Pro像电子幽灵般阴魂不散&#xff1f;总是会有残留文件&#xff0c;别慌&#xff01;这份Mac软件卸载指南&#xff0c;将用最硬核的方式教你"数字分手术"&#xff0…...

相机从app启动流程

一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...

什么是EULA和DPA

文章目录 EULA&#xff08;End User License Agreement&#xff09;DPA&#xff08;Data Protection Agreement&#xff09;一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA&#xff08;End User License Agreement&#xff09; 定义&#xff1a; EULA即…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南

文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/55aefaea8a9f477e86d065227851fe3d.pn…...

有限自动机到正规文法转换器v1.0

1 项目简介 这是一个功能强大的有限自动机&#xff08;Finite Automaton, FA&#xff09;到正规文法&#xff08;Regular Grammar&#xff09;转换器&#xff0c;它配备了一个直观且完整的图形用户界面&#xff0c;使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...