FlutterUnit 周边 | 收录排序算法可视化
theme: cyanosis
1. FlutterUnit 更新:排序算法可视化
排序算法可视化是用视图层表现出算法执行过程中排序的过程,感谢 编程的平行世界 在 《十几种排序算法的可视化效果,快来看看!👀》》 一文中提供的算法支持。我进行了一些代码和交互上的优化,将其集成到了 FlutterUnit 中,大家可以在 release v2.9.3 下载全平台应用查看体验 ~
掘金目前已经支持插入视频了,但目前支持西瓜视频。下面通过两个视频来看一下效果:
移动端: 交互视频
jvideo
桌面端: 交互视频
jvideo
2. 交互界面介绍
在移动端,排序算法可视化被放在 知识/可视排序 页签下,左上角的绿色按钮点击后启动排序,从而驱动数字列表数据变化,更新主界面产生排序的动态效果。下拉可以展开排序算法列表,选择对应的算法进行排序:
| 标题 | | | --- | --- | | |
|
在桌面端,排序算法可视化先放在 可视排序 侧栏导航下,以后可能会拓展其他的有趣案例。
另外排序有设置界面,可以设置配置参数。
个数表示数据的数量,每个数据对应主界面中的一个线条。
间隔时长是排序过程中每步间的停顿时间,单位是微秒。
随机种子是随机数的种子,不为 -1 的话,相同的种子,每次重置生成的随机数列表一致。便于比较不排序算法下,同一组数据表现。
3. 项目的结构
这里核心代码新建了一个 algorithm 的包来单独维护,其中 algorithm/sort 文件夹中盛放排序的具体算法。把它们分文件放置,更便于阅读。
在 algorithm/data_scope 中,用于维护界面中的数据;在 algorithm/views 中处理视图组件的构建。
最后在 pubspec.yaml 中通过 path 引入本地的包,就可以在主项目中使用 algorithm 包中的组件进行展示。比如下面,在侧栏导航中添加一个 可视排序 的菜单栏,对应 DeskSortPage 组件。
yaml algorithm: path: packages/algorithm
4. 代码实现细节:算法方面
可视化排序的思路是:在每次排序列表数据发生变化时,通过回调来通知处理。这里定义 SortCallback
类型方便维护,其返回 Future<void>
对象,可以回调排序过程中此时的列表数据。
dart typedef SortCallback = Future<void> Function(List<int> dist);
拿冒泡排序来说,定义 bubbleSort 函数,传入待排序的数字列表,每次循环完成,出发 callback 通知调用者。比如想要放慢排序的过程,每一步可以等待一定的时间,也就是设置中的间隔微秒数。
dart ///冒泡排序 Future<void> bubbleSort(List<int> src, SortCallback callback) async{ //控制需要进行排序的次数。每一轮循环都会确定一个数字的最终位置。 for (int i = 0; i < src.length; ++i) { //遍历当前未排序的元素,通过相邻的元素比较并交换位置来完成排序。 for (int j = 0; j < src.length - i - 1; ++j) { //如果 _numbers[j] 大于 _numbers[j + 1],则交换它们的位置,确保较大的元素移到右边。 if (src[j] > src[j + 1]) { int temp = src[j]; src[j] = src[j + 1]; src[j + 1] = temp; } //实现一个延迟,以便在ui上展示排序的动画效果 await callback(src); } } }
另外排序的函数结构都是一致的,输入待排序列表与回调,可以通过 typedef 定义一个排序函数类型 SortFunction:
dart typedef SortFunction = Future<void> Function(List<int> src, SortCallback callback);
这样就可以维护排序的名称和排序函数间的映射关系:
dart Map<String, SortFunction> sortFunctionMap = { 'insertion': insertionSort, 'bubble': bubbleSort, 'cocktail': cocktailSort, 'comb': combSort, 'pigeonHole': pigeonHoleSort, 'shell': shellSort, 'selection': selectionSort, 'gnome': gnomeSort, 'cycle': cycleSort, 'heap': heapSort, 'quick': quickSort, 'merge': mergeSort, };
5. 代码实现细节:数据方面
数据方面的代码在 data_scope
包中,这里排序界面中的数据有三大类:
其一是待排序数字列表。
其二是配置的参数。
其三是排序状态。
配置参数包括四个,通过 SortConfig 类维护:
```dart class SortConfig { final int count; // 列表数字数量 final int seed; // 随机数种子 final Duration duration; // 间隔时长 final String name; // 算法名称
SortConfig({ this.count = 100, this.duration = const Duration(microseconds: 1500), this.seed = -1, this.name = 'insertion', });
SortConfig copyWith({ int? count, int? seed, Duration? duration, String? name, }) => SortConfig( count:count??this.count, seed:seed??this.seed, duration:duration??this.duration, name:name??this.name, ); } ```
排序状态通过 SortStatus 枚举定义:
dart enum SortStatus{ none, // 未操作 sorting, // 排序中 sorted, // 排序完成 }
排序界面整体的数据状态通过 SortState 维护,它继承自 ChangeNotifier,可以在数据变化时调用 notifyListeners 通知监听者,从而实现界面的更新。 SortState 调用 sort 方法触发排序,会根据排序算法名,从 sortFunctionMap 中拿到排序算法调用。每次回调时触发 notifyListeners 方法通知更新。
```dart class SortState with ChangeNotifier{
SortState(){ reset(); }
SortStatus status = SortStatus.none; List data = []; SortConfig _config = SortConfig(); SortConfig get config => _config; Random random = Random();
set config(SortConfig config){ _config = config; reset(); notifyListeners(); }
void reset(){ data.clear(); status = SortStatus.none; notifyListeners(); int count = config.count; if(config.seed!=-1){ random = Random(config.seed); } for (int i = 0; i < count; i++) { data.add(random.nextInt(1000)); } }
void sort() async{ status = SortStatus.sorting; notifyListeners(); SortFunction? sortFunction = sortFunctionMap[config.name]; if(sortFunction!=null){ await sortFunction(data,(arr) async { await Future.delayed(config.duration); notifyListeners(); }); } status = SortStatus.sorted; notifyListeners(); } } ```
6. 代码实现细节:界面方面
这里目前没有使用三方状态管理包,而是通过 Flutter 内部的 InheritedNotifier 完成子树共享可监听数据的任务。
```dart class SortStateScope extends InheritedNotifier { const SortStateScope({ required super.notifier, required super.child, super.key, });
static SortState of(BuildContext context) => context.dependOnInheritedWidgetOfExactType ()!.notifier!; } ```
如果某个组件是数据的依赖者,在可监听对象发生变化时,会通知其更新。拿 SortButton 来说,他需要依赖排序状态 SortStatus
数据来展示不同的图标,或响应不同的事件。使用 SortStateScope.of(context) 相当于依赖了数据,那么数据(SortState)在 notifyListeners 时,就会通知 SortButton 进行重新构建,这就是 InheritedNotifier 组件的功能。
| none | sorting |sorted | | --- | --- |--- | | |
|
```dart class SortButton extends StatelessWidget { const SortButton({super.key});
@override Widget build(BuildContext context) { SortState state = SortStateScope.of(context); VoidCallback? action; IconData icon; Color color; switch (state.status) { case SortStatus.none: icon = Icons.notstartedoutlined; color = Colors.green; action = state.sort; break; case SortStatus.sorting: icon = Icons.stopcircleoutlined; color = Colors.grey; action = null; break; case SortStatus.sorted: icon = Icons.refresh; color = Colors.black; action = state.reset; break; }
return GestureDetector(onTap: action,child: Icon(icon,color: color,),
);
} } ```
最后,主体界面通过 CustomPainter 对数字列表进行绘制,遍历数据根据数值大小绘制不同高度的线条。
```dart class DataPainter extends CustomPainter { final List data;
DataPainter({required this.data});
@override void paint(Canvas canvas, Size size) { canvas.clipRect(Offset.zero & size); double itemWidth = size.width / data.length;
Paint paint = Paint();
paint.strokeWidth = itemWidth;
paint.strokeCap = StrokeCap.round;for (int i = 0; i < data.length; i++) {int value = data[i];if (value < 1000 * .10) {paint.color = Colors.blue.shade100;} else if (value < 1000 * .20) {paint.color = Colors.blue.shade200;} else if (value < 1000 * .30) {paint.color = Colors.blue.shade300;} else if (value < 1000 * .40) {paint.color = Colors.blue.shade400;} else if (value < 1000 * .50) {paint.color = Colors.blue.shade500;} else if (value < 1000 * .60) {paint.color = Colors.blue.shade600;} else if (value < 1000 * .70) {paint.color = Colors.blue.shade700;} else if (value < 1000 * .80) {paint.color = Colors.blue.shade800;} else if (value < 1000 * .90) {paint.color = Colors.blue.shade900;} else {paint.color = const Color(0xFF011E51);}canvas.drawLine(Offset(i * itemWidth + itemWidth / 2, 0),Offset(i * itemWidth + itemWidth / 2,size.height * (value / 1000),),paint);
}
}
@override bool shouldRepaint(covariant DataPainter oldDelegate) { return true; } } ```
整个核心的逻辑就是这些,有兴趣的可以自己查阅 FlutterUnit 中的相关代码,那么本文就到这里,谢谢观看,我们下次再见~
相关文章:

FlutterUnit 周边 | 收录排序算法可视化
theme: cyanosis 1. FlutterUnit 更新:排序算法可视化 排序算法可视化是用视图层表现出算法执行过程中排序的过程,感谢 编程的平行世界 在 《十几种排序算法的可视化效果,快来看看!👀》》 一文中提供的算法支持。我进行…...

代码随想录Day30 贪心05 LeetCode T435无重叠区间 T763划分字母区间 T56 合并区间
LeetCode T435 无重叠区间 题目链接:435. 无重叠区间 - 力扣(LeetCode) 题目思路: 这题思路和昨天的打气球类似,我们需要按照左区间或者右区间进行排序,然后哦判断第i个区间的左端点和第i-1个区间的右端点的大小关系,,如果大于等于,那么就无需操作,一旦…...

发展高质量存储力,中国高科技力量聚浪成潮
中国信息通信研究院指出,在全球数字化转型与产业变革的浪潮下,算力正在成为改变全球竞争格局的关键力量。而根据最新的《算力基础设施高质量发展行动计划》,算力是集信息计算力、数据存储力和网络运载力于一体的新型生产力。当前,…...

修改svc的LoadBalancer的IP引发的惨案
文章目录 背景修改externalIPs的操作api-server报错日志挽救教训 背景 k8s集群没有接外部负载均衡,部署istio的时候ingressgateway一直pending。 于是手动修改了这个lb svc的externalIP,于是k8s就崩了,如何崩的,且听我还道来。 …...
2520. 统计能整除数字的位数
2520. 统计能整除数字的位数 class Solution {public int countDigits(int num) {int res 0;int o num;while (num > 0) {if (o % (num % 10) 0) {res 1;}num num / 10;}return res;} }...
BeanUtils.copyProperties的用法
常见场景 我们如果有两个具有很多相同属性名的JavaBean对象a和b,想把a中的属性赋值到b,例如 接口中将接收到的前端请求参数XxxReqVo,我们想把这个入参转化为XxxQuery对象作为数据库的查询条件对象 传统做法是手动set,即 XxxQuery xxxQuer…...

【RabbitMQ 实战】12 镜像队列
一、镜像队列的概念 RabbitMQ的镜像队列是将消息副本存储在一组节点上,以提高可用性和可靠性。镜像队列将队列中的消息复制到一个或多个其他节点上,并使这些节点上的队列保持同步。当一个节点失败时,其他节点上的队列不受影响,因…...

PyCharm社区版安装
PyCharm社区版安装 到中国官网下载 https://www.jetbrains.com/zh-cn/pycharm/download/?sectionwindows 首次创建项目,会自动下载安装Python 3.9 社区版的区别 社区版的区别...

【LeetCode每日一题合集】2023.10.16-2023.10.22(只出现一次的数字Ⅲ)
文章目录 260. 只出现一次的数字 III⭐(异或)🐂2652. 倍数求和解法1——枚举模拟解法2—— O ( 1 ) O(1) O(1)容斥原理相似题目——1201. 丑数 III(二分查找容斥原理) 2530. 执行 K 次操作后的最大分数解法1——贪心优…...

尚硅谷大数据项目《在线教育之实时数仓》笔记003
视频地址:尚硅谷大数据项目《在线教育之实时数仓》_哔哩哔哩_bilibili 目录 第7章 数仓开发之ODS层 P015 第8章 数仓开发之DIM层 P016 P017 P018 P019 01、node001节点Linux命令 02、KafkaUtil.java 03、DimSinkApp.java P020 P021 P022 P023 第7章 数…...

【Linux】部署单体项目以及前后端分离项目(项目部署)
一、简介 以下就是Linux部署单机项目和前后端分离项目的优缺点,希望对你有所帮助。 1、Linux部署单机项目: 优点: 1.简化了系统管理:由于所有服务都在同一台机器上运行,因此可以简化系统管理和维护。 2.提高了性能&a…...

设计模式之门面模式
前言 什么是门面模式 门面模式是一种结构型设计模式,它提供了一个统一的接口,用来访问子系统中的一群接口。它定义了一个高层接口,让子系统更容易使用。这种模式常用于将一个复杂的子系统封装成一个简单的接口,使得客户端可以方…...

Postman的使用
Postman的使用 Postman断言Postman常用断言1、断言响应状态码2、断言包含某个字符串3、断言JSON数据4、Postman断言工作原理 Postman关联Postman自动关联创建环境 3、Postman参数化CSV文件JSON文件1、用例集的导入导出2、环境导出 Postman断言 让Postman工具代替人自动判断预期…...

QGIS008:QGIS拓扑检查、修改及验证
摘要:本文介绍使用QGIS拓扑检查器和几何图形检查器检查图层的拓扑错误,修改拓扑错误,并对修改后的图层进行错误验证。 实验数据: 链接:https://pan.baidu.com/s/1Vy2s-KYS-XJevqHNdavv9A?pwdf06o 提取码:…...
安装DBD-Oracle报错处理
cd DBD-Oracle-1.83 perl Makefile.PL make && make install make编译报错如下: /bin/ld: 找不到 -lnsl collect2: 错误:ld 返回 1 make: *** [Makefile:524:blib/arch/auto/DBD/Oracle/Oracle.so] 错误 1 [rootlocalhost DBD-Ora…...
【机器学习】KNN算法-鸢尾花种类预测
KNN算法-鸢尾花种类预测 文章目录 KNN算法-鸢尾花种类预测1. 数据集介绍2. KNN优缺点: K最近邻(K-Nearest Neighbors,KNN)算法是一种用于模式识别和分类的简单但强大的机器学习算法。它的工作原理非常直观:给定一个新数…...
LuatOS-SOC接口文档(air780E)--lora - lora驱动模块
常量 常量 类型 解释 lora.SLEEP number SLEEP模式 lora.STANDBY number STANDBY模式 lora.init(ic, loraconfig,spiconfig) lora初始化 参数 传入值类型 解释 string lora 型号,当前支持: llcc68 sx1268 table lora配置参数,与具体设备…...

Compose 自定义 - 绘制 Draw
一、概念 所有的绘制操作都是通过调整像素大小来执行的。若要确保项目在不同的设备密度和屏幕尺寸上都能采用一致的尺寸,请务必使用 .toPx() 对 dp 进行转换或者采用小数尺寸。 二、Modifier 修饰符绘制 官方页面 在修饰的可组合项之上或之下绘制。 .drawWithCon…...

c#学习相关系列之构造函数
目录 一、构造函数的作用 二、构造函数的特征 三、三种构造函数介绍 1、实例构造函数 2、静态构造函数 3、私有构造函数 一、构造函数的作用 构造函数用来创建对象,并且可以在构造函数中对此对象进行初始化。构造函数具有与类相同的名称,它通常用来…...

CS224W1.3——图表示的选择
文章目录 1. 图网络构成2. 选择一个合适的表示3. 图结构实例3.1 二部图3.2 图的表示 4. 节点和边的属性 这小节主要讲图表示的选择。 1. 图网络构成 对于每个实体,我们创建节点 N N N,对于每个关系,我们创建边 E E E,对于整体而言…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...

3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...

网页端 js 读取发票里的二维码信息(图片和PDF格式)
起因 为了实现在报销流程中,发票不能重用的限制,发票上传后,希望能读出发票号,并记录发票号已用,下次不再可用于报销。 基于上面的需求,研究了OCR 的方式和读PDF的方式,实际是可行的ÿ…...
背包问题双雄:01 背包与完全背包详解(Java 实现)
一、背包问题概述 背包问题是动态规划领域的经典问题,其核心在于如何在有限容量的背包中选择物品,使得总价值最大化。根据物品选择规则的不同,主要分为两类: 01 背包:每件物品最多选 1 次(选或不选&#…...