FlutterUnit 周边 | 收录排序算法可视化


theme: cyanosis

1. FlutterUnit 更新:排序算法可视化

排序算法可视化是用视图层表现出算法执行过程中排序的过程,感谢 编程的平行世界 在 《十几种排序算法的可视化效果,快来看看!👀》》 一文中提供的算法支持。我进行了一些代码和交互上的优化,将其集成到了 FlutterUnit 中,大家可以在 release v2.9.3 下载全平台应用查看体验 ~

image.png


掘金目前已经支持插入视频了,但目前支持西瓜视频。下面通过两个视频来看一下效果:

移动端: 交互视频

jvideo

桌面端: 交互视频

jvideo


2. 交互界面介绍

在移动端,排序算法可视化被放在 知识/可视排序 页签下,左上角的绿色按钮点击后启动排序,从而驱动数字列表数据变化,更新主界面产生排序的动态效果。下拉可以展开排序算法列表,选择对应的算法进行排序:

| 标题 | | | --- | --- | | 0240f2d1ca9b3778246fa722d1382bc.jpg | 433d6cab476b3dbc51ac22ed58b6854.jpg |

在桌面端,排序算法可视化先放在 可视排序 侧栏导航下,以后可能会拓展其他的有趣案例。

image.png

另外排序有设置界面,可以设置配置参数。

个数表示数据的数量,每个数据对应主界面中的一个线条。
间隔时长是排序过程中每步间的停顿时间,单位是微秒。
随机种子是随机数的种子,不为 -1 的话,相同的种子,每次重置生成的随机数列表一致。便于比较不排序算法下,同一组数据表现。

image.png


3. 项目的结构

这里核心代码新建了一个 algorithm 的包来单独维护,其中 algorithm/sort 文件夹中盛放排序的具体算法。把它们分文件放置,更便于阅读。

image.png


algorithm/data_scope 中,用于维护界面中的数据;在 algorithm/views 中处理视图组件的构建。

image.png


最后在 pubspec.yaml 中通过 path 引入本地的包,就可以在主项目中使用 algorithm 包中的组件进行展示。比如下面,在侧栏导航中添加一个 可视排序 的菜单栏,对应 DeskSortPage 组件。

yaml algorithm: path: packages/algorithm

image.png


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 | | --- | --- |--- | | image.png | image.png |image.png

```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 中的相关代码,那么本文就到这里,谢谢观看,我们下次再见~

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

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

相关文章

单目3D目标检测[基于深度辅助篇]

基于深度辅助的方法 1. Pseudo-LiDAR Pseudo-LiDAR from Visual Depth Estimation: Bridging the Gap in 3D Object Detection for Autonomous Driving康奈尔大学https://zhuanlan.zhihu.com/p/52803631 首先利用DRON或PSMNET从单目 (Monocular)或双目 (Stereo)图像获取对应的…

dijkstra算法python

Dijkstra算法是一种用于寻找带权重图中从一个起始节点到其他节点的最短路径的算法。下面是一个使用Python实现Dijkstra算法的示例代码&#xff1a; import heapq def dijkstra(graph, start): # 创建一个字典来存储节点到起始节点的最短距离 distances {node: float(infinity)…

设计院图纸加密防泄密方案——天锐绿盾加密软件@德人合科技

天锐绿盾是一款专业的企业信息化防泄密软件&#xff0c;主要针对文档全生命周期进行加密保护&#xff0c;包括创建、修改、传输、归档、分发、销毁等全过程。它可以加强外发数据及终端离线的管理&#xff0c;对正常授权外带范围内的数据流程进行规范。设计图纸、文档等成果数据…

(免费领源码)java#Springboot#mysql装修选购网站99192-计算机毕业设计项目选题推荐

摘 要 随着科学技术&#xff0c;计算机迅速的发展。在如今的社会中&#xff0c;市场上涌现出越来越多的新型的产品&#xff0c;人们有了不同种类的选择拥有产品的方式&#xff0c;而电子商务就是随着人们的需求和网络的发展涌动出的产物&#xff0c;电子商务网站是建立在企业与…

顺序表的定义与实现(数据结构与算法)

一、顺序表的定义 1. 顺序表的定义 #define MaxSize 10 //定义最大长度 typedef struct{ ElemType data[MaxSize]; //用静态的“数组”存放数据元素int length; //顺序表的当前长度 …

Ubuntu设置允许root用户登录

1、激活root用户 sudo passwd root //设置root密码 su root //接着使用root登录 2、设置允许root通过ssh登录 nano /etc/ssh/sshd_config 将 PermitRootLogin 改为 yes 3、重启ssh service ssh restart 到这里就好了

SparkSQL的Shuffle分区设定及异常数据处理API(去重、缺失值处理)

一、Spark SQL的Shuffle分区数目设定 二、异常数据处理API &#xff08;1&#xff09;去重方法dropDuplicates &#xff08;2&#xff09;删除有缺失值的行方法dropna &#xff08;3&#xff09;填充缺失值数据fillna 一、Spark SQL的Shuffle分区数目设定 在允许spark程序时&a…

德语中第二虚拟式在主动态的形式,柯桥哪里可以学德语

德语中第二虚拟式在主动态的形式 1. 对于大多数的动词&#xff0c;一般使用这样的一般现在时时态&#xff1a; wrde 动词原形 例句&#xff1a;Wenn es nicht so viel kosten wrde, wrde ich mir ein Haus am Meer kaufen. 如果不花这么多钱&#xff0c;我会在海边买一栋房…

8. Cesium中的camera

1. 介绍 camera是viewer.scene中的属性&#xff0c;用来控制当前的可见域。viewer.camera 是快捷写法&#xff0c;访问的是 viewer.scene.camera&#xff0c;所以调用时这两种写法都行。介绍常用的view.carmera方法。 &#xff08;1&#xff09;. setView Camera根据视图位置…

React 在非组件环境切换路由

我的react-router-dom版本是6.16.0。之前在react中是这样配置路由的 App.jsx import ReactDOM from react-dom/client; import { HashRouter, Route, Routes } from react-router-dom;const root ReactDOM.createRoot(document.getElementById("app")); root.rend…

Python实验项目4 :面对对象程序设计

1&#xff1a;运行下面的程序&#xff0c;回答问题。 &#xff08;1&#xff09;说明程序的执行过程&#xff1b; &#xff08;2&#xff09;程序运行结果是什么&#xff1f; # &#xff08;1&#xff09;说明程序的执行过程&#xff1b; # &#xff08;2&#xff09;程序运行…

android开发使用OkHttp自带的WebSocket实现IM功能

一、背景 android app开发经常会有IM需求&#xff0c;很多新手不晓得如何入手&#xff0c;难点在于通讯不中断。其实android发展到今天&#xff0c;很多技术都很完善&#xff0c;有很多类似框架可以实现。例如有&#xff1a;okhttp自带的websocket框架、easysocket等等。本文主…

vite工具官方地址 +前端工具插件

该项目与 Vite 一起提供服务和捆绑。推荐的 IDE 设置是 ** VSCode Volar。 **如果您需要测试组件和网页&#xff0c;请查看赛普拉斯和赛普拉斯组件测试。 有关更多说明&#xff0c;请参见。README.md vite官方地址

如何做一个无符号数识别程序

1.状态分析 我们可以把无符号数分为&#xff1a;整数&#xff0c;带小数&#xff0c;带指数部分三种形式。以此构建一个DFA。首先需识别输入是整数还是小数点&#xff0c;若是整数部分输入然后还要再循环识别一次是否有小数点&#xff0c;最后识别是否有指数部分&#xff0c;指…

Spring学习笔记—JDK动态代理

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; Spring专栏 ✨特色专栏&#xff1a; M…

Redis -- 基础知识2

1.Redis客户端介绍 1.基础介绍 Redis是一种客户端-服务器结构的程序&#xff0c;通过网络进行互动 客户端的多种形态 1.自带了命令行客户端&#xff1a;redis-cil 2.图形化界面的客户端&#xff1a;依赖windows系统&#xff0c;连接服务器有诸多限制&#xff0c;不建议使用 3.基…

Redis -- 基础知识1

1.介绍 1.初识Redis Redis&#xff1a;The open source, in-memory data store used by millions of developers as a database, cache, streaming engine, and message broker. in-memory data&#xff1a;在内存中存储&#xff0c;Redis是在分布式系统中存储起作用的 解释&am…

打印机连接网络后怎么安装驱动?

打印机在我们办公和生活中算是比较常见的设备&#xff0c;特别是在上班时需要时常打印各种文件&#xff0c;但是有时电脑上的打印机也会有无法打印的问题&#xff0c;或者新买的打印机需要先安装驱动才能正常打印的。 那么这个时候我们需要先检查电脑上的打印机是否有安装驱动&…

主线程和子线程的区别

在多线程编程中&#xff0c;我们通常会涉及到主线程&#xff08;也称为父线程&#xff09;和子线程&#xff08;也称为工作线程&#xff09;。 1. 主线程&#xff1a;主线程是程序的起点&#xff0c;它负责创建和管理其他线程。主线程通常执行程序的初始化操作&#xff0c;包括…

Kotlin中的嵌套类、内部类、枚举类、密封类、数据类、单例类、伴生对象

在Kotlin中&#xff0c;类可以分为以下几种类型&#xff0c;并使用样例代码进行说明&#xff1a; 嵌套类&#xff08;Nested Class&#xff09;&#xff1a;嵌套类是指可以嵌套在其他类中的类。嵌套类不能直接访问外部类的成员。例如&#xff0c;在下面的代码中&#xff0c;&q…