v7
This commit is contained in:
@@ -1,15 +1,10 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'iroute.dart';
|
||||
import 'route_history.dart';
|
||||
|
||||
const List<String> kDestinationsPaths = [
|
||||
'/color',
|
||||
'/counter',
|
||||
'/user',
|
||||
'/settings',
|
||||
];
|
||||
import 'iroute_config.dart';
|
||||
import 'route_history_manager.dart';
|
||||
import 'routes.dart';
|
||||
import 'views/not_find_view.dart';
|
||||
|
||||
AppRouterDelegate router = AppRouterDelegate();
|
||||
|
||||
@@ -18,120 +13,82 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
|
||||
|
||||
String get path => _path;
|
||||
|
||||
AppRouterDelegate() {
|
||||
// keepAlivePath.add('/color');
|
||||
_histories.add(RouteHistory(path));
|
||||
final IRoutePageBuilder? notFindPageBuilder;
|
||||
|
||||
AppRouterDelegate({this.notFindPageBuilder}) {
|
||||
_historyManager.recode(IRouteConfig(uri: Uri.parse(path)));
|
||||
}
|
||||
|
||||
final List<RouteHistory> _histories = [];
|
||||
final List<RouteHistory> _backHistories = [];
|
||||
Page _defaultNotFindPageBuilder(_, __) => const MaterialPage(
|
||||
child: Material(child: NotFindPage()),
|
||||
);
|
||||
|
||||
List<RouteHistory> get histories => _histories.reversed.toList();
|
||||
final RouteHistoryManager _historyManager = RouteHistoryManager();
|
||||
|
||||
bool get hasHistory => _histories.length > 1;
|
||||
|
||||
bool get hasBackHistory => _backHistories.isNotEmpty;
|
||||
RouteHistoryManager get historyManager => _historyManager;
|
||||
|
||||
/// 历史回退操作
|
||||
/// 将当前顶层移除,并加入 _backHistories 撤销列表
|
||||
/// 并转到前一路径
|
||||
void back() {
|
||||
if (!hasHistory) return;
|
||||
RouteHistory top = _histories.removeLast();
|
||||
_backHistories.add(top);
|
||||
if (_histories.isNotEmpty) {
|
||||
_path = _histories.last.path;
|
||||
if (_histories.last.extra != null) {
|
||||
_pathExtraMap[_path] = _histories.last.extra;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
/// 详见: [RouteHistoryManager.back]
|
||||
void back() => _historyManager.back(changeRoute);
|
||||
|
||||
void toHistory(RouteHistory history) {
|
||||
_path = history.path;
|
||||
if (history.extra != null) {
|
||||
_pathExtraMap[_path] = history.extra;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
/// 撤销回退操作
|
||||
/// 详见: [RouteHistoryManager.revocation]
|
||||
void revocation() => _historyManager.revocation(changeRoute);
|
||||
|
||||
void closeHistory(int index) {
|
||||
_histories.removeAt(index);
|
||||
_historyManager.close(index);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void clearHistory() {
|
||||
_histories.clear();
|
||||
_historyManager.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 撤销回退操作
|
||||
/// 取出回退列表的最后元素,跳转到该路径
|
||||
void revocation() {
|
||||
RouteHistory target = _backHistories.removeLast();
|
||||
_path = target.path;
|
||||
if (target.extra != null) {
|
||||
_pathExtraMap[_path] = target.extra;
|
||||
}
|
||||
_histories.add(target);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
int? get activeIndex {
|
||||
if (path.startsWith('/color')) return 0;
|
||||
if (path.startsWith('/counter')) return 1;
|
||||
if (path.startsWith('/user')) return 2;
|
||||
if (path.startsWith('/settings')) return 3;
|
||||
return null;
|
||||
}
|
||||
|
||||
final Map<String, Completer<dynamic>> _completerMap = {};
|
||||
|
||||
Completer<dynamic>? completer;
|
||||
|
||||
final Map<String, dynamic> _pathExtraMap = {};
|
||||
|
||||
final List<String> keepAlivePath = [];
|
||||
|
||||
FutureOr<dynamic> changePath(
|
||||
String value, {
|
||||
bool forResult = false,
|
||||
Object? extra,
|
||||
bool keepAlive = false,
|
||||
bool recordHistory = true,
|
||||
}) {
|
||||
FutureOr<dynamic> changeRoute(IRouteConfig config) {
|
||||
String value = config.uri.path;
|
||||
if (_path == value) null;
|
||||
if (forResult) {
|
||||
if (config.forResult) {
|
||||
_completerMap[value] = Completer();
|
||||
}
|
||||
if (keepAlive) {
|
||||
if (config.keepAlive) {
|
||||
if (keepAlivePath.contains(value)) {
|
||||
keepAlivePath.remove(value);
|
||||
}
|
||||
keepAlivePath.add(value);
|
||||
}
|
||||
if (extra != null) {
|
||||
_pathExtraMap[value] = extra;
|
||||
if (config.extra != null) {
|
||||
_pathExtraMap[value] = config.extra;
|
||||
}
|
||||
|
||||
if (recordHistory) {
|
||||
_addPathToHistory(value,extra);
|
||||
if (config.recordHistory) {
|
||||
_historyManager.recode(config);
|
||||
}
|
||||
|
||||
_path = value;
|
||||
notifyListeners();
|
||||
if (forResult) {
|
||||
if (config.forResult) {
|
||||
return _completerMap[value]!.future;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void _addPathToHistory(String value, Object? extra) {
|
||||
if (_histories.isNotEmpty && value == _histories.last.path) return;
|
||||
_histories.add(RouteHistory(
|
||||
value,
|
||||
extra: _pathExtraMap[path],
|
||||
FutureOr<dynamic> changePath(
|
||||
String value, {
|
||||
bool forResult = false,
|
||||
Object? extra,
|
||||
bool keepAlive = false,
|
||||
bool recordHistory = true,
|
||||
}) {
|
||||
return changeRoute(IRouteConfig(
|
||||
uri: Uri.parse(value),
|
||||
forResult: forResult,
|
||||
extra: extra,
|
||||
keepAlive: keepAlive,
|
||||
recordHistory: recordHistory,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -156,7 +113,7 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
|
||||
|
||||
/// 去除和 topPages 中重复的界面
|
||||
pages.removeWhere(
|
||||
(element) => topPages.map((e) => e.key).contains(element.key));
|
||||
(element) => topPages.map((e) => e.key).contains(element.key));
|
||||
}
|
||||
|
||||
pages.addAll(topPages);
|
||||
@@ -165,23 +122,26 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
|
||||
|
||||
List<Page> _buildPageByPathFromTree(BuildContext context, String path) {
|
||||
List<Page> result = [];
|
||||
List<IRoute> iRoutes = root.find(path);
|
||||
List<IRouteNode> iRoutes = rootRoute.find(path);
|
||||
if (iRoutes.isNotEmpty) {
|
||||
for (int i = 0; i < iRoutes.length; i++) {
|
||||
IRoute iroute = iRoutes[i];
|
||||
IRouteNode iroute = iRoutes[i];
|
||||
String path = iroute.path;
|
||||
Object? extra = _pathExtraMap[path];
|
||||
bool keepAlive = keepAlivePath.contains(path);
|
||||
bool forResult = _completerMap.containsKey(path);
|
||||
Page? page = iroute.builder?.call(
|
||||
context,
|
||||
IRouteData(
|
||||
uri: Uri.parse(path),
|
||||
extra: extra,
|
||||
keepAlive: keepAlive,
|
||||
forResult: forResult,
|
||||
),
|
||||
IRouteConfig config = IRouteConfig(
|
||||
uri: Uri.parse(path),
|
||||
extra: extra,
|
||||
keepAlive: keepAlive,
|
||||
forResult: forResult,
|
||||
);
|
||||
Page? page;
|
||||
if (iroute is NotFindNode) {
|
||||
page = (notFindPageBuilder ?? _defaultNotFindPageBuilder)(context, config);
|
||||
} else {
|
||||
page = iroute.createPage(context, config);
|
||||
}
|
||||
if (page != null) {
|
||||
result.add(page);
|
||||
}
|
||||
@@ -201,8 +161,7 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
|
||||
_completerMap[path]?.complete(result);
|
||||
_completerMap.remove(path);
|
||||
}
|
||||
|
||||
changePath(backPath(path),recordHistory: false);
|
||||
changePath(backPath(path), recordHistory: false);
|
||||
return route.didPop(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,185 +1,105 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../pages/color/color_add_page.dart';
|
||||
import '../../../pages/color/color_detail_page.dart';
|
||||
import '../../../pages/color/color_page.dart';
|
||||
import '../../../pages/counter/counter_page.dart';
|
||||
import '../../../pages/user/user_page.dart';
|
||||
import '../../../pages/settings/settings_page.dart';
|
||||
import '../transition/fade_transition_page.dart';
|
||||
import 'iroute_config.dart';
|
||||
|
||||
class IRoute {
|
||||
typedef IRoutePageBuilder = Page? Function(
|
||||
BuildContext context,
|
||||
IRouteConfig data,
|
||||
);
|
||||
|
||||
typedef IRouteWidgetBuilder = Widget? Function(
|
||||
BuildContext context,
|
||||
IRouteConfig data,
|
||||
);
|
||||
|
||||
abstract class IRouteNode {
|
||||
final String path;
|
||||
final IRoutePageBuilder? builder;
|
||||
final List<IRoute> children;
|
||||
final List<IRouteNode> children;
|
||||
|
||||
const IRoute({
|
||||
const IRouteNode({
|
||||
required this.path,
|
||||
this.children = const [],
|
||||
this.builder,
|
||||
required this.children,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'IRoute{path: $path, children: $children}';
|
||||
Page? createPage(BuildContext context, IRouteConfig config);
|
||||
|
||||
List<IRouteNode> find(String input,) {
|
||||
return findNodes(this, Uri.parse(input), 0, '/', []);
|
||||
}
|
||||
|
||||
IRoute? match(String path) {
|
||||
return matchRoute(this, path);
|
||||
}
|
||||
|
||||
List<IRoute> find(String input){
|
||||
String fixInput = input.substring(1);
|
||||
List<IRoute> nodes = findNodes(this,fixInput,0,'/',[]);
|
||||
if(nodes.isNotEmpty&&nodes.last.path!=input){
|
||||
return [];
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
List<IRoute> findNodes(IRoute node,String input,int deep,String prefix,List<IRoute> result){
|
||||
String separator = '/';
|
||||
|
||||
List<String> parts = input.split(separator);
|
||||
if(deep>parts.length-1){
|
||||
List<IRouteNode> findNodes(
|
||||
IRouteNode node,
|
||||
Uri uri,
|
||||
int deep,
|
||||
String prefix,
|
||||
List<IRouteNode> result,
|
||||
) {
|
||||
List<String> parts = uri.pathSegments;
|
||||
if (deep > parts.length - 1) {
|
||||
return result;
|
||||
}
|
||||
String target = parts[deep];
|
||||
if(node.children.isNotEmpty){
|
||||
List<IRoute> nodes = node.children.where((e) => e.path==prefix+target).toList();
|
||||
if (node.children.isNotEmpty) {
|
||||
target = prefix + target;
|
||||
List<IRouteNode> nodes = node.children.where((e) => e.path == target).toList();
|
||||
bool match = nodes.isNotEmpty;
|
||||
if(match){
|
||||
IRoute matched = nodes.first;
|
||||
if (match) {
|
||||
IRouteNode matched = nodes.first;
|
||||
result.add(matched);
|
||||
String nextPrefix = '${matched.path}$separator';
|
||||
findNodes(matched, input, ++deep,nextPrefix,result);
|
||||
String nextPrefix = '${matched.path}/';
|
||||
findNodes(matched, uri, ++deep, nextPrefix, result);
|
||||
}else{
|
||||
result.add(NotFindNode(path: target));
|
||||
return result;
|
||||
}
|
||||
}else{
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// List<IRoute> findNodes(IRoute node,String input,int deep,String prefix,List<IRoute> result){
|
||||
// String separator = '/';
|
||||
// List<String> parts = input.split(separator);
|
||||
// if(deep>parts.length-1){
|
||||
// return result;
|
||||
// }
|
||||
// String target = parts[deep];
|
||||
// if(node.children.isNotEmpty){
|
||||
// List<IRoute> nodes = node.children.where((e) => e.path==prefix+target).toList();
|
||||
// bool match = nodes.isNotEmpty;
|
||||
// if(match){
|
||||
// IRoute matched = nodes.first;
|
||||
// result.add(matched);
|
||||
// String nextPrefix = '${matched.path}$separator';
|
||||
// findNodes(matched, input, ++deep,nextPrefix,result);
|
||||
// }
|
||||
// }else{
|
||||
// return result;
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
/// 优先调用 [pageBuilder] 构建 Page
|
||||
/// 没有 [pageBuilder] 时, 使用 [widgetBuilder] 构建组件
|
||||
/// 没有 [pageBuilder] 和 [widgetBuilder] 时, 使用 [widget] 构建组件
|
||||
class IRoute extends IRouteNode {
|
||||
final IRoutePageBuilder? pageBuilder;
|
||||
final IRouteWidgetBuilder? widgetBuilder;
|
||||
final Widget? widget;
|
||||
final Map<String,dynamic>? mata;
|
||||
|
||||
IRoute? matchRoute(IRoute route, String path) {
|
||||
if (route.path == path) {
|
||||
return route;
|
||||
} else {
|
||||
if (route.children.isNotEmpty) {
|
||||
for (int i = 0; i < route.children.length; i++) {
|
||||
IRoute current = route.children[i];
|
||||
IRoute? target = matchRoute(current, path);
|
||||
if (target != null) {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
const IRoute({
|
||||
required super.path,
|
||||
super.children = const [],
|
||||
this.mata,
|
||||
this.widget,
|
||||
this.pageBuilder,
|
||||
this.widgetBuilder,
|
||||
});
|
||||
|
||||
@override
|
||||
Page? createPage(BuildContext context, IRouteConfig config) {
|
||||
if (pageBuilder != null) {
|
||||
return pageBuilder!(context, config);
|
||||
}
|
||||
Widget? child;
|
||||
if (widgetBuilder != null) {
|
||||
child = widgetBuilder!(context, config);
|
||||
}
|
||||
child ??= widget;
|
||||
if (child != null) {
|
||||
return MaterialPage(child: child, key: config.pageKey);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
typedef IRoutePageBuilder = Page? Function(
|
||||
BuildContext context, IRouteData data);
|
||||
|
||||
class IRouteData {
|
||||
final Object? extra;
|
||||
final bool forResult;
|
||||
final Uri uri;
|
||||
final bool keepAlive;
|
||||
|
||||
IRouteData({
|
||||
this.extra,
|
||||
required this.uri,
|
||||
this.forResult = false,
|
||||
this.keepAlive = false,
|
||||
});
|
||||
class NotFindNode extends IRouteNode{
|
||||
NotFindNode({required super.path, super.children= const[]});
|
||||
|
||||
@override
|
||||
Page? createPage(BuildContext context, IRouteConfig config) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
IRoute root = IRoute(path: 'root', children: kDestinationsIRoutes);
|
||||
|
||||
List<IRoute> kDestinationsIRoutes = [
|
||||
IRoute(
|
||||
path: '/color',
|
||||
builder: (ctx, data) {
|
||||
return const FadeTransitionPage(
|
||||
key: ValueKey('/color'),
|
||||
child: ColorPage(),
|
||||
);
|
||||
},
|
||||
children: [
|
||||
IRoute(
|
||||
path: '/color/detail',
|
||||
builder: (ctx, data) {
|
||||
final Map<String, String> queryParams = data.uri.queryParameters;
|
||||
String? selectedColor = queryParams['color'];
|
||||
Color color = Colors.black;
|
||||
if (selectedColor != null) {
|
||||
color = Color(int.parse(selectedColor, radix: 16));
|
||||
} else if (data.extra is Color) {
|
||||
color = data.extra as Color;
|
||||
}
|
||||
return FadeTransitionPage(
|
||||
key: const ValueKey('/color/detail'),
|
||||
child: ColorDetailPage(color: color),
|
||||
);
|
||||
},
|
||||
),
|
||||
IRoute(
|
||||
path: '/color/add',
|
||||
builder: (ctx, data) {
|
||||
return const FadeTransitionPage(
|
||||
key: ValueKey('/color/add'),
|
||||
child: ColorAddPage(),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
IRoute(
|
||||
path: '/counter',
|
||||
builder: (ctx, data) {
|
||||
return const FadeTransitionPage(
|
||||
key: ValueKey('/counter'),
|
||||
child: CounterPage(),
|
||||
);
|
||||
}),
|
||||
IRoute(
|
||||
path: '/user',
|
||||
builder: (ctx, data) {
|
||||
return const FadeTransitionPage(
|
||||
key: ValueKey('/user'),
|
||||
child: UserPage(),
|
||||
);
|
||||
}),
|
||||
IRoute(
|
||||
path: '/settings',
|
||||
builder: (ctx, data) {
|
||||
return const FadeTransitionPage(
|
||||
key: ValueKey('/settings'),
|
||||
child: SettingPage(),
|
||||
);
|
||||
}),
|
||||
];
|
||||
|
||||
35
lib/v7/app/navigation/router/iroute_config.dart
Normal file
35
lib/v7/app/navigation/router/iroute_config.dart
Normal file
@@ -0,0 +1,35 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class IRouteConfig {
|
||||
final Object? extra;
|
||||
final bool forResult;
|
||||
final Uri uri;
|
||||
final bool keepAlive;
|
||||
final bool recordHistory;
|
||||
|
||||
const IRouteConfig({
|
||||
this.extra,
|
||||
required this.uri,
|
||||
this.forResult = false,
|
||||
this.keepAlive = false,
|
||||
this.recordHistory = false,
|
||||
});
|
||||
|
||||
String get path => uri.path;
|
||||
|
||||
IRouteConfig copyWith({
|
||||
Object? extra,
|
||||
bool? forResult,
|
||||
bool? keepAlive,
|
||||
bool? recordHistory,
|
||||
}) =>
|
||||
IRouteConfig(
|
||||
extra: extra ?? this.extra,
|
||||
forResult: forResult ?? this.forResult,
|
||||
keepAlive: keepAlive ?? this.keepAlive,
|
||||
recordHistory: recordHistory ?? this.recordHistory,
|
||||
uri: uri,
|
||||
);
|
||||
|
||||
ValueKey get pageKey => ValueKey(path);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
class RouteHistory{
|
||||
final String path;
|
||||
final Object? extra;
|
||||
|
||||
RouteHistory(this.path, { this.extra});
|
||||
}
|
||||
@@ -1,12 +1,47 @@
|
||||
// import 'route_history.dart';
|
||||
//
|
||||
// class RouteHistoryManager{
|
||||
// final List<RouteHistory> _histories = [];
|
||||
// final List<RouteHistory> _backHistories = [];
|
||||
//
|
||||
// List<RouteHistory> get histories => _histories.reversed.toList();
|
||||
//
|
||||
// bool get hasHistory => _histories.length > 1;
|
||||
//
|
||||
// bool get hasBackHistory => _backHistories.isNotEmpty;
|
||||
// }
|
||||
import 'iroute_config.dart';
|
||||
|
||||
typedef OnRouteChange = void Function(IRouteConfig config);
|
||||
|
||||
class RouteHistoryManager{
|
||||
final List<IRouteConfig> _histories = [];
|
||||
final List<IRouteConfig> _backHistories = [];
|
||||
|
||||
List<IRouteConfig> get histories => _histories.reversed.toList();
|
||||
|
||||
bool get hasHistory => _histories.length > 1;
|
||||
|
||||
bool get hasBackHistory => _backHistories.isNotEmpty;
|
||||
|
||||
/// 将 [config] 加入历史记录
|
||||
void recode(IRouteConfig config){
|
||||
if (_histories.isNotEmpty && config.path == _histories.last.path) return;
|
||||
_histories.add(config);
|
||||
}
|
||||
|
||||
/// 历史回退操作
|
||||
/// 将当前顶层移除,并加入 [_backHistories] 撤销列表
|
||||
/// 并转到前一路径 [_histories.last]
|
||||
void back(OnRouteChange callback) {
|
||||
if (!hasHistory) return;
|
||||
IRouteConfig top = _histories.removeLast();
|
||||
_backHistories.add(top);
|
||||
if (_histories.isNotEmpty) {
|
||||
callback(_histories.last);
|
||||
}
|
||||
}
|
||||
|
||||
/// 撤销回退操作
|
||||
/// 取出回退列表的最后元素,跳转到该路径
|
||||
void revocation(OnRouteChange callback) {
|
||||
IRouteConfig target = _backHistories.removeLast();
|
||||
callback(target);
|
||||
}
|
||||
|
||||
void close(int index) {
|
||||
_histories.removeAt(index);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_histories.clear();
|
||||
}
|
||||
}
|
||||
41
lib/v7/app/navigation/router/routes.dart
Normal file
41
lib/v7/app/navigation/router/routes.dart
Normal file
@@ -0,0 +1,41 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'iroute_config.dart';
|
||||
import 'iroute.dart';
|
||||
import '../../../pages/color/color_add_page.dart';
|
||||
import '../../../pages/color/color_detail_page.dart';
|
||||
import '../../../pages/color/color_page.dart';
|
||||
import '../../../pages/counter/counter_page.dart';
|
||||
import '../../../pages/user/user_page.dart';
|
||||
import '../../../pages/settings/settings_page.dart';
|
||||
import '../../../pages/sort/views/sort_page.dart';
|
||||
|
||||
|
||||
IRoute rootRoute = const IRoute(
|
||||
path: 'root',
|
||||
children: [
|
||||
IRoute(
|
||||
path: '/color',
|
||||
widget: ColorPage(),
|
||||
children: [
|
||||
IRoute(path: '/color/detail', widgetBuilder: _buildColorDetail),
|
||||
IRoute(path: '/color/add', widget: ColorAddPage()),
|
||||
],
|
||||
),
|
||||
IRoute(path: '/counter', widget: CounterPage()),
|
||||
IRoute(path: '/sort', widget: SortPage()),
|
||||
IRoute(path: '/user', widget: UserPage()),
|
||||
IRoute(path: '/settings', widget: SettingPage()),
|
||||
],
|
||||
);
|
||||
|
||||
Widget? _buildColorDetail(BuildContext context, IRouteConfig data) {
|
||||
final Map<String, String> queryParams = data.uri.queryParameters;
|
||||
String? selectedColor = queryParams['color'];
|
||||
Color color = Colors.black;
|
||||
if (selectedColor != null) {
|
||||
color = Color(int.parse(selectedColor, radix: 16));
|
||||
} else if (data.extra is Color) {
|
||||
color = data.extra as Color;
|
||||
}
|
||||
return ColorDetailPage(color: color);
|
||||
}
|
||||
25
lib/v7/app/navigation/router/views/not_find_view.dart
Normal file
25
lib/v7/app/navigation/router/views/not_find_view.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NotFindPage extends StatelessWidget {
|
||||
const NotFindPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Material(
|
||||
child: Center(
|
||||
child: Wrap(
|
||||
spacing: 16,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
direction: Axis.vertical,
|
||||
children: [
|
||||
Icon(Icons.nearby_error,size: 64, color: Colors.redAccent),
|
||||
Text(
|
||||
'404 Page Not Find',
|
||||
style: TextStyle(fontSize: 24, color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FadePageTransitionsBuilder extends PageTransitionsBuilder {
|
||||
|
||||
const FadePageTransitionsBuilder();
|
||||
|
||||
@override
|
||||
Widget buildTransitions<T>(
|
||||
PageRoute<T>? route,
|
||||
BuildContext? context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
Widget child,
|
||||
) {
|
||||
return _FadePagePageTransition(
|
||||
animation: animation,
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FadePagePageTransition extends StatelessWidget {
|
||||
|
||||
const _FadePagePageTransition({
|
||||
required this.animation,
|
||||
required this.secondaryAnimation,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
final Animation<double> animation;
|
||||
final Animation<double> secondaryAnimation;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var curveTween = CurveTween(curve: Curves.easeIn);
|
||||
return FadeTransition(
|
||||
opacity: animation.drive(curveTween),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,7 @@ class FadeTransitionPage<T> extends Page<T> {
|
||||
});
|
||||
|
||||
@override
|
||||
Route<T> createRoute(BuildContext context) =>
|
||||
PageBasedFadeTransitionRoute<T>(this);
|
||||
Route<T> createRoute(BuildContext context) => PageBasedFadeTransitionRoute<T>(this);
|
||||
}
|
||||
|
||||
class PageBasedFadeTransitionRoute<T> extends PageRoute<T> {
|
||||
|
||||
@@ -12,10 +12,11 @@ class AppNavigationRail extends StatefulWidget {
|
||||
class _AppNavigationRailState extends State<AppNavigationRail> {
|
||||
|
||||
final List<MenuMeta> deskNavBarMenus = const [
|
||||
MenuMeta(label: '颜色板', icon: Icons.color_lens_outlined),
|
||||
MenuMeta(label: '计数器', icon: Icons.add_chart),
|
||||
MenuMeta(label: '我的', icon: Icons.person),
|
||||
MenuMeta(label: '设置', icon: Icons.settings),
|
||||
MenuMeta(label: '颜色板', icon: Icons.color_lens_outlined,path: '/color'),
|
||||
MenuMeta(label: '计数器', icon: Icons.add_chart,path: '/counter'),
|
||||
MenuMeta(label: '排序', icon: Icons.sort,path: '/sort'),
|
||||
MenuMeta(label: '我的', icon: Icons.person,path: '/user'),
|
||||
MenuMeta(label: '设置', icon: Icons.settings,path: '/settings'),
|
||||
];
|
||||
|
||||
@override
|
||||
@@ -41,21 +42,33 @@ class _AppNavigationRailState extends State<AppNavigationRail> {
|
||||
),
|
||||
tail: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 6.0),
|
||||
child: Text('V0.0.6',style: TextStyle(color: Colors.white,fontSize: 12),),
|
||||
child: Text('V0.0.7',style: TextStyle(color: Colors.white,fontSize: 12),),
|
||||
),
|
||||
backgroundColor: const Color(0xff3975c6),
|
||||
onDestinationSelected: _onDestinationSelected,
|
||||
selectedIndex: router.activeIndex,
|
||||
selectedIndex: activeIndex,
|
||||
),
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
RegExp _segReg = RegExp(r'/\w+');
|
||||
|
||||
int? get activeIndex{
|
||||
String path = router.path;
|
||||
RegExpMatch? match = _segReg.firstMatch(path);
|
||||
if(match==null) return null;
|
||||
String? target = match.group(0);
|
||||
int index = deskNavBarMenus.indexWhere((menu) => menu.path==target);
|
||||
if(index==-1) return null;
|
||||
return index;
|
||||
}
|
||||
|
||||
void _onDestinationSelected(int index) {
|
||||
String path = deskNavBarMenus[index].path!;
|
||||
if(index==1){
|
||||
router.changePath(kDestinationsPaths[index],keepAlive: true);
|
||||
router.changePath(path,keepAlive: true);
|
||||
}else{
|
||||
router.changePath(kDestinationsPaths[index]);
|
||||
router.changePath(path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ Map<String, String> kRouteLabelMap = {
|
||||
'/color/add': '添加颜色',
|
||||
'/color/detail': '颜色详情',
|
||||
'/counter': '计数器',
|
||||
'/sort': '可视化排序算法',
|
||||
'/user': '我的',
|
||||
'/settings': '系统设置',
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:iroute/components/components.dart';
|
||||
import '../../router/app_router_delegate.dart';
|
||||
import '../../router/route_history.dart';
|
||||
import '../../router/iroute_config.dart';
|
||||
import 'app_top_bar.dart';
|
||||
|
||||
class HistoryViewIcon extends StatelessWidget{
|
||||
@@ -55,7 +55,7 @@ class HistoryViewIcon extends StatelessWidget{
|
||||
}
|
||||
|
||||
class HistoryItem extends StatefulWidget {
|
||||
final RouteHistory history;
|
||||
final IRouteConfig history;
|
||||
final VoidCallback onPressed;
|
||||
final VoidCallback onDelete;
|
||||
|
||||
@@ -83,7 +83,7 @@ class _HistoryItemState extends State<HistoryItem> {
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
Text(kRouteLabelMap[widget.history.path]!),
|
||||
Text(kRouteLabelMap[widget.history.path]??'未知路由'),
|
||||
],
|
||||
)),
|
||||
GestureDetector(
|
||||
@@ -124,7 +124,8 @@ class _HistoryPanelState extends State<HistoryPanel> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if(router.histories.isEmpty){
|
||||
List<IRouteConfig> histories = router.historyManager.histories;
|
||||
if(histories.isEmpty){
|
||||
return const Center(
|
||||
child: Text(
|
||||
'暂无浏览历史记录',
|
||||
@@ -135,18 +136,18 @@ class _HistoryPanelState extends State<HistoryPanel> {
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||
itemExtent: 46,
|
||||
itemCount: router.histories.length,
|
||||
itemCount: histories.length,
|
||||
itemBuilder: (_, index) =>
|
||||
HistoryItem(
|
||||
onDelete: (){
|
||||
int fixIndex = router.histories.length - 1 - index;
|
||||
int fixIndex = histories.length - 1 - index;
|
||||
router.closeHistory(fixIndex);
|
||||
},
|
||||
onPressed: (){
|
||||
router.toHistory(router.histories[index]);
|
||||
router.changeRoute(histories[index].copyWith(recordHistory: false));
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
history: router.histories[index]),
|
||||
history: histories[index]),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ class _RouteHistoryButtonState extends State<RouteHistoryButton> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool hasHistory = router.hasHistory;
|
||||
bool hasBackHistory = router.hasBackHistory;
|
||||
bool hasHistory = router.historyManager.hasHistory;
|
||||
bool hasBackHistory = router.historyManager.hasBackHistory;
|
||||
Color activeColor = const Color(0xff9195AC);
|
||||
Color inActiveColor = const Color(0xffC7CAD5);
|
||||
Color historyColor = hasHistory?activeColor:inActiveColor;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'navigation/router/app_router_delegate.dart';
|
||||
import '../pages/sort/provider/state.dart';
|
||||
import 'navigation/transition/fade_page_transitions_builder.dart';
|
||||
import 'navigation/views/app_navigation.dart';
|
||||
import 'navigation/views/app_navigation_rail.dart';
|
||||
|
||||
|
||||
class UnitApp extends StatelessWidget {
|
||||
const UnitApp({super.key});
|
||||
@@ -9,20 +10,32 @@ class UnitApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return MaterialApp(
|
||||
theme: ThemeData(
|
||||
fontFamily: "宋体",
|
||||
scaffoldBackgroundColor: Colors.white,
|
||||
appBarTheme: const AppBarTheme(
|
||||
elevation: 0,
|
||||
iconTheme: IconThemeData(color: Colors.black),
|
||||
titleTextStyle: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
))),
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: AppNavigation()
|
||||
return SortStateScope(
|
||||
notifier: SortState(),
|
||||
child: MaterialApp(
|
||||
theme: ThemeData(
|
||||
fontFamily: "宋体",
|
||||
scaffoldBackgroundColor: Colors.white,
|
||||
pageTransitionsTheme: const PageTransitionsTheme(
|
||||
builders: {
|
||||
TargetPlatform.android: ZoomPageTransitionsBuilder(),
|
||||
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
|
||||
TargetPlatform.macOS: FadePageTransitionsBuilder(),
|
||||
TargetPlatform.windows: FadePageTransitionsBuilder(),
|
||||
TargetPlatform.linux: FadePageTransitionsBuilder(),
|
||||
}
|
||||
),
|
||||
appBarTheme: const AppBarTheme(
|
||||
elevation: 0,
|
||||
iconTheme: IconThemeData(color: Colors.black),
|
||||
titleTextStyle: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
))),
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: AppNavigation()
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,12 +38,12 @@ class _ColorPageState extends State<ColorPage> {
|
||||
void _selectColor(Color color){
|
||||
// String value = color.value.toRadixString(16);
|
||||
// router.path = '/color/detail?color=$value';
|
||||
router.changePath('/color/detail',extra: color);
|
||||
router.changePath('/color/detail_error',extra: color);
|
||||
|
||||
}
|
||||
|
||||
void _toAddPage() async {
|
||||
Color? color = await router.changePath('/color/add',forResult: true);
|
||||
Color? color = await router.changePath('/color/add',forResult: true,recordHistory: false);
|
||||
if (color != null) {
|
||||
setState(() {
|
||||
_colors.add(color);
|
||||
|
||||
46
lib/v7/pages/sort/functions.dart
Normal file
46
lib/v7/pages/sort/functions.dart
Normal file
@@ -0,0 +1,46 @@
|
||||
import 'functions/bubble.dart';
|
||||
import 'functions/cocktail.dart';
|
||||
import 'functions/comb.dart';
|
||||
import 'functions/cycle.dart';
|
||||
import 'functions/gnome.dart';
|
||||
import 'functions/heap.dart';
|
||||
import 'functions/insertion.dart';
|
||||
import 'functions/merage.dart';
|
||||
import 'functions/pigeonHole.dart';
|
||||
import 'functions/quick.dart';
|
||||
import 'functions/selection.dart';
|
||||
import 'functions/shell.dart';
|
||||
|
||||
typedef SortFunction = Future<void> Function(List<int> src, SortCallback callback);
|
||||
typedef SortCallback = Future<void> Function(List<int> dist);
|
||||
|
||||
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,
|
||||
'mergeSort': mergeSort,
|
||||
};
|
||||
|
||||
Map<String, String> sortNameMap = {
|
||||
'insertion': '插入排序',
|
||||
'bubble': '冒泡排序',
|
||||
'cocktail': '鸡尾酒排序(双向冒泡排序)',
|
||||
'comb': '梳排序',
|
||||
'pigeonHole': '鸽巢排序',
|
||||
'shell': '希尔排序',
|
||||
'selection': '选择排序',
|
||||
'gnome': '侏儒排序',
|
||||
'cycle': '循环排序',
|
||||
'heap': '堆排序',
|
||||
'quick': '快速排序',
|
||||
'mergeSort': '归并排序',
|
||||
};
|
||||
|
||||
19
lib/v7/pages/sort/functions/bubble.dart
Normal file
19
lib/v7/pages/sort/functions/bubble.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
import '../functions.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
52
lib/v7/pages/sort/functions/cocktail.dart
Normal file
52
lib/v7/pages/sort/functions/cocktail.dart
Normal file
@@ -0,0 +1,52 @@
|
||||
import '../functions.dart';
|
||||
|
||||
///鸡尾酒排序(双向冒泡排序)
|
||||
Future<void> cocktailSort(List<int> src, SortCallback callback ) async {
|
||||
bool swapped = true; // 表示是否进行了交换
|
||||
int start = 0; // 当前未排序部分的起始位置
|
||||
int end = src.length; // 当前未排序部分的结束位置
|
||||
|
||||
// 开始排序循环,只有当没有进行交换时才会退出循环
|
||||
while (swapped == true) {
|
||||
swapped = false;
|
||||
|
||||
// 从左往右遍历需要排序的部分
|
||||
for (int i = start; i < end - 1; ++i) {
|
||||
// 对每两个相邻元素进行比较
|
||||
if (src[i] > src[i + 1]) {
|
||||
// 如果前面的元素大于后面的元素,则交换它们的位置
|
||||
int temp = src[i];
|
||||
src[i] = src[i + 1];
|
||||
src[i + 1] = temp;
|
||||
swapped = true; // 进行了交换
|
||||
}
|
||||
|
||||
// 实现动画效果,延迟一段时间后更新数组状态
|
||||
await callback(src);
|
||||
}
|
||||
|
||||
// 如果没有进行交换,则说明已经排好序,退出循环
|
||||
if (swapped == false) break;
|
||||
// 重设为false,准备进行下一轮排序
|
||||
swapped = false;
|
||||
// 将end设置为上一轮排序的最后一个元素的位置
|
||||
end = end - 1;
|
||||
|
||||
// 从右往左遍历需要排序的部分
|
||||
for (int i = end - 1; i >= start; i--) {
|
||||
// 对每两个相邻元素进行比较
|
||||
if (src[i] > src[i + 1]) {
|
||||
// 如果前面的元素大于后面的元素,则交换它们的位置
|
||||
int temp = src[i];
|
||||
src[i] = src[i + 1];
|
||||
src[i + 1] = temp;
|
||||
swapped = true; // 进行了交换
|
||||
}
|
||||
|
||||
// 实现动画效果,延迟一段时间后更新数组状态
|
||||
await callback(src);
|
||||
}
|
||||
// 将start向右移一位,准备下一轮排序
|
||||
start = start + 1;
|
||||
}
|
||||
}
|
||||
34
lib/v7/pages/sort/functions/comb.dart
Normal file
34
lib/v7/pages/sort/functions/comb.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
import '../functions.dart';
|
||||
|
||||
///梳排序(Comb Sort)
|
||||
Future<void> combSort(List<int> src, SortCallback callback) async{
|
||||
int gap = src.length;
|
||||
|
||||
bool swapped = true;
|
||||
|
||||
// 当间隔不为1或存在交换时执行循环
|
||||
while (gap != 1 || swapped == true) {
|
||||
// 通过缩小间隔来逐步将元素归位
|
||||
gap = getNextGap(gap);
|
||||
swapped = false;
|
||||
for (int i = 0; i < src.length - gap; i++) {
|
||||
// 如果当前元素大于间隔位置上的元素,则交换它们的位置
|
||||
if (src[i] > src[i + gap]) {
|
||||
int temp = src[i];
|
||||
src[i] = src[i + gap];
|
||||
src[i + gap] = temp;
|
||||
swapped = true;
|
||||
}
|
||||
|
||||
// 实现一个延迟,以便在 UI 上展示排序的动画效果。
|
||||
await callback(src);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getNextGap(int gap) {
|
||||
// 根据当前间隔值计算下一个间隔值
|
||||
gap = (gap * 10) ~/ 13;
|
||||
if (gap < 1) return 1;
|
||||
return gap;
|
||||
}
|
||||
54
lib/v7/pages/sort/functions/cycle.dart
Normal file
54
lib/v7/pages/sort/functions/cycle.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
import '../functions.dart';
|
||||
|
||||
///循环排序
|
||||
Future<void> cycleSort(List<int> src, SortCallback callback) async {
|
||||
int writes = 0;
|
||||
for (int cycleStart = 0; cycleStart <= src.length - 2; cycleStart++) {
|
||||
int item = src[cycleStart];
|
||||
int pos = cycleStart;
|
||||
|
||||
// 在未排序部分中寻找比当前元素小的元素个数
|
||||
for (int i = cycleStart + 1; i < src.length; i++) {
|
||||
if (src[i] < item) pos++;
|
||||
}
|
||||
|
||||
// 如果当前元素已经在正确位置上,则跳过此次迭代
|
||||
if (pos == cycleStart) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 将当前元素放置到正确的位置上,并记录写操作次数
|
||||
while (item == src[pos]) {
|
||||
pos += 1;
|
||||
}
|
||||
if (pos != cycleStart) {
|
||||
int temp = item;
|
||||
item = src[pos];
|
||||
src[pos] = temp;
|
||||
writes++;
|
||||
}
|
||||
|
||||
// 循环将位于当前位置的元素放置到正确的位置上
|
||||
while (pos != cycleStart) {
|
||||
pos = cycleStart;
|
||||
// 继续在未排序部分中寻找比当前元素小的元素个数
|
||||
for (int i = cycleStart + 1; i < src.length; i++) {
|
||||
if (src[i] < item) pos += 1;
|
||||
}
|
||||
|
||||
// 将当前元素放置到正确的位置上,并记录写操作次数
|
||||
while (item == src[pos]) {
|
||||
pos += 1;
|
||||
}
|
||||
if (item != src[pos]) {
|
||||
int temp = item;
|
||||
item = src[pos];
|
||||
src[pos] = temp;
|
||||
writes++;
|
||||
}
|
||||
|
||||
// 添加延迟操作以展示排序过程
|
||||
await callback(src);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
lib/v7/pages/sort/functions/gnome.dart
Normal file
22
lib/v7/pages/sort/functions/gnome.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
import '../functions.dart';
|
||||
|
||||
///地精排序 (侏儒排序)
|
||||
Future<void> gnomeSort(List<int> src, SortCallback callback) async {
|
||||
int index = 0;
|
||||
while (index < src.length) {
|
||||
// 当 index 小于数组长度时执行循环
|
||||
if (index == 0) index++;
|
||||
if (src[index] >= src[index - 1]) {
|
||||
// 如果当前元素大于等于前面的元素,则将 index 加1
|
||||
index++;
|
||||
} else {
|
||||
// 否则,交换这两个元素,并将 index 减1(使得元素可以沉到正确位置)
|
||||
int temp = src[index];
|
||||
src[index] = src[index - 1];
|
||||
src[index - 1] = temp;
|
||||
index--;
|
||||
}
|
||||
await callback(src);
|
||||
}
|
||||
return;
|
||||
}
|
||||
38
lib/v7/pages/sort/functions/heap.dart
Normal file
38
lib/v7/pages/sort/functions/heap.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
import '../functions.dart';
|
||||
|
||||
///堆排序
|
||||
Future<void> heapSort(List<int> src, SortCallback callback) async {
|
||||
// 从最后一个非叶子节点开始,构建最大堆
|
||||
for (int i = src.length ~/ 2; i >= 0; i--) {
|
||||
await heapify(src,callback, src.length, i);
|
||||
}
|
||||
|
||||
// 依次取出最大堆的根节点(最大值),并进行堆化
|
||||
for (int i = src.length - 1; i >= 0; i--) {
|
||||
int temp = src[0];
|
||||
src[0] = src[i];
|
||||
src[i] = temp;
|
||||
await heapify(src, callback,i, 0);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> heapify(List<int> src, SortCallback callback, int n, int i) async{
|
||||
int largest = i;
|
||||
int l = 2 * i + 1; // 左子节点索引
|
||||
int r = 2 * i + 2; // 右子节点索引
|
||||
|
||||
// 如果左子节点存在并且大于父节点,则更新最大值索引
|
||||
if (l < n && src[l] > src[largest]) largest = l;
|
||||
|
||||
// 如果右子节点存在并且大于父节点或左子节点,则更新最大值索引
|
||||
if (r < n && src[r] > src[largest]) largest = r;
|
||||
|
||||
// 如果最大值索引不等于当前节点索引,则交换节点值,并递归进行堆化
|
||||
if (largest != i) {
|
||||
int temp = src[i];
|
||||
src[i] = src[largest];
|
||||
src[largest] = temp;
|
||||
heapify(src,callback, n, largest);
|
||||
}
|
||||
await callback(src);
|
||||
}
|
||||
19
lib/v7/pages/sort/functions/insertion.dart
Normal file
19
lib/v7/pages/sort/functions/insertion.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
import '../functions.dart';
|
||||
|
||||
///插入排序
|
||||
Future<void> insertionSort(List<int> src, SortCallback callback) async {
|
||||
for (int i = 1; i < src.length; i++) {
|
||||
int temp = src[i]; // 将当前元素存储到临时变量 temp 中
|
||||
int j = i - 1; // j 表示已排序部分的最后一个元素的索引
|
||||
|
||||
// 在已排序部分从后往前查找,找到合适位置插入当前元素
|
||||
while (j >= 0 && temp < src[j]) {
|
||||
src[j + 1] = src[j]; // 当前元素比已排序部分的元素小,将元素后移一位
|
||||
--j; // 向前遍历
|
||||
// 更新排序结果回调
|
||||
await callback(src);
|
||||
}
|
||||
src[j + 1] = temp; // 插入当前元素到已排序部分的正确位置
|
||||
await callback(src);
|
||||
}
|
||||
}
|
||||
79
lib/v7/pages/sort/functions/merage.dart
Normal file
79
lib/v7/pages/sort/functions/merage.dart
Normal file
@@ -0,0 +1,79 @@
|
||||
import '../functions.dart';
|
||||
|
||||
//快速排序
|
||||
Future<void> mergeSort(List<int> src, SortCallback callback) async {
|
||||
await _mergeSort(src,callback,0,src.length-1);
|
||||
}
|
||||
|
||||
///归并排序
|
||||
Future<void> _mergeSort(List<int> src, SortCallback callback,int leftIndex, int rightIndex) async {
|
||||
// 定义一个名为 merge 的异步函数,用于合并两个有序子数组
|
||||
Future<void> merge(int leftIndex, int middleIndex, int rightIndex) async {
|
||||
// 计算左侧子数组和右侧子数组的大小
|
||||
int leftSize = middleIndex - leftIndex + 1;
|
||||
int rightSize = rightIndex - middleIndex;
|
||||
|
||||
// 创建左侧子数组和右侧子数组
|
||||
List leftList = List.generate(leftSize, (index) => 0);
|
||||
List rightList = List.generate(rightSize, (index) => 0);
|
||||
|
||||
// 将原始数组中的元素分别复制到左侧子数组和右侧子数组中
|
||||
for (int i = 0; i < leftSize; i++) {
|
||||
leftList[i] = src[leftIndex + i];
|
||||
}
|
||||
for (int j = 0; j < rightSize; j++) {
|
||||
rightList[j] = src[middleIndex + j + 1];
|
||||
}
|
||||
|
||||
// 初始化游标和索引
|
||||
int i = 0, j = 0;
|
||||
int k = leftIndex;
|
||||
|
||||
// 比较左侧子数组和右侧子数组的元素,并按顺序将较小的元素放入原始数组中
|
||||
while (i < leftSize && j < rightSize) {
|
||||
if (leftList[i] <= rightList[j]) {
|
||||
src[k] = leftList[i];
|
||||
i++;
|
||||
} else {
|
||||
src[k] = rightList[j];
|
||||
j++;
|
||||
}
|
||||
|
||||
await callback(src);
|
||||
|
||||
k++;
|
||||
}
|
||||
|
||||
// 将左侧子数组或右侧子数组中剩余的元素放入原始数组中
|
||||
while (i < leftSize) {
|
||||
src[k] = leftList[i];
|
||||
i++;
|
||||
k++;
|
||||
|
||||
await callback(src);
|
||||
}
|
||||
|
||||
while (j < rightSize) {
|
||||
src[k] = rightList[j];
|
||||
j++;
|
||||
k++;
|
||||
|
||||
await callback(src);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果左索引小于右索引,则递归地对数组进行归并排序
|
||||
if (leftIndex < rightIndex) {
|
||||
// 计算中间索引位置
|
||||
int middleIndex = (rightIndex + leftIndex) ~/ 2;
|
||||
|
||||
// 分别对左侧子数组和右侧子数组进行归并排序
|
||||
await _mergeSort(src,callback,leftIndex, middleIndex);
|
||||
await _mergeSort(src,callback,middleIndex + 1, rightIndex);
|
||||
|
||||
await callback(src);
|
||||
|
||||
// 合并两个有序子数组
|
||||
await merge(leftIndex, middleIndex, rightIndex);
|
||||
}
|
||||
}
|
||||
37
lib/v7/pages/sort/functions/oddEven.dart
Normal file
37
lib/v7/pages/sort/functions/oddEven.dart
Normal file
@@ -0,0 +1,37 @@
|
||||
import '../functions.dart';
|
||||
|
||||
///奇偶排序(Odd-Even Sort)
|
||||
Future<void> oddEvenSort(List<int> src, SortCallback callback) async {
|
||||
bool isSorted = false;
|
||||
|
||||
while (!isSorted) {
|
||||
// 当 isSorted 为 false 时执行循环
|
||||
isSorted = true; // 先假设数组已经排好序
|
||||
|
||||
for (int i = 1; i <= src.length - 2; i = i + 2) {
|
||||
// 对奇数索引位置进行比较
|
||||
if (src[i] > src[i + 1]) {
|
||||
// 如果当前元素大于后面的元素,则交换它们的值
|
||||
int temp = src[i];
|
||||
src[i] = src[i + 1];
|
||||
src[i + 1] = temp;
|
||||
isSorted = false; // 若发生了交换,则说明数组仍未完全排序,将 isSorted 设为 false
|
||||
await callback(src);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i <= src.length - 2; i = i + 2) {
|
||||
// 对偶数索引位置进行比较
|
||||
if (src[i] > src[i + 1]) {
|
||||
// 如果当前元素大于后面的元素,则交换它们的值
|
||||
int temp = src[i];
|
||||
src[i] = src[i + 1];
|
||||
src[i + 1] = temp;
|
||||
isSorted = false;
|
||||
await callback(src);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
33
lib/v7/pages/sort/functions/pigeonHole.dart
Normal file
33
lib/v7/pages/sort/functions/pigeonHole.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
import '../functions.dart';
|
||||
|
||||
///鸽巢排序
|
||||
Future<void> pigeonHoleSort(List<int> src, SortCallback callback ) async{
|
||||
int min = src[0];
|
||||
int max = src[0];
|
||||
int range, i, j, index;
|
||||
|
||||
// 找到数组中的最大值和最小值
|
||||
for (int a = 0; a < src.length; a++) {
|
||||
if (src[a] > max) max = src[a];
|
||||
if (src[a] < min) min = src[a];
|
||||
}
|
||||
|
||||
// 计算鸽巢的个数
|
||||
range = max - min + 1;
|
||||
List<int> p = List.generate(range, (i) => 0);
|
||||
|
||||
// 将数字分配到各个鸽巢中
|
||||
for (i = 0; i < src.length; i++) {
|
||||
p[src[i] - min]++;
|
||||
}
|
||||
|
||||
index = 0;
|
||||
|
||||
// 将鸽巢中的数字取出,重新放回到数组中
|
||||
for (j = 0; j < range; j++) {
|
||||
while (p[j]-- > 0) {
|
||||
src[index++] = j + min;
|
||||
await callback(src);
|
||||
}
|
||||
}
|
||||
}
|
||||
65
lib/v7/pages/sort/functions/quick.dart
Normal file
65
lib/v7/pages/sort/functions/quick.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
import '../functions.dart';
|
||||
|
||||
//快速排序
|
||||
Future<void> quickSort(List<int> src, SortCallback callback) async {
|
||||
await _quickSort(src,callback,0,src.length-1);
|
||||
}
|
||||
|
||||
///快速排序
|
||||
Future<void> _quickSort(List<int> src, SortCallback callback,int leftIndex,int rightIndex) async {
|
||||
// 定义一个名为 _partition 的异步函数,用于划分数组,并返回划分后的基准元素的索引位置
|
||||
Future<int> _partition(int left, int right) async {
|
||||
// 选择中间位置的元素作为基准元素
|
||||
int p = (left + (right - left) / 2).toInt();
|
||||
|
||||
// 交换基准元素和最右边的元素
|
||||
var temp = src[p];
|
||||
src[p] = src[right];
|
||||
src[right] = temp;
|
||||
await callback(src);
|
||||
|
||||
// 初始化游标 cursor
|
||||
int cursor = left;
|
||||
|
||||
// 遍历数组并根据基准元素将元素交换到左侧或右侧
|
||||
for (int i = left; i < right; i++) {
|
||||
if (cf(src[i], src[right]) <= 0) {
|
||||
// 如果当前元素小于等于基准元素,则交换它和游标位置的元素
|
||||
var temp = src[i];
|
||||
src[i] = src[cursor];
|
||||
src[cursor] = temp;
|
||||
cursor++;
|
||||
await callback(src);
|
||||
}
|
||||
}
|
||||
|
||||
// 将基准元素放置在游标位置
|
||||
temp = src[right];
|
||||
src[right] = src[cursor];
|
||||
src[cursor] = temp;
|
||||
|
||||
await callback(src);
|
||||
|
||||
return cursor; // 返回基准元素的索引位置
|
||||
}
|
||||
|
||||
// 如果左索引小于右索引,则递归地对数组进行快速排序
|
||||
if (leftIndex < rightIndex) {
|
||||
int p = await _partition(leftIndex, rightIndex);
|
||||
|
||||
await _quickSort(src,callback,leftIndex, p - 1); // 对基准元素左侧的子数组进行快速排序
|
||||
|
||||
await _quickSort(src,callback, p + 1, rightIndex); // 对基准元素右侧的子数组进行快速排序
|
||||
}
|
||||
}
|
||||
|
||||
// 比较函数,用于判断两个元素的大小关系
|
||||
cf(int a, int b) {
|
||||
if (a < b) {
|
||||
return -1; // 若 a 小于 b,则返回 -1
|
||||
} else if (a > b) {
|
||||
return 1; // 若 a 大于 b,则返回 1
|
||||
} else {
|
||||
return 0; // 若 a 等于 b,则返回 0
|
||||
}
|
||||
}
|
||||
18
lib/v7/pages/sort/functions/selection.dart
Normal file
18
lib/v7/pages/sort/functions/selection.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import '../functions.dart';
|
||||
|
||||
///选择排序
|
||||
Future<void> selectionSort(List<int> src, SortCallback callback ) async {
|
||||
for (int i = 0; i < src.length; i++) {
|
||||
for (int j = i + 1; j < src.length; j++) {
|
||||
// 遍历未排序部分,内层循环控制变量 j
|
||||
if (src[i] > src[j]) {
|
||||
// 判断当前元素是否比后续元素小
|
||||
int temp = src[j];
|
||||
// 交换当前元素和后续较小的元素
|
||||
src[j] = src[i];
|
||||
src[i] = temp;
|
||||
}
|
||||
await callback(src);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
lib/v7/pages/sort/functions/shell.dart
Normal file
21
lib/v7/pages/sort/functions/shell.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
import '../functions.dart';
|
||||
|
||||
///希尔排序
|
||||
Future<void> shellSort(List<int> src, SortCallback callback) async{
|
||||
//定义变量 gap 并初始化为数组长度的一半。每次循环完成后将 gap 减半直到等于 0。
|
||||
for (int gap = src.length ~/ 2; gap > 0; gap ~/= 2) {
|
||||
//遍历每个子序列并进行插入排序。初始时从第一个子序列的第二个元素开始,即 i = gap,以 gap 为步长逐个遍历每个子序列。
|
||||
for (int i = gap; i < src.length; i += 1) {
|
||||
//将当前遍历到的元素赋值给它
|
||||
int temp = src[i];
|
||||
//内部使用一个 for 循环来实现插入排序。
|
||||
//循环开始时定义变量 j 并将其初始化为当前遍历到的元素的下标。通过不断比较前后相隔 gap 的元素大小并交换位置,将当前元素插入到正确的位置。
|
||||
int j;
|
||||
for (j = i; j >= gap && src[j - gap] > temp; j -= gap) {
|
||||
src[j] = src[j - gap];
|
||||
}
|
||||
src[j] = temp;
|
||||
await callback(src);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
lib/v7/pages/sort/provider/sort_config.dart
Normal file
31
lib/v7/pages/sort/provider/sort_config.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.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,
|
||||
);
|
||||
}
|
||||
|
||||
82
lib/v7/pages/sort/provider/state.dart
Normal file
82
lib/v7/pages/sort/provider/state.dart
Normal file
@@ -0,0 +1,82 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
import '../functions.dart';
|
||||
import 'sort_config.dart';
|
||||
|
||||
enum SortStatus{
|
||||
none, // 未操作
|
||||
sorting, // 排序中
|
||||
sorted, // 排序完成
|
||||
}
|
||||
|
||||
class SortState with ChangeNotifier{
|
||||
|
||||
SortState(){
|
||||
reset();
|
||||
}
|
||||
|
||||
SortStatus status = SortStatus.none;
|
||||
|
||||
List<int> data = [];
|
||||
List<int> stepData = [];
|
||||
|
||||
SortConfig _config = SortConfig();
|
||||
SortConfig get config => _config;
|
||||
Random random = Random();
|
||||
|
||||
set config(SortConfig config){
|
||||
_config = config;
|
||||
reset();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void selectName(String name){
|
||||
if(name==config.name) return;
|
||||
config = config.copyWith(name: name);
|
||||
}
|
||||
|
||||
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();
|
||||
Stopwatch stopwatch = Stopwatch()..start();
|
||||
SortFunction? sortFunction = sortFunctionMap[config.name];
|
||||
if(sortFunction!=null){
|
||||
await sortFunction(data,(arr) async {
|
||||
await Future.delayed(config.duration);
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
status = SortStatus.sorted;
|
||||
notifyListeners();
|
||||
stopwatch.stop();
|
||||
print("Sorting completed in ${stopwatch.elapsed.inMilliseconds} ms.");
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides the current [SortState] to descendant widgets in the tree.
|
||||
class SortStateScope extends InheritedNotifier<SortState> {
|
||||
const SortStateScope({
|
||||
required super.notifier,
|
||||
required super.child,
|
||||
super.key,
|
||||
});
|
||||
|
||||
static SortState of(BuildContext context) =>
|
||||
context.dependOnInheritedWidgetOfExactType<SortStateScope>()!.notifier!;
|
||||
}
|
||||
@@ -1,859 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SortPage extends StatefulWidget {
|
||||
const SortPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<SortPage> createState() => _SortPageState();
|
||||
}
|
||||
|
||||
class _SortPageState extends State<SortPage> {
|
||||
//存放随机数组
|
||||
List<int> numbers = [];
|
||||
|
||||
//订阅流
|
||||
StreamController<List<int>> streamController = StreamController();
|
||||
String currentSort = 'bubble';
|
||||
|
||||
//柱子的数量 -> 生成排序数组的长度
|
||||
double sampleSize = 0;
|
||||
|
||||
//是否排序
|
||||
bool isSorted = false;
|
||||
|
||||
//是否在排序中
|
||||
bool isSorting = false;
|
||||
|
||||
//排序动画更新的速度
|
||||
int speed = 0;
|
||||
|
||||
static int duration = 1500;
|
||||
|
||||
String getTitle() {
|
||||
switch (currentSort) {
|
||||
case "bubble":
|
||||
return "Bubble Sort";
|
||||
case "coctail":
|
||||
return "Coctail Sort";
|
||||
case "comb":
|
||||
return "Comb Sort";
|
||||
case "pigeonhole":
|
||||
return "Pigeonhole Sort";
|
||||
case "shell":
|
||||
return "Shell Sort";
|
||||
case "selection":
|
||||
return "Selection Sort";
|
||||
case "cycle":
|
||||
return "Cycle Sort";
|
||||
case "heap":
|
||||
return "Heap Sort";
|
||||
case "insertion":
|
||||
return "Insertion Sort";
|
||||
case "gnome":
|
||||
return "Gnome Sort";
|
||||
case "oddeven":
|
||||
return "OddEven Sort";
|
||||
case "quick":
|
||||
return "Quick Sort";
|
||||
case "merge":
|
||||
return "Merge Sort";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
reset() {
|
||||
isSorted = false;
|
||||
numbers = [];
|
||||
for (int i = 0; i < sampleSize; ++i) {
|
||||
numbers.add(Random().nextInt(500));
|
||||
}
|
||||
streamController.add(numbers);
|
||||
}
|
||||
|
||||
Duration getDuration() {
|
||||
return Duration(microseconds: duration);
|
||||
}
|
||||
|
||||
///动画时间
|
||||
changeSpeed() {
|
||||
if (speed >= 3) {
|
||||
speed = 0;
|
||||
duration = 1500;
|
||||
} else {
|
||||
speed++;
|
||||
duration = duration ~/ 2;
|
||||
}
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
///冒泡排序
|
||||
bubbleSort() async {
|
||||
//控制需要进行排序的次数。每一轮循环都会确定一个数字的最终位置。
|
||||
for (int i = 0; i < numbers.length; ++i) {
|
||||
//遍历当前未排序的元素,通过相邻的元素比较并交换位置来完成排序。
|
||||
for (int j = 0; j < numbers.length - i - 1; ++j) {
|
||||
//如果 _numbers[j] 大于 _numbers[j + 1],则交换它们的位置,确保较大的元素移到右边。
|
||||
if (numbers[j] > numbers[j + 1]) {
|
||||
int temp = numbers[j];
|
||||
numbers[j] = numbers[j + 1];
|
||||
numbers[j + 1] = temp;
|
||||
}
|
||||
//实现一个延迟,以便在ui上展示排序的动画效果
|
||||
await Future.delayed(getDuration(), () {});
|
||||
streamController.add(numbers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///鸡尾酒排序(双向冒泡排序)
|
||||
cocktailSort() async {
|
||||
bool swapped = true; // 表示是否进行了交换
|
||||
int start = 0; // 当前未排序部分的起始位置
|
||||
int end = numbers.length; // 当前未排序部分的结束位置
|
||||
|
||||
// 开始排序循环,只有当没有进行交换时才会退出循环
|
||||
while (swapped == true) {
|
||||
swapped = false;
|
||||
|
||||
// 从左往右遍历需要排序的部分
|
||||
for (int i = start; i < end - 1; ++i) {
|
||||
// 对每两个相邻元素进行比较
|
||||
if (numbers[i] > numbers[i + 1]) {
|
||||
// 如果前面的元素大于后面的元素,则交换它们的位置
|
||||
int temp = numbers[i];
|
||||
numbers[i] = numbers[i + 1];
|
||||
numbers[i + 1] = temp;
|
||||
swapped = true; // 进行了交换
|
||||
}
|
||||
|
||||
// 实现动画效果,延迟一段时间后更新数组状态
|
||||
await Future.delayed(getDuration());
|
||||
streamController.add(numbers);
|
||||
}
|
||||
|
||||
// 如果没有进行交换,则说明已经排好序,退出循环
|
||||
if (swapped == false) break;
|
||||
// 重设为false,准备进行下一轮排序
|
||||
swapped = false;
|
||||
// 将end设置为上一轮排序的最后一个元素的位置
|
||||
end = end - 1;
|
||||
|
||||
// 从右往左遍历需要排序的部分
|
||||
for (int i = end - 1; i >= start; i--) {
|
||||
// 对每两个相邻元素进行比较
|
||||
if (numbers[i] > numbers[i + 1]) {
|
||||
// 如果前面的元素大于后面的元素,则交换它们的位置
|
||||
int temp = numbers[i];
|
||||
numbers[i] = numbers[i + 1];
|
||||
numbers[i + 1] = temp;
|
||||
swapped = true; // 进行了交换
|
||||
}
|
||||
|
||||
// 实现动画效果,延迟一段时间后更新数组状态
|
||||
await Future.delayed(getDuration());
|
||||
streamController.add(numbers);
|
||||
}
|
||||
// 将start向右移一位,准备下一轮排序
|
||||
start = start + 1;
|
||||
}
|
||||
}
|
||||
|
||||
///梳排序(Comb Sort)
|
||||
combSort() async {
|
||||
int gap = numbers.length;
|
||||
|
||||
bool swapped = true;
|
||||
|
||||
// 当间隔不为1或存在交换时执行循环
|
||||
while (gap != 1 || swapped == true) {
|
||||
// 通过缩小间隔来逐步将元素归位
|
||||
gap = getNextGap(gap);
|
||||
swapped = false;
|
||||
for (int i = 0; i < numbers.length - gap; i++) {
|
||||
// 如果当前元素大于间隔位置上的元素,则交换它们的位置
|
||||
if (numbers[i] > numbers[i + gap]) {
|
||||
int temp = numbers[i];
|
||||
numbers[i] = numbers[i + gap];
|
||||
numbers[i + gap] = temp;
|
||||
swapped = true;
|
||||
}
|
||||
|
||||
// 实现一个延迟,以便在 UI 上展示排序的动画效果。
|
||||
await Future.delayed(getDuration());
|
||||
streamController.add(numbers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getNextGap(int gap) {
|
||||
// 根据当前间隔值计算下一个间隔值
|
||||
gap = (gap * 10) ~/ 13;
|
||||
if (gap < 1) return 1;
|
||||
return gap;
|
||||
}
|
||||
|
||||
///鸽巢排序
|
||||
pigeonHole() async {
|
||||
int min = numbers[0];
|
||||
int max = numbers[0];
|
||||
int range, i, j, index;
|
||||
|
||||
// 找到数组中的最大值和最小值
|
||||
for (int a = 0; a < numbers.length; a++) {
|
||||
if (numbers[a] > max) max = numbers[a];
|
||||
if (numbers[a] < min) min = numbers[a];
|
||||
}
|
||||
|
||||
// 计算鸽巢的个数
|
||||
range = max - min + 1;
|
||||
List<int> p = List.generate(range, (i) => 0);
|
||||
|
||||
// 将数字分配到各个鸽巢中
|
||||
for (i = 0; i < numbers.length; i++) {
|
||||
p[numbers[i] - min]++;
|
||||
}
|
||||
|
||||
index = 0;
|
||||
|
||||
// 将鸽巢中的数字取出,重新放回到数组中
|
||||
for (j = 0; j < range; j++) {
|
||||
while (p[j]-- > 0) {
|
||||
numbers[index++] = j + min;
|
||||
await Future.delayed(getDuration());
|
||||
streamController.add(numbers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///希尔排序
|
||||
shellSort() async {
|
||||
//定义变量 gap 并初始化为数组长度的一半。每次循环完成后将 gap 减半直到等于 0。
|
||||
for (int gap = numbers.length ~/ 2; gap > 0; gap ~/= 2) {
|
||||
//遍历每个子序列并进行插入排序。初始时从第一个子序列的第二个元素开始,即 i = gap,以 gap 为步长逐个遍历每个子序列。
|
||||
for (int i = gap; i < numbers.length; i += 1) {
|
||||
//将当前遍历到的元素赋值给它
|
||||
int temp = numbers[i];
|
||||
//内部使用一个 for 循环来实现插入排序。
|
||||
//循环开始时定义变量 j 并将其初始化为当前遍历到的元素的下标。通过不断比较前后相隔 gap 的元素大小并交换位置,将当前元素插入到正确的位置。
|
||||
int j;
|
||||
for (j = i; j >= gap && numbers[j - gap] > temp; j -= gap) {
|
||||
numbers[j] = numbers[j - gap];
|
||||
}
|
||||
numbers[j] = temp;
|
||||
await Future.delayed(getDuration());
|
||||
streamController.add(numbers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///选择排序
|
||||
selectionSort() async {
|
||||
for (int i = 0; i < numbers.length; i++) {
|
||||
for (int j = i + 1; j < numbers.length; j++) {
|
||||
// 遍历未排序部分,内层循环控制变量 j
|
||||
if (numbers[i] > numbers[j]) {
|
||||
// 判断当前元素是否比后续元素小
|
||||
int temp = numbers[j];
|
||||
// 交换当前元素和后续较小的元素
|
||||
numbers[j] = numbers[i];
|
||||
numbers[i] = temp;
|
||||
}
|
||||
|
||||
await Future.delayed(getDuration(), () {});
|
||||
|
||||
streamController.add(numbers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///循环排序
|
||||
cycleSort() async {
|
||||
int writes = 0;
|
||||
for (int cycleStart = 0; cycleStart <= numbers.length - 2; cycleStart++) {
|
||||
int item = numbers[cycleStart];
|
||||
int pos = cycleStart;
|
||||
|
||||
// 在未排序部分中寻找比当前元素小的元素个数
|
||||
for (int i = cycleStart + 1; i < numbers.length; i++) {
|
||||
if (numbers[i] < item) pos++;
|
||||
}
|
||||
|
||||
// 如果当前元素已经在正确位置上,则跳过此次迭代
|
||||
if (pos == cycleStart) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 将当前元素放置到正确的位置上,并记录写操作次数
|
||||
while (item == numbers[pos]) {
|
||||
pos += 1;
|
||||
}
|
||||
if (pos != cycleStart) {
|
||||
int temp = item;
|
||||
item = numbers[pos];
|
||||
numbers[pos] = temp;
|
||||
writes++;
|
||||
}
|
||||
|
||||
// 循环将位于当前位置的元素放置到正确的位置上
|
||||
while (pos != cycleStart) {
|
||||
pos = cycleStart;
|
||||
// 继续在未排序部分中寻找比当前元素小的元素个数
|
||||
for (int i = cycleStart + 1; i < numbers.length; i++) {
|
||||
if (numbers[i] < item) pos += 1;
|
||||
}
|
||||
|
||||
// 将当前元素放置到正确的位置上,并记录写操作次数
|
||||
while (item == numbers[pos]) {
|
||||
pos += 1;
|
||||
}
|
||||
if (item != numbers[pos]) {
|
||||
int temp = item;
|
||||
item = numbers[pos];
|
||||
numbers[pos] = temp;
|
||||
writes++;
|
||||
}
|
||||
|
||||
// 添加延迟操作以展示排序过程
|
||||
await Future.delayed(getDuration());
|
||||
streamController.add(numbers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///堆排序
|
||||
heapSort() async {
|
||||
// 从最后一个非叶子节点开始,构建最大堆
|
||||
for (int i = numbers.length ~/ 2; i >= 0; i--) {
|
||||
await heapify(numbers, numbers.length, i);
|
||||
}
|
||||
|
||||
// 依次取出最大堆的根节点(最大值),并进行堆化
|
||||
for (int i = numbers.length - 1; i >= 0; i--) {
|
||||
int temp = numbers[0];
|
||||
numbers[0] = numbers[i];
|
||||
numbers[i] = temp;
|
||||
await heapify(numbers, i, 0);
|
||||
streamController.add(numbers);
|
||||
}
|
||||
}
|
||||
|
||||
heapify(List<int> arr, int n, int i) async {
|
||||
int largest = i;
|
||||
int l = 2 * i + 1; // 左子节点索引
|
||||
int r = 2 * i + 2; // 右子节点索引
|
||||
|
||||
// 如果左子节点存在并且大于父节点,则更新最大值索引
|
||||
if (l < n && arr[l] > arr[largest]) largest = l;
|
||||
|
||||
// 如果右子节点存在并且大于父节点或左子节点,则更新最大值索引
|
||||
if (r < n && arr[r] > arr[largest]) largest = r;
|
||||
|
||||
// 如果最大值索引不等于当前节点索引,则交换节点值,并递归进行堆化
|
||||
if (largest != i) {
|
||||
int temp = numbers[i];
|
||||
numbers[i] = numbers[largest];
|
||||
numbers[largest] = temp;
|
||||
heapify(arr, n, largest);
|
||||
}
|
||||
|
||||
await Future.delayed(getDuration()); // 延迟操作,用于可视化排序过程
|
||||
streamController.add(numbers);
|
||||
}
|
||||
|
||||
///插入排序
|
||||
insertionSort() async {
|
||||
for (int i = 1; i < numbers.length; i++) {
|
||||
int temp = numbers[i]; // 将当前元素存储到临时变量 temp 中
|
||||
int j = i - 1; // j 表示已排序部分的最后一个元素的索引
|
||||
|
||||
// 在已排序部分从后往前查找,找到合适位置插入当前元素
|
||||
while (j >= 0 && temp < numbers[j]) {
|
||||
numbers[j + 1] = numbers[j]; // 当前元素比已排序部分的元素小,将元素后移一位
|
||||
--j; // 向前遍历
|
||||
await Future.delayed(getDuration());
|
||||
streamController.add(numbers); // 更新排序结果
|
||||
}
|
||||
|
||||
numbers[j + 1] = temp; // 插入当前元素到已排序部分的正确位置
|
||||
await Future.delayed(getDuration(), () {});
|
||||
streamController.add(numbers); // 更新排序结果
|
||||
}
|
||||
}
|
||||
|
||||
///地精排序 (侏儒排序)
|
||||
gnomeSort() async {
|
||||
int index = 0;
|
||||
|
||||
while (index < numbers.length) {
|
||||
// 当 index 小于数组长度时执行循环
|
||||
if (index == 0) index++;
|
||||
if (numbers[index] >= numbers[index - 1]) {
|
||||
// 如果当前元素大于等于前面的元素,则将 index 加1
|
||||
index++;
|
||||
} else {
|
||||
// 否则,交换这两个元素,并将 index 减1(使得元素可以沉到正确位置)
|
||||
int temp = numbers[index];
|
||||
numbers[index] = numbers[index - 1];
|
||||
numbers[index - 1] = temp;
|
||||
index--;
|
||||
}
|
||||
await Future.delayed(getDuration());
|
||||
streamController.add(numbers);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
///奇偶排序(Odd-Even Sort)
|
||||
oddEvenSort() async {
|
||||
bool isSorted = false;
|
||||
|
||||
while (!isSorted) {
|
||||
// 当 isSorted 为 false 时执行循环
|
||||
isSorted = true; // 先假设数组已经排好序
|
||||
|
||||
for (int i = 1; i <= numbers.length - 2; i = i + 2) {
|
||||
// 对奇数索引位置进行比较
|
||||
if (numbers[i] > numbers[i + 1]) {
|
||||
// 如果当前元素大于后面的元素,则交换它们的值
|
||||
int temp = numbers[i];
|
||||
numbers[i] = numbers[i + 1];
|
||||
numbers[i + 1] = temp;
|
||||
isSorted = false; // 若发生了交换,则说明数组仍未完全排序,将 isSorted 设为 false
|
||||
await Future.delayed(getDuration());
|
||||
streamController.add(numbers);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i <= numbers.length - 2; i = i + 2) {
|
||||
// 对偶数索引位置进行比较
|
||||
if (numbers[i] > numbers[i + 1]) {
|
||||
// 如果当前元素大于后面的元素,则交换它们的值
|
||||
int temp = numbers[i];
|
||||
numbers[i] = numbers[i + 1];
|
||||
numbers[i + 1] = temp;
|
||||
isSorted = false;
|
||||
await Future.delayed(getDuration());
|
||||
streamController.add(numbers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
///快速排序
|
||||
quickSort(int leftIndex, int rightIndex) async {
|
||||
// 定义一个名为 _partition 的异步函数,用于划分数组,并返回划分后的基准元素的索引位置
|
||||
Future<int> _partition(int left, int right) async{
|
||||
// 选择中间位置的元素作为基准元素
|
||||
int p = (left + (right - left) / 2).toInt();
|
||||
|
||||
// 交换基准元素和最右边的元素
|
||||
var temp = numbers[p];
|
||||
numbers[p] = numbers[right];
|
||||
numbers[right] = temp;
|
||||
await Future.delayed(getDuration());
|
||||
streamController.add(numbers);
|
||||
|
||||
// 初始化游标 cursor
|
||||
int cursor = left;
|
||||
|
||||
// 遍历数组并根据基准元素将元素交换到左侧或右侧
|
||||
for (int i = left; i < right; i++) {
|
||||
if (cf(numbers[i], numbers[right]) <= 0) {
|
||||
// 如果当前元素小于等于基准元素,则交换它和游标位置的元素
|
||||
var temp = numbers[i];
|
||||
numbers[i] = numbers[cursor];
|
||||
numbers[cursor] = temp;
|
||||
cursor++;
|
||||
|
||||
await Future.delayed(getDuration());
|
||||
streamController.add(numbers);
|
||||
}
|
||||
}
|
||||
|
||||
// 将基准元素放置在游标位置
|
||||
temp = numbers[right];
|
||||
numbers[right] = numbers[cursor];
|
||||
numbers[cursor] = temp;
|
||||
|
||||
await Future.delayed(getDuration());
|
||||
streamController.add(numbers);
|
||||
|
||||
return cursor; // 返回基准元素的索引位置
|
||||
}
|
||||
|
||||
// 如果左索引小于右索引,则递归地对数组进行快速排序
|
||||
if (leftIndex < rightIndex) {
|
||||
int p = await _partition(leftIndex, rightIndex);
|
||||
|
||||
await quickSort(leftIndex, p - 1); // 对基准元素左侧的子数组进行快速排序
|
||||
|
||||
await quickSort(p + 1, rightIndex); // 对基准元素右侧的子数组进行快速排序
|
||||
}
|
||||
}
|
||||
|
||||
// 比较函数,用于判断两个元素的大小关系
|
||||
cf(int a, int b) {
|
||||
if (a < b) {
|
||||
return -1; // 若 a 小于 b,则返回 -1
|
||||
} else if (a > b) {
|
||||
return 1; // 若 a 大于 b,则返回 1
|
||||
} else {
|
||||
return 0; // 若 a 等于 b,则返回 0
|
||||
}
|
||||
}
|
||||
|
||||
///归并排序
|
||||
mergeSort(int leftIndex, int rightIndex) async {
|
||||
// 定义一个名为 merge 的异步函数,用于合并两个有序子数组
|
||||
Future<void> merge(int leftIndex, int middleIndex, int rightIndex) async {
|
||||
// 计算左侧子数组和右侧子数组的大小
|
||||
int leftSize = middleIndex - leftIndex + 1;
|
||||
int rightSize = rightIndex - middleIndex;
|
||||
|
||||
// 创建左侧子数组和右侧子数组
|
||||
List leftList = List.generate(leftSize, (index) => 0);
|
||||
List rightList = List.generate(rightSize, (index) => 0);
|
||||
|
||||
// 将原始数组中的元素分别复制到左侧子数组和右侧子数组中
|
||||
for (int i = 0; i < leftSize; i++) {
|
||||
leftList[i] = numbers[leftIndex + i];
|
||||
}
|
||||
for (int j = 0; j < rightSize; j++) {
|
||||
rightList[j] = numbers[middleIndex + j + 1];
|
||||
}
|
||||
|
||||
// 初始化游标和索引
|
||||
int i = 0, j = 0;
|
||||
int k = leftIndex;
|
||||
|
||||
// 比较左侧子数组和右侧子数组的元素,并按顺序将较小的元素放入原始数组中
|
||||
while (i < leftSize && j < rightSize) {
|
||||
if (leftList[i] <= rightList[j]) {
|
||||
numbers[k] = leftList[i];
|
||||
i++;
|
||||
} else {
|
||||
numbers[k] = rightList[j];
|
||||
j++;
|
||||
}
|
||||
|
||||
await Future.delayed(getDuration());
|
||||
streamController.add(numbers);
|
||||
|
||||
k++;
|
||||
}
|
||||
|
||||
// 将左侧子数组或右侧子数组中剩余的元素放入原始数组中
|
||||
while (i < leftSize) {
|
||||
numbers[k] = leftList[i];
|
||||
i++;
|
||||
k++;
|
||||
|
||||
await Future.delayed(getDuration());
|
||||
streamController.add(numbers);
|
||||
}
|
||||
|
||||
while (j < rightSize) {
|
||||
numbers[k] = rightList[j];
|
||||
j++;
|
||||
k++;
|
||||
|
||||
await Future.delayed(getDuration());
|
||||
streamController.add(numbers);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果左索引小于右索引,则递归地对数组进行归并排序
|
||||
if (leftIndex < rightIndex) {
|
||||
// 计算中间索引位置
|
||||
int middleIndex = (rightIndex + leftIndex) ~/ 2;
|
||||
|
||||
// 分别对左侧子数组和右侧子数组进行归并排序
|
||||
await mergeSort(leftIndex, middleIndex);
|
||||
await mergeSort(middleIndex + 1, rightIndex);
|
||||
|
||||
await Future.delayed(getDuration());
|
||||
streamController.add(numbers);
|
||||
|
||||
// 合并两个有序子数组
|
||||
await merge(leftIndex, middleIndex, rightIndex);
|
||||
}
|
||||
}
|
||||
|
||||
checkAndResetIfSorted() async {
|
||||
if (isSorted) {
|
||||
reset();
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
}
|
||||
}
|
||||
|
||||
sort() async {
|
||||
setState(() {
|
||||
isSorting = true;
|
||||
});
|
||||
|
||||
await checkAndResetIfSorted();
|
||||
|
||||
Stopwatch stopwatch = Stopwatch()..start();
|
||||
|
||||
switch (currentSort) {
|
||||
case "bubble":
|
||||
await bubbleSort();
|
||||
break;
|
||||
case "coctail":
|
||||
await cocktailSort();
|
||||
break;
|
||||
case "comb":
|
||||
await combSort();
|
||||
break;
|
||||
case "pigeonhole":
|
||||
await pigeonHole();
|
||||
break;
|
||||
case "shell":
|
||||
await shellSort();
|
||||
break;
|
||||
case "selection":
|
||||
await selectionSort();
|
||||
break;
|
||||
case "cycle":
|
||||
await cycleSort();
|
||||
break;
|
||||
case "heap":
|
||||
await heapSort();
|
||||
break;
|
||||
case "insertion":
|
||||
await insertionSort();
|
||||
break;
|
||||
case "gnome":
|
||||
await gnomeSort();
|
||||
break;
|
||||
case "oddeven":
|
||||
await oddEvenSort();
|
||||
break;
|
||||
case "quick":
|
||||
await quickSort(0, sampleSize.toInt() - 1);
|
||||
break;
|
||||
case "merge":
|
||||
await mergeSort(0, sampleSize.toInt() - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
stopwatch.stop();
|
||||
|
||||
print("Sorting completed in ${stopwatch.elapsed.inMilliseconds} ms.");
|
||||
setState(() {
|
||||
isSorting = false;
|
||||
isSorted = true;
|
||||
});
|
||||
}
|
||||
|
||||
setSort(String type) {
|
||||
setState(() {
|
||||
currentSort = type;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// reset();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
sampleSize = MediaQuery.of(context).size.width / 2;
|
||||
for (int i = 0; i < sampleSize; ++i) {
|
||||
//随机往数组中填值
|
||||
numbers.add(Random().nextInt(500));
|
||||
}
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
streamController.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
"当前选择的是:${getTitle()}",
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
actions: <Widget>[
|
||||
PopupMenuButton(
|
||||
initialValue: currentSort,
|
||||
itemBuilder: (ctx) {
|
||||
return const [
|
||||
PopupMenuItem(
|
||||
value: 'bubble',
|
||||
child: Text("Bubble Sort — 冒泡排序"),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'coctail',
|
||||
child: Text("Coctail Sort — 鸡尾酒排序(双向冒泡排序)"),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'comb',
|
||||
child: Text("Comb Sort — 梳排序"),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'pigeonhole',
|
||||
child: Text("pigeonhole Sort — 鸽巢排序"),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'shell',
|
||||
child: Text("shell Sort — 希尔排序"),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'selection',
|
||||
child: Text("Selection Sort — 选择排序"),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'cycle',
|
||||
child: Text("CycleSort — 循环排序"),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'heap',
|
||||
child: Text("HeapSort — 堆排序"),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'insertion',
|
||||
child: Text("InsertionSort — 插入排序"),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'gnome',
|
||||
child: Text("GnomeSort — 地精排序 (侏儒排序)"),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'oddeven',
|
||||
child: Text("OddEvenSort — 奇偶排序"),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'quick',
|
||||
child: Text("QuickSort — 快速排序"),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'merge',
|
||||
child: Text("MergeSort — 归并排序"),
|
||||
),
|
||||
];
|
||||
},
|
||||
onSelected: (String value) {
|
||||
reset();
|
||||
setSort(value);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
body: StreamBuilder<Object>(
|
||||
initialData: numbers,
|
||||
stream: streamController.stream,
|
||||
builder: (context, snapshot) {
|
||||
List<int> numbers = snapshot.data as List<int>;
|
||||
int counter = 0;
|
||||
return Row(
|
||||
children: numbers.map((int num) {
|
||||
counter++;
|
||||
return CustomPaint(
|
||||
painter: BarPainter(
|
||||
width: MediaQuery.of(context).size.width / sampleSize,
|
||||
value: num,
|
||||
index: counter,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
bottomNavigationBar: BottomAppBar(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: <Widget>[
|
||||
ElevatedButton(
|
||||
onPressed: isSorting
|
||||
? null
|
||||
: () {
|
||||
reset();
|
||||
setSort(currentSort);
|
||||
},
|
||||
child: const Text("重置")),
|
||||
ElevatedButton(
|
||||
onPressed: isSorting ? null : sort, child: const Text("开始排序")),
|
||||
ElevatedButton(
|
||||
onPressed: isSorting ? null : changeSpeed,
|
||||
child: Text(
|
||||
"${speed + 1}x",
|
||||
style: const TextStyle(fontSize: 20),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BarPainter extends CustomPainter {
|
||||
//宽度
|
||||
final double width;
|
||||
|
||||
//高度(数组中对应的值)
|
||||
final int value;
|
||||
|
||||
//位置索引
|
||||
final int index;
|
||||
|
||||
BarPainter({required this.width, required this.value, required this.index});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
Paint paint = Paint();
|
||||
if (value < 500 * .10) {
|
||||
paint.color = Colors.blue.shade100;
|
||||
} else if (value < 500 * .20) {
|
||||
paint.color = Colors.blue.shade200;
|
||||
} else if (value < 500 * .30) {
|
||||
paint.color = Colors.blue.shade300;
|
||||
} else if (value < 500 * .40) {
|
||||
paint.color = Colors.blue.shade400;
|
||||
} else if (value < 500 * .50) {
|
||||
paint.color = Colors.blue.shade500;
|
||||
} else if (value < 500 * .60) {
|
||||
paint.color = Colors.blue.shade600;
|
||||
} else if (value < 500 * .70) {
|
||||
paint.color = Colors.blue.shade700;
|
||||
} else if (value < 500 * .80) {
|
||||
paint.color = Colors.blue.shade800;
|
||||
} else if (value < 500 * .90) {
|
||||
paint.color = Colors.blue.shade900;
|
||||
} else {
|
||||
paint.color = const Color(0xFF011E51);
|
||||
}
|
||||
|
||||
paint.strokeWidth = width;
|
||||
paint.strokeCap = StrokeCap.round;
|
||||
|
||||
canvas.drawLine(
|
||||
Offset(index * width, 0),
|
||||
Offset(
|
||||
index * width,
|
||||
value.ceilToDouble(),
|
||||
),
|
||||
paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
29
lib/v7/pages/sort/views/code_page/code_page.dart
Normal file
29
lib/v7/pages/sort/views/code_page/code_page.dart
Normal file
@@ -0,0 +1,29 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../functions.dart';
|
||||
import '../../provider/state.dart';
|
||||
|
||||
class CodePage extends StatelessWidget {
|
||||
const CodePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SortState state = SortStateScope.of(context);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
leading: BackButton(),
|
||||
titleTextStyle: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
centerTitle: true,
|
||||
title: Text(sortNameMap[state.config.name]!+'代码实现'),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Text('代码'*1000),
|
||||
));
|
||||
}
|
||||
}
|
||||
60
lib/v7/pages/sort/views/data_painter.dart
Normal file
60
lib/v7/pages/sort/views/data_painter.dart
Normal file
@@ -0,0 +1,60 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
|
||||
class DataPainter extends CustomPainter{
|
||||
|
||||
final List<int> data;
|
||||
|
||||
DataPainter({required this.data});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
double itemWidth = size.width/data.length;
|
||||
double height = size.height;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
48
lib/v7/pages/sort/views/sort_bar.dart
Normal file
48
lib/v7/pages/sort/views/sort_bar.dart
Normal file
@@ -0,0 +1,48 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:iroute/components/components.dart';
|
||||
|
||||
import '../provider/state.dart';
|
||||
import '../functions.dart';
|
||||
import 'sort_button.dart';
|
||||
|
||||
class SortBar extends StatelessWidget {
|
||||
const SortBar({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SortState state = SortStateScope.of(context);
|
||||
return Row(
|
||||
children: [
|
||||
const SortButton(),
|
||||
const SizedBox(width: 10,),
|
||||
DropSelectableWidget(
|
||||
value: sortNameMap[state.config.name]!,
|
||||
fontSize: 12,
|
||||
data: sortNameMap.values.toList(),
|
||||
iconSize: 20,
|
||||
height: 28,
|
||||
width: 200,
|
||||
disableColor: const Color(0xff1F425F),
|
||||
onDropSelected: (int index) async {
|
||||
SortState state = SortStateScope.of(context);
|
||||
state.config =state.config.copyWith(
|
||||
name: sortNameMap.keys.toList()[index]
|
||||
);
|
||||
// curveAnim = CurvedAnimation(
|
||||
// parent: _ctrl, curve: maps.values.toList()[index]);
|
||||
// _startAnim();
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 10,),
|
||||
GestureDetector(
|
||||
onTap: (){
|
||||
Scaffold.of(context).openEndDrawer();
|
||||
// showDialog(
|
||||
// useRootNavigator: false,
|
||||
// context: context, builder: (ctx)=>AlertDialog());
|
||||
},
|
||||
child: const Icon(Icons.settings))
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
58
lib/v7/pages/sort/views/sort_button.dart
Normal file
58
lib/v7/pages/sort/views/sort_button.dart
Normal file
@@ -0,0 +1,58 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../provider/state.dart';
|
||||
|
||||
class SortButton extends StatelessWidget {
|
||||
const SortButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SortState state = SortStateScope.of(context);
|
||||
VoidCallback? action;
|
||||
IconData icon;
|
||||
String text = '';
|
||||
Color color;
|
||||
switch (state.status) {
|
||||
case SortStatus.none:
|
||||
icon = Icons.not_started_outlined;
|
||||
color = Colors.green;
|
||||
action = state.sort;
|
||||
text = '点击启动';
|
||||
break;
|
||||
case SortStatus.sorting:
|
||||
icon = Icons.stop_circle_outlined;
|
||||
color = Colors.grey;
|
||||
action = null;
|
||||
text = '排序中...';
|
||||
break;
|
||||
case SortStatus.sorted:
|
||||
icon = CupertinoIcons.repeat;
|
||||
color = Colors.black;
|
||||
action = state.reset;
|
||||
text = '点击重置';
|
||||
break;
|
||||
}
|
||||
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: action,
|
||||
child: Wrap(
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
color: color,
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 4,),
|
||||
Text(text,style: TextStyle(fontSize: 12,fontWeight: FontWeight.bold,color: color),),
|
||||
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
164
lib/v7/pages/sort/views/sort_page.dart
Normal file
164
lib/v7/pages/sort/views/sort_page.dart
Normal file
@@ -0,0 +1,164 @@
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'sort_button.dart';
|
||||
|
||||
import '../functions.dart';
|
||||
import '../provider/state.dart';
|
||||
import 'data_painter.dart';
|
||||
|
||||
class SortPage extends StatelessWidget {
|
||||
const SortPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SortState state = SortStateScope.of(context);
|
||||
List<int> numbers = state.data;
|
||||
return Scaffold(
|
||||
body: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 220,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
// color: Color(0xffF4F4F4),
|
||||
padding: EdgeInsets.symmetric(horizontal: 12,vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
SortButton(),
|
||||
Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
Divider(height: 1,),
|
||||
Expanded(
|
||||
child: SortSelectorPanel(
|
||||
active: state.config.name,
|
||||
options: sortNameMap.values.toList(),
|
||||
onSelected: state.selectName,
|
||||
),
|
||||
),
|
||||
|
||||
],
|
||||
),
|
||||
),
|
||||
VerticalDivider(width: 1,),
|
||||
Expanded(
|
||||
child: NavigatorScope(),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onSelected(String value) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
final GlobalKey key = GlobalKey();
|
||||
|
||||
class NavigatorScope extends StatefulWidget {
|
||||
const NavigatorScope({super.key});
|
||||
|
||||
@override
|
||||
State<NavigatorScope> createState() => _NavigatorScopeState();
|
||||
}
|
||||
|
||||
class _NavigatorScopeState extends State<NavigatorScope> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SortState state = SortStateScope.of(context);
|
||||
List<int> numbers = state.data;
|
||||
return Navigator(
|
||||
onPopPage: _onPopPage,
|
||||
key: key,
|
||||
pages: [
|
||||
MaterialPage(child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: CustomPaint(
|
||||
painter: DataPainter(data: numbers),
|
||||
child: ConstrainedBox(constraints: BoxConstraints.expand()),
|
||||
),
|
||||
))
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
bool _onPopPage(Route<dynamic> route, result) {
|
||||
return route.didPop(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class SortSelectorPanel extends StatelessWidget {
|
||||
final String active;
|
||||
final ValueChanged<String> onSelected;
|
||||
final List<String> options;
|
||||
|
||||
const SortSelectorPanel(
|
||||
{super.key, required this.active, required this.options, required this.onSelected});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView.builder(
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
itemExtent: 46,
|
||||
itemCount: sortNameMap.length,
|
||||
itemBuilder: _buildByIndex,
|
||||
);
|
||||
}
|
||||
|
||||
Widget? _buildByIndex(BuildContext context, int index) {
|
||||
String key = sortNameMap.keys.toList()[index];
|
||||
bool selected = sortNameMap.keys.toList()[index] == active;
|
||||
return SortItemTile(
|
||||
selected: selected,
|
||||
onTap: ()=>onSelected(key),
|
||||
title: options[index],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SortItemTile extends StatefulWidget {
|
||||
final String title;
|
||||
final VoidCallback onTap;
|
||||
final bool selected;
|
||||
const SortItemTile({super.key, required this.title, required this.selected, required this.onTap});
|
||||
|
||||
@override
|
||||
State<SortItemTile> createState() => _SortItemTileState();
|
||||
}
|
||||
|
||||
class _SortItemTileState extends State<SortItemTile> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: widget.onTap,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0,vertical: 2),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
color: widget.selected?Color(0xffE6F0FF):null
|
||||
),
|
||||
padding: EdgeInsets.only(left: 12),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
widget.title,
|
||||
style: TextStyle(fontSize: 14,
|
||||
fontWeight: widget.selected?FontWeight.bold:null
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
109
lib/v7/pages/sort/views/sort_setting.dart
Normal file
109
lib/v7/pages/sort/views/sort_setting.dart
Normal file
@@ -0,0 +1,109 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../provider/sort_config.dart';
|
||||
import '../provider/state.dart';
|
||||
|
||||
class SortSettings extends StatefulWidget {
|
||||
|
||||
const SortSettings({super.key,});
|
||||
|
||||
@override
|
||||
State<SortSettings> createState() => _SortSettingsState();
|
||||
}
|
||||
|
||||
class _SortSettingsState extends State<SortSettings> {
|
||||
late TextEditingController _count =
|
||||
TextEditingController();
|
||||
late TextEditingController _duration = TextEditingController();
|
||||
late TextEditingController _seed =
|
||||
TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
print('========_SortSettingsState#didChangeDependencies=============');
|
||||
super.didChangeDependencies();
|
||||
SortState state = SortStateScope.of(context);
|
||||
_count.text = state.config.count.toString();
|
||||
_duration.text = state.config.duration.inMicroseconds.toString();
|
||||
_seed.text = state.config.seed.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
leading: BackButton(),
|
||||
titleTextStyle: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
centerTitle: true,
|
||||
title: Text('排序算法配置'),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text('数据数量(个数):'),
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _count,
|
||||
)),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text('时间间隔(微秒):'),
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _duration,
|
||||
)),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text('随机种子:'),
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _seed,
|
||||
)),
|
||||
],
|
||||
),
|
||||
Spacer(),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
SortState state = SortStateScope.of(context);
|
||||
state.config =state.config.copyWith(
|
||||
count: int.parse(_count.text),
|
||||
duration: Duration(
|
||||
microseconds: int.parse(_duration.text),
|
||||
),
|
||||
seed: int.parse(_seed.text)
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text('确定设置'))
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user