This commit is contained in:
toly
2023-10-27 17:25:31 +08:00
parent 95e510d814
commit a1a70fae78
108 changed files with 7593 additions and 355 deletions

View File

@@ -13,6 +13,7 @@ class DropSelectableWidget extends StatefulWidget {
final double height;
final double width;
final double fontSize;
final String value;
const DropSelectableWidget(
{Key? key,
@@ -20,6 +21,7 @@ class DropSelectableWidget extends StatefulWidget {
this.onDropSelected,
this.disableColor = Colors.black,
this.iconSize = 24,
required this.value,
this.height = 30,
this.width = 200,
this.fontSize = 14,
@@ -40,7 +42,7 @@ class _DropSelectableWidgetState extends State<DropSelectableWidget>
late Animation<double> animation;
final LayerLink layerLink = LayerLink();
int _selectedIndex = 0;
// int _selectedIndex = 0;
@override
void initState() {
@@ -80,7 +82,12 @@ class _DropSelectableWidgetState extends State<DropSelectableWidget>
@override
Widget build(BuildContext context) {
_nodeAttachment.reparent();
return GestureDetector(
return TapRegion(
groupId: 'selector',
onTapOutside: (_){
_node.unfocus();
},
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
if (_focused) {
@@ -93,12 +100,13 @@ class _DropSelectableWidgetState extends State<DropSelectableWidget>
link: layerLink,
child: buildTarget(),
),
),
);
}
void _showOverlay() {
_overlayEntry = _createOverlayEntry();
Overlay.of(context)?.insert(_overlayEntry!);
Overlay.of(context).insert(_overlayEntry!);
}
void _hideOverlay() {
@@ -113,14 +121,12 @@ class _DropSelectableWidgetState extends State<DropSelectableWidget>
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
border: Border.all(
width: _focused ?1:1/View.of(context).devicePixelRatio,
color: _focused ? Colors.blue : widget.disableColor,
)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.data.isNotEmpty ? widget.data[_selectedIndex] : "暂无数据",style: TextStyle(
Text( widget.value ,style: TextStyle(
height: 1,
fontSize: widget.fontSize
),),
@@ -140,6 +146,7 @@ class _DropSelectableWidgetState extends State<DropSelectableWidget>
);
}
OverlayEntry _createOverlayEntry() => OverlayEntry(
builder: (BuildContext context) => UnconstrainedBox(
child: CompositedTransformFollower(
@@ -179,21 +186,23 @@ class _DropSelectableWidgetState extends State<DropSelectableWidget>
);
Widget _buildItem(BuildContext context, int index) {
return Material(
bool active = widget.data[index] == widget.value;
return TapRegion(
groupId: 'selector',
child: Material(
child: InkWell(
onTap: () {
if (_selectedIndex != index) widget.onDropSelected?.call(index);
_selectedIndex = index;
if (!active) widget.onDropSelected?.call(index);
_overlayEntry?.markNeedsBuild();
_node.unfocus();
},
child: Container(
padding: const EdgeInsets.all(8),
color: index == _selectedIndex
? Colors.blue.withOpacity(0.2)
: Colors.transparent,
color: active ? Colors.blue.withOpacity(0.2) : Colors.transparent,
child: Text(widget.data[index],style: TextStyle(fontSize: widget.fontSize),)),
),
),
);
}
}

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:window_manager/window_manager.dart';
import 'v5/app.dart';
import 'v6/app.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();

View File

@@ -7,16 +7,14 @@ import '../../../pages/color/color_detail_page.dart';
import '../../../pages/color/color_page.dart';
import '../../../pages/empty/empty_page.dart';
import '../../../pages/settings/settings_page.dart';
import '../../../pages/user/user_page.dart';
import '../../../pages/counter/counter_page.dart';
import '../../../pages/sort/views/sort_page.dart';
import '../../../pages/user/user_page.dart';
import '../transition/fade_transition_page.dart';
import '../../../pages/color/color_add_page.dart';
const List<String> kDestinationsPaths = [
'/color',
'/counter',
'/sort',
'/user',
'/settings',
];
@@ -27,13 +25,14 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
String _path = '/color';
String get path => _path;
AppRouterDelegate(){
// keepAlivePath.add('/color');
}
int? get activeIndex {
if(path.startsWith('/color')) return 0;
if(path.startsWith('/counter')) return 1;
if(path.startsWith('/sort')) return 2;
if(path.startsWith('/user')) return 3;
if(path.startsWith('/settings')) return 4;
if(path.startsWith('/user')) return 2;
if(path.startsWith('/settings')) return 3;
return null;
}
@@ -41,28 +40,29 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
Completer<dynamic>? completer;
final Map<String,List<Page>> _alivePageMap = {};
final Map<String,dynamic> _pathExtraMap = {};
final List<String> keepAlivePath = [] ;
void setPathKeepLive(String value){
_alivePageMap[value] = _buildPageByPath(value);
if(keepAlivePath.contains(value)){
keepAlivePath.remove(value);
}
keepAlivePath.add(value);
path = value;
}
final Map<String,dynamic> _pathExtraMap = {};
FutureOr<dynamic> changePath(String value,{bool forResult=false,Object? extra}){
if(forResult){
_completerMap[value] = Completer();
}
if(extra!=null){
_pathExtraMap[value] = extra;
void setPathForData(String value,dynamic data){
_pathExtraMap[value] = data;
path = value;
}
if(forResult){
return _completerMap[value]!.future;
Future<dynamic> changePathForResult(String value) async{
Completer<dynamic> completer = Completer();
_completerMap[value] = completer;
path = value;
return completer.future;
}
}
set path(String value) {
if (_path == value) return;
@@ -72,22 +72,30 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
@override
Widget build(BuildContext context) {
List<Page> pages = [];
if(_alivePageMap.containsKey(path)){
pages = _alivePageMap[path]!;
}else{
for (var element in _alivePageMap.values) {
pages.addAll(element);
}
pages.addAll(_buildPageByPath(path));
}
return Navigator(
onPopPage: _onPopPage,
pages: pages.toSet().toList(),
pages: _buildPages(path),
);
}
List<Page> _buildPages(path){
List<Page> pages = [];
List<Page> topPages = _buildPageByPath(path);
if(keepAlivePath.isNotEmpty){
for (String alivePath in keepAlivePath) {
if(alivePath!=path){
pages.addAll(_buildPageByPath(alivePath)) ;
}
}
/// 去除和 topPages 中重复的界面
pages.removeWhere((element) => topPages.map((e) => e.key).contains(element.key));
}
pages.addAll(topPages);
return pages;
}
List<Page> _buildPageByPath(String path) {
Widget? child;
if(path.startsWith('/color')){
@@ -98,12 +106,9 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
child = const CounterPage();
}
if (path == kDestinationsPaths[2]) {
child = SortPage();
}
if (path == kDestinationsPaths[3]) {
child = const UserPage();
}
if (path == kDestinationsPaths[4]) {
if (path == kDestinationsPaths[3]) {
child = const SettingPage();
}
return [
@@ -127,7 +132,6 @@ class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
if(segment =='detail'){
final Map<String, String> queryParams = uri.queryParameters;
String? selectedColor = queryParams['color'];
if (selectedColor != null) {
Color color = Color(int.parse(selectedColor, radix: 16));
result.add(FadeTransitionPage(

View File

@@ -1,20 +1,8 @@
import 'package:flutter/cupertino.dart';
import '../../../pages/color/color_add_page.dart';
import '../../../pages/color/color_detail_page.dart';
import '../../../pages/color/color_page.dart';
import '../transition/fade_transition_page.dart';
class IRoute {
final String path;
final IRoutePageBuilder builder;
final List<IRoute> children;
const IRoute({
required this.path,
this.children = const [],
required this.builder,
});
const IRoute({required this.path, this.children = const []});
@override
String toString() {
@@ -22,84 +10,28 @@ class IRoute {
}
List<String> list(){
return [];
}
}
typedef IRoutePageBuilder = Page? Function(
BuildContext context, IRouteData data);
class IRouteData {
final Object? extra;
final bool forResult;
final Uri uri;
final bool keepAlive;
IRouteData({
required this.extra,
required this.uri,
required this.forResult,
required this.keepAlive,
});
}
List<IRoute> kDestinationsIRoutes = [
const 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'];
if (selectedColor != null) {
Color color = Color(int.parse(selectedColor, radix: 16));
return FadeTransitionPage(
key: const ValueKey('/color/detail'),
child: ColorDetailPage(color: color),
);
}
return null;
},
),
IRoute(
path: '/color/add',
builder: (ctx, data) {
return const FadeTransitionPage(
key: ValueKey('/color/add'),
child: ColorAddPage(),
);
}),
IRoute(path: '/color/add'),
IRoute(path: '/color/detail'),
],
),
IRoute(
path: '/counter',
builder: (ctx, data) {
return const FadeTransitionPage(
key: ValueKey('/counter'),
child: ColorAddPage(),
);
}),
),
IRoute(
path: '/user',
builder: (ctx, data) {
return const FadeTransitionPage(
key: ValueKey('/user'),
child: ColorAddPage(),
);
}),
),
IRoute(
path: '/settings',
builder: (ctx, data) {
return const FadeTransitionPage(
key: ValueKey('/settings'),
child: ColorAddPage(),
);
}),
),
];

View File

@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import '../../../pages/sort/sort_setting.dart';
import '../router/app_router_delegate.dart';
import 'app_navigation_rail.dart';
import 'app_top_bar.dart';
@@ -11,9 +10,6 @@ class AppNavigation extends StatelessWidget {
Widget build(BuildContext context) {
double px1 = 1/View.of(context).devicePixelRatio;
return Scaffold(
endDrawer: Drawer(
child: SortSettings(),
),
body: Row(
children: [
const AppNavigationRail(),

View File

@@ -14,7 +14,6 @@ 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.sort),
MenuMeta(label: '我的', icon: Icons.person),
MenuMeta(label: '设置', icon: Icons.settings),
];
@@ -42,7 +41,7 @@ class _AppNavigationRailState extends State<AppNavigationRail> {
),
tail: Padding(
padding: const EdgeInsets.only(bottom: 6.0),
child: Text('V0.0.4',style: TextStyle(color: Colors.white,fontSize: 12),),
child: Text('V0.0.5',style: TextStyle(color: Colors.white,fontSize: 12),),
),
backgroundColor: const Color(0xff3975c6),
onDestinationSelected: _onDestinationSelected,

View File

@@ -1,10 +1,6 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:iroute/components/toly_ui/button/hover_icon_button.dart';
import 'package:iroute/components/toly_ui/popable/drop_selectable_widget.dart';
import '../../../pages/sort/functions.dart';
import '../../../pages/sort/views/sort_bar.dart';
import '../../../pages/sort/views/sort_button.dart';
import '../router/app_router_delegate.dart';
class AppRouterEditor extends StatefulWidget {
@@ -29,9 +25,6 @@ class _AppRouterEditorState extends State<AppRouterEditor> {
void _onRouteChange() {
_controller.text=router.path;
setState(() {
});
}
@override
@@ -43,14 +36,7 @@ class _AppRouterEditorState extends State<AppRouterEditor> {
@override
Widget build(BuildContext context) {
print(router.path);
if(router.path=='/sort'){
return SortBar();
}
return SizedBox(
width: 250,
child: Stack(
return Stack(
alignment: Alignment.centerRight,
children: [
SizedBox(
@@ -73,7 +59,6 @@ class _AppRouterEditorState extends State<AppRouterEditor> {
),
)
],
),
);
}
}

View File

@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:iroute/components/components.dart';
import '../../../pages/sort/functions.dart';
import '../router/app_router_delegate.dart';
import 'app_router_editor.dart';
@@ -21,7 +20,7 @@ class AppTopBar extends StatelessWidget {
child: Row(children: [
const Spacer(),
SizedBox(
// width: 200,
width: 250,
child: AppRouterEditor(
onSubmit: (path) => router.path = path,
)),
@@ -53,7 +52,6 @@ Map<String, String> kRouteLabelMap = {
'/color/detail': '颜色详情',
'/counter': '计数器',
'/user': '我的',
'/sort': '可视化排序',
'/settings': '系统设置',
};

View File

@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import '../pages/sort/provider/state.dart';
import 'navigation/router/app_router_delegate.dart';
import 'navigation/views/app_navigation.dart';
import 'navigation/views/app_navigation_rail.dart';
@@ -9,9 +8,8 @@ class UnitApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SortStateScope(
notifier: SortState(),
child: MaterialApp(
return MaterialApp(
theme: ThemeData(
fontFamily: "宋体",
scaffoldBackgroundColor: Colors.white,
@@ -25,7 +23,6 @@ class UnitApp extends StatelessWidget {
))),
debugShowCheckedModeBanner: false,
home: AppNavigation()
),
);
}
}

View File

@@ -89,9 +89,6 @@ class _ColorAddPageState extends State<ColorAddPage> {
);
}
void _selectColor() {
Navigator.of(context).pop(_color);
}
void changeColor(Color value) {
_color = value;

View File

@@ -15,15 +15,6 @@ class ColorDetailPage extends StatelessWidget {
);
String text = '# ${color.value.toRadixString(16)}';
return Scaffold(
// appBar: AppBar(
// systemOverlayStyle: SystemUiOverlayStyle(
// statusBarColor: Colors.transparent,
// statusBarIconBrightness: Brightness.light
// ),
// iconTheme: IconThemeData(color: Colors.white),
// titleTextStyle:TextStyle(color: Colors.white,fontSize: 18) ,
// backgroundColor: color,
// title: Text('颜色界面',),),
body: Container(
alignment: Alignment.center,
color: color,

View File

@@ -36,15 +36,14 @@ class _ColorPageState extends State<ColorPage> {
}
void _selectColor(Color color){
String value = color.value.toRadixString(16);
router.path = '/color/detail?color=$value';
// router.setPathForData('/color/detail',color);
// router.setPathKeepLive('/color/detail?color=$value');
// String value = color.value.toRadixString(16);
// router.path = '/color/detail?color=$value';
router.setPathForData('/color/detail',color);
}
void _toAddPage() async {
Color? color = await router.changePath('/color/add',forResult: true);
Color? color = await router.changePathForResult('/color/add');
if (color != null) {
setState(() {
_colors.add(color);

View File

@@ -0,0 +1,859 @@
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;
}
}

1
lib/v5_/app.dart Normal file
View File

@@ -0,0 +1 @@
export 'app/unit_app.dart';

View File

@@ -0,0 +1,237 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import '../../../pages/color/color_detail_page.dart';
import '../../../pages/color/color_page.dart';
import '../../../pages/empty/empty_page.dart';
import '../../../pages/settings/settings_page.dart';
import '../../../pages/user/user_page.dart';
import '../../../pages/counter/counter_page.dart';
import '../transition/fade_transition_page.dart';
import '../../../pages/color/color_add_page.dart';
const List<String> kDestinationsPaths = [
'/color',
'/counter',
'/user',
'/settings',
];
AppRouterDelegate router = AppRouterDelegate();
class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
AppRouterDelegate({String initial = '/'}){
// changePath('/color',keepAlive: true);
}
String _path = '/color';
String get path => _path;
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,List<Page>> _alivePageMap = {};
void setPathKeepLive(String value){
_alivePageMap[value] = _buildPageByPath(value);
path = value;
}
final Map<String,dynamic> _pathExtraMap = {};
FutureOr<dynamic> changePath(String value,{bool forResult=false,Object? extra,bool keepAlive = false}){
if(forResult){
_completerMap[value] = Completer();
}
if(keepAlive){
_alivePageMap[value] = _buildPageByPath(value);
}
if(extra!=null){
_pathExtraMap[value] = extra;
}
path = value;
if(forResult){
return _completerMap[value]!.future;
}
}
set path(String value) {
if (_path == value) return;
_path = value;
notifyListeners();
}
@override
Widget build(BuildContext context) {
List<Page> pages = [];
if(_alivePageMap.containsKey(path)){
for (String key in _alivePageMap.keys) {
if(path!=key){
pages.addAll(_alivePageMap[key]!);
}
}
pages.addAll(_alivePageMap[path]!) ;
}else{
for (var element in _alivePageMap.values) {
pages.addAll(element);
}
List<Page> currentPages = _buildPageByPath(path);
pages.removeWhere((element) {
return currentPages.map((e) => e.key).contains(element.key);
});
// 移除 pages 中和 currentPages 具有相同key 的元素
pages.addAll(_buildPageByPath(path));
}
return Navigator(
onPopPage: _onPopPage,
pages: pages,
);
}
List<Page> _buildPageByPath(String path) {
Widget? child;
if(path.startsWith('/color')){
return buildColorPages(path);
}
if (path == kDestinationsPaths[1]) {
child = const CounterPage();
}
if (path == kDestinationsPaths[2]) {
child = const UserPage();
}
if (path == kDestinationsPaths[3]) {
child = const SettingPage();
}
return [
FadeTransitionPage(
key: ValueKey(path),
child: child ?? const EmptyPage(),
)
];
}
List<Page> buildColorPages(String path){
List<Page> result = [];
Uri uri = Uri.parse(path);
for (String segment in uri.pathSegments) {
if(segment == 'color'){
result.add( const FadeTransitionPage(
key: ValueKey('/color'),
child:ColorPage(),
));
}
if(segment =='detail'){
final Map<String, String> queryParams = uri.queryParameters;
String? selectedColor = queryParams['color'];
if (selectedColor != null) {
Color color = Color(int.parse(selectedColor, radix: 16));
result.add( FadeTransitionPage(
key: const ValueKey('/color/detail'),
child:ColorDetailPage(color: color),
));
}else{
Color? selectedColor = _pathExtraMap[path];
if (selectedColor != null) {
result.add( FadeTransitionPage(
key: const ValueKey('/color/detail'),
child:ColorDetailPage(color: selectedColor),
));
_pathExtraMap.remove(path);
}
}
}
if(segment == 'add'){
result.add( const FadeTransitionPage(
key: ValueKey('/color/add'),
child:ColorAddPage(),
));
}
}
return result;
}
@override
Future<bool> popRoute() async {
print('=======popRoute=========');
return true;
}
bool _onPopPage(Route route, result) {
if(_completerMap.containsKey(path)){
_completerMap[path]?.complete(result);
_completerMap.remove(path);
}
path = backPath(path);
return route.didPop(result);
}
String backPath(String path){
Uri uri = Uri.parse(path);
if(uri.pathSegments.length==1) return path;
List<String> parts = List.of(uri.pathSegments)..removeLast();
return '/${parts.join('/')}';
}
@override
Future<void> setNewRoutePath(configuration) async {}
}
// class AppRouterDelegate extends RouterDelegate<String> with ChangeNotifier, PopNavigatorRouterDelegateMixin {
//
// List<String> _value = ['/'];
//
//
// List<String> get value => _value;
//
// set value(List<String> value){
// _value = value;
// notifyListeners();
// }
//
// @override
// Widget build(BuildContext context) {
// return Navigator(
// onPopPage: _onPopPage,
// pages: _value.map((e) => _pageMap[e]!).toList(),
// );
// }
//
// final Map<String, Page> _pageMap = const {
// '/': MaterialPage(child: HomePage()),
// 'a': MaterialPage(child: PageA()),
// 'b': MaterialPage(child: PageB()),
// 'c': MaterialPage(child: PageC()),
// };
//
// bool _onPopPage(Route route, result) {
// _value = List.of(_value)..removeLast();
// notifyListeners();
// return route.didPop(result);
// }
//
// @override
// GlobalKey<NavigatorState>? navigatorKey = GlobalKey<NavigatorState>();
//
// @override
// Future<void> setNewRoutePath(String configuration) async{
// }
// }

View File

@@ -0,0 +1,105 @@
import 'package:flutter/cupertino.dart';
import '../../../pages/color/color_add_page.dart';
import '../../../pages/color/color_detail_page.dart';
import '../../../pages/color/color_page.dart';
import '../transition/fade_transition_page.dart';
class IRoute {
final String path;
final IRoutePageBuilder builder;
final List<IRoute> children;
const IRoute({
required this.path,
this.children = const [],
required this.builder,
});
@override
String toString() {
return 'IRoute{path: $path, children: $children}';
}
List<String> list() {
return [];
}
}
typedef IRoutePageBuilder = Page? Function(
BuildContext context, IRouteData data);
class IRouteData {
final Object? extra;
final bool forResult;
final Uri uri;
final bool keepAlive;
IRouteData({
required this.extra,
required this.uri,
required this.forResult,
required this.keepAlive,
});
}
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'];
if (selectedColor != null) {
Color color = Color(int.parse(selectedColor, radix: 16));
return FadeTransitionPage(
key: const ValueKey('/color/detail'),
child: ColorDetailPage(color: color),
);
}
return null;
},
),
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: ColorAddPage(),
);
}),
IRoute(
path: '/user',
builder: (ctx, data) {
return const FadeTransitionPage(
key: ValueKey('/user'),
child: ColorAddPage(),
);
}),
IRoute(
path: '/settings',
builder: (ctx, data) {
return const FadeTransitionPage(
key: ValueKey('/settings'),
child: ColorAddPage(),
);
}),
];

View File

@@ -0,0 +1,53 @@
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:flutter/material.dart';
class FadeTransitionPage<T> extends Page<T> {
final Widget child;
final Duration duration;
const FadeTransitionPage({
super.key,
required this.child,
this.duration = const Duration(milliseconds: 300),
});
@override
Route<T> createRoute(BuildContext context) =>
PageBasedFadeTransitionRoute<T>(this);
}
class PageBasedFadeTransitionRoute<T> extends PageRoute<T> {
final FadeTransitionPage<T> _page;
PageBasedFadeTransitionRoute(this._page) : super(settings: _page);
@override
Color? get barrierColor => null;
@override
String? get barrierLabel => null;
@override
Duration get transitionDuration => _page.duration;
@override
bool get maintainState => true;
@override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
var curveTween = CurveTween(curve: Curves.easeIn);
return FadeTransition(
opacity: animation.drive(curveTween),
child: (settings as FadeTransitionPage).child,
);
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) =>
child;
}

View File

@@ -0,0 +1,47 @@
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:flutter/material.dart';
class NoTransitionPage<T> extends Page<T> {
final Widget child;
const NoTransitionPage({
super.key,
required this.child,
});
@override
Route<T> createRoute(BuildContext context) => NoTransitionRoute<T>(this);
}
class NoTransitionRoute<T> extends PageRoute<T> {
final NoTransitionPage<T> _page;
NoTransitionRoute(this._page) : super(settings: _page);
@override
Color? get barrierColor => null;
@override
String? get barrierLabel => null;
@override
Duration get transitionDuration => const Duration(milliseconds: 0);
@override
bool get maintainState => true;
@override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return (settings as NoTransitionPage).child;
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) =>
child;
}

View File

@@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import '../router/app_router_delegate.dart';
import 'app_navigation_rail.dart';
import 'app_top_bar.dart';
class AppNavigation extends StatelessWidget {
const AppNavigation({super.key});
@override
Widget build(BuildContext context) {
double px1 = 1/View.of(context).devicePixelRatio;
return Scaffold(
body: Row(
children: [
const AppNavigationRail(),
Expanded(
child: Column(
children: [
const AppTopBar(),
Divider(height: px1,),
Expanded(
child: Router(
routerDelegate: router,
backButtonDispatcher: RootBackButtonDispatcher(),
),
),
],
),
),
],
),
);
}
}

View File

@@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
import 'package:iroute/components/components.dart';
import '../router/app_router_delegate.dart';
class AppNavigationRail extends StatefulWidget {
const AppNavigationRail({super.key});
@override
State<AppNavigationRail> createState() => _AppNavigationRailState();
}
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),
];
@override
void initState() {
super.initState();
router.addListener(_onRouterChange);
}
@override
void dispose() {
router.removeListener(_onRouterChange);
super.dispose();
}
@override
Widget build(BuildContext context) {
return DragToMoveWrap(
child: TolyNavigationRail(
items: deskNavBarMenus,
leading: const Padding(
padding: EdgeInsets.symmetric(vertical: 18.0),
child: FlutterLogo(),
),
tail: Padding(
padding: const EdgeInsets.only(bottom: 6.0),
child: Text('V0.0.4',style: TextStyle(color: Colors.white,fontSize: 12),),
),
backgroundColor: const Color(0xff3975c6),
onDestinationSelected: _onDestinationSelected,
selectedIndex: router.activeIndex,
),
);
}
void _onDestinationSelected(int index) {
if(index==1){
router.changePath(kDestinationsPaths[index],keepAlive: true);
}else{
router.path = kDestinationsPaths[index];
}
}
void _onRouterChange() {
setState(() {});
}
}

View File

@@ -0,0 +1,70 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:iroute/components/toly_ui/button/hover_icon_button.dart';
import '../router/app_router_delegate.dart';
class AppRouterEditor extends StatefulWidget {
final ValueChanged<String>? onSubmit;
const AppRouterEditor({super.key, this.onSubmit});
@override
State<AppRouterEditor> createState() => _AppRouterEditorState();
}
class _AppRouterEditorState extends State<AppRouterEditor> {
final TextEditingController _controller = TextEditingController();
@override
void initState() {
super.initState();
_onRouteChange();
router.addListener(_onRouteChange);
}
void _onRouteChange() {
_controller.text=router.path;
setState(() {
});
}
@override
void dispose() {
_controller.dispose();
router.removeListener(_onRouteChange);
super.dispose();
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: 250,
child: Stack(
alignment: Alignment.centerRight,
children: [
SizedBox(
child: CupertinoTextField(
controller: _controller,
style: TextStyle(fontSize: 14),
padding: EdgeInsets.only(left:12,top: 6,bottom: 6,right: 32),
placeholder: '输入路由地址导航',
onSubmitted: widget.onSubmit,
decoration: BoxDecoration(color: Color(0xffF1F2F3),borderRadius: BorderRadius.circular(6)),
),
),
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: HoverIconButton(
icon: Icons.directions_outlined,
defaultColor: Color(0xff68696B),
onPressed:()=>widget.onSubmit?.call(_controller.text),
size: 20
),
)
],
),
);
}
}

View File

@@ -0,0 +1,104 @@
import 'package:flutter/material.dart';
import 'package:iroute/components/components.dart';
import '../router/app_router_delegate.dart';
import 'app_router_editor.dart';
class AppTopBar extends StatelessWidget {
const AppTopBar({super.key});
@override
Widget build(BuildContext context) {
return DragToMoveWrap(
child: Container(
alignment: Alignment.center,
height: 46,
child: Row(
children: [
const SizedBox(width: 16),
const RouterIndicator(),
Expanded(
child: Row(children: [
const Spacer(),
SizedBox(
// width: 200,
child: AppRouterEditor(
onSubmit: (path) => router.path = path,
)),
const Padding(
padding: EdgeInsets.symmetric(vertical: 12.0),
child: VerticalDivider(
width: 32,
),
)
])),
const WindowButtons()
],
),
),
);
}
}
class RouterIndicator extends StatefulWidget {
const RouterIndicator({super.key});
@override
State<RouterIndicator> createState() => _RouterIndicatorState();
}
Map<String, String> kRouteLabelMap = {
'/color': '颜色板',
'/color/add': '添加颜色',
'/color/detail': '颜色详情',
'/counter': '计数器',
'/user': '我的',
'/settings': '系统设置',
};
class _RouterIndicatorState extends State<RouterIndicator> {
@override
void initState() {
super.initState();
router.addListener(_onRouterChange);
}
@override
void dispose() {
router.removeListener(_onRouterChange);
super.dispose();
}
@override
Widget build(BuildContext context) {
return TolyBreadcrumb(
items: pathToBreadcrumbItems(router.path),
onTapItem: (item) {
if (item.to != null) {
router.path = item.to!;
}
},
);
}
void _onRouterChange() {
setState(() {});
}
List<BreadcrumbItem> pathToBreadcrumbItems(String path) {
Uri uri = Uri.parse(path);
List<BreadcrumbItem> result = [];
String to = '';
String distPath = '';
for (String segment in uri.pathSegments) {
distPath += '/$segment';
}
for (String segment in uri.pathSegments) {
to += '/$segment';
String label = kRouteLabelMap[to] ?? '未知路由';
result.add(BreadcrumbItem(to: to, label: label, active: to == distPath));
}
return result;
}
}

29
lib/v5_/app/unit_app.dart Normal file
View File

@@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'navigation/views/app_navigation.dart';
class UnitApp extends StatelessWidget {
const UnitApp({super.key});
@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()
);
}
}

View File

@@ -0,0 +1,102 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
class ColorAddPage extends StatefulWidget {
const ColorAddPage({super.key});
@override
State<ColorAddPage> createState() => _ColorAddPageState();
}
class _ColorAddPageState extends State<ColorAddPage> {
late Color _color;
@override
void initState() {
super.initState();
_color = randomColor;
}
@override
Widget build(BuildContext context) {
String text = '# ${_color.value.toRadixString(16)}';
return Scaffold(
bottomNavigationBar: Container(
margin: EdgeInsets.only(right:20,bottom: 20),
// color: Colors.redAccent,
child: Row(
textDirection:TextDirection.rtl,
children: [
ElevatedButton(onPressed: (){
Navigator.of(context).pop(_color);
}, child: Text('添加')),
SizedBox(width: 12,),
OutlinedButton(onPressed: (){
Navigator.of(context).pop();
}, child: Text('取消')),
],
),
),
body: Column(
// mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0,vertical: 16),
child: Row(
children: [
Expanded(child: Text(text,style: TextStyle(color: _color,fontSize: 24,letterSpacing: 4),)),
Container(
margin: EdgeInsets.only(left: 10),
width: 40,
height: 40,
child: Icon(
Icons.sell_outlined,
color: Colors.white,
),
decoration: BoxDecoration(
color: _color,
borderRadius: BorderRadius.circular(8),
),
),
],
),
),
ColorPicker(
colorPickerWidth:200,
// enableAlpha: false,
displayThumbColor:true,
pickerColor: _color,
paletteType: PaletteType.hueWheel,
onColorChanged: changeColor,
),
],
),
);
}
Random _random = Random();
Color get randomColor {
return Color.fromARGB(
255,
_random.nextInt(256),
_random.nextInt(256),
_random.nextInt(256),
);
}
void _selectColor() {
Navigator.of(context).pop(_color);
}
void changeColor(Color value) {
_color = value;
setState(() {
});
}
}

View File

@@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class ColorDetailPage extends StatelessWidget {
final Color color;
const ColorDetailPage({super.key, required this.color});
@override
Widget build(BuildContext context) {
const TextStyle style = TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.white
);
String text = '# ${color.value.toRadixString(16)}';
return Scaffold(
// appBar: AppBar(
// systemOverlayStyle: SystemUiOverlayStyle(
// statusBarColor: Colors.transparent,
// statusBarIconBrightness: Brightness.light
// ),
// iconTheme: IconThemeData(color: Colors.white),
// titleTextStyle:TextStyle(color: Colors.white,fontSize: 18) ,
// backgroundColor: color,
// title: Text('颜色界面',),),
body: Container(
alignment: Alignment.center,
color: color,
child: Text(text ,style: style,),
),
);
}
}

View File

@@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:iroute/components/project/colors_panel.dart';
import '../../app/navigation/router/app_router_delegate.dart';
class ColorPage extends StatefulWidget {
const ColorPage({super.key});
@override
State<ColorPage> createState() => _ColorPageState();
}
class _ColorPageState extends State<ColorPage> {
final List<Color> _colors = [
Colors.red, Colors.black, Colors.blue, Colors.green, Colors.orange,
Colors.pink, Colors.purple, Colors.indigo, Colors.amber, Colors.cyan,
Colors.redAccent, Colors.grey, Colors.blueAccent, Colors.greenAccent, Colors.orangeAccent,
Colors.pinkAccent, Colors.purpleAccent, Colors.indigoAccent, Colors.amberAccent, Colors.cyanAccent,
];
@override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(title:const Text('颜色主页')),
floatingActionButton: FloatingActionButton(
onPressed: _toAddPage,
child: const Icon(Icons.add),
),
body: Align(
alignment: Alignment.topCenter,
child: ColorsPanel(
colors: _colors,
onSelect: _selectColor,
),
),
);
}
void _selectColor(Color color){
String value = color.value.toRadixString(16);
router.path = '/color/detail?color=$value';
// router.setPathForData('/color/detail',color);
// router.setPathKeepLive('/color/detail?color=$value');
}
void _toAddPage() async {
Color? color = await router.changePath('/color/add',forResult: true);
if (color != null) {
setState(() {
_colors.add(color);
});
}
}
}

View File

@@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
class CounterPage extends StatefulWidget {
const CounterPage({super.key});
@override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}

View File

@@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
class EmptyPage extends StatelessWidget {
const EmptyPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(
// title: Text('界面走丢了'),
// ),
body: Scaffold(
body: Center(
child: Wrap(
spacing: 16,
crossAxisAlignment: WrapCrossAlignment.center,
direction: Axis.vertical,
children: [
Icon(Icons.nearby_error,size: 64, color: Colors.grey),
Text(
'404 界面丢失',
style: TextStyle(fontSize: 24, color: Colors.grey),
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
class SettingPage extends StatelessWidget {
const SettingPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body:Center(child: Text('SettingPage')));
}
}

View File

@@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
class UserPage extends StatelessWidget {
const UserPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body:Center(child: Text('UserPage')));
}
}

1
lib/v5_02/app.dart Normal file
View File

@@ -0,0 +1 @@
export 'app/unit_app.dart';

View File

@@ -0,0 +1,241 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import '../../../pages/color/color_detail_page.dart';
import '../../../pages/color/color_page.dart';
import '../../../pages/empty/empty_page.dart';
import '../../../pages/settings/settings_page.dart';
import '../../../pages/counter/counter_page.dart';
import '../../../pages/user/user_page.dart';
import '../transition/fade_transition_page.dart';
import '../../../pages/color/color_add_page.dart';
const List<String> kDestinationsPaths = [
'/color',
'/counter',
'/user',
'/settings',
];
AppRouterDelegate router = AppRouterDelegate();
ValueNotifier counter = ValueNotifier(0);
class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
String _path = '/color';
String get path => _path;
AppRouterDelegate(){
// keepAlivePath.add('/color');
}
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 = [] ;
void setPathKeepLive(String value){
if(keepAlivePath.contains(value)){
keepAlivePath.remove(value);
}
keepAlivePath.add(value);
path = value;
}
void setPathForData(String value,dynamic data){
_pathExtraMap[value] = data;
path = value;
}
Future<dynamic> changePathForResult(String value) async{
Completer<dynamic> completer = Completer();
_completerMap[value] = completer;
path = value;
return completer.future;
}
set path(String value) {
if (_path == value) return;
_path = value;
notifyListeners();
}
@override
Widget build(BuildContext context) {
return Navigator(
onPopPage: _onPopPage,
pages: _buildPages(path),
);
}
List<Page> _buildPages(path){
List<Page> pages = [];
List<Page> topPages = _buildPageByPath(path);
if(keepAlivePath.isNotEmpty){
for (String alivePath in keepAlivePath) {
if(alivePath!=path){
pages.addAll(_buildPageByPath(alivePath)) ;
}
}
/// 去除和 topPages 中重复的界面
pages.removeWhere((element) => topPages.map((e) => e.key).contains(element.key));
}
pages.addAll(topPages);
return pages;
}
int _counter = 0;
List<Page> _buildPageByPath(String path) {
Widget? child;
if(path.startsWith('/color')){
return buildColorPages(path);
}
if (path == kDestinationsPaths[1]) {
child = CounterPage(
counter: _counter,
onAddTap: (){
_counter++;
notifyListeners();
},
);
}
if (path == kDestinationsPaths[2]) {
child = const UserPage();
}
if (path == kDestinationsPaths[3]) {
child = const SettingPage();
}
return [
FadeTransitionPage(
key: ValueKey(path),
child: child ?? const EmptyPage(),
)
];
}
List<Page> buildColorPages(String path){
List<Page> result = [];
Uri uri = Uri.parse(path);
for (String segment in uri.pathSegments) {
if(segment == 'color'){
result.add( const FadeTransitionPage(
key: ValueKey('/color'),
child:ColorPage(),
));
}
if(segment =='detail'){
final Map<String, String> queryParams = uri.queryParameters;
String? selectedColor = queryParams['color'];
if (selectedColor != null) {
Color color = Color(int.parse(selectedColor, radix: 16));
result.add(FadeTransitionPage(
key: const ValueKey('/color/detail'),
child:ColorDetailPage(color: color),
));
}else{
Color? selectedColor = _pathExtraMap[path];
if (selectedColor != null) {
result.add( FadeTransitionPage(
key: const ValueKey('/color/detail'),
child:ColorDetailPage(color: selectedColor),
));
_pathExtraMap.remove(path);
}
}
}
if(segment == 'add'){
result.add( const FadeTransitionPage(
key: ValueKey('/color/add'),
child:ColorAddPage(),
));
}
}
return result;
}
@override
Future<bool> popRoute() async {
print('=======popRoute=========');
return true;
}
bool _onPopPage(Route route, result) {
if(_completerMap.containsKey(path)){
_completerMap[path]?.complete(result);
_completerMap.remove(path);
}
path = backPath(path);
return route.didPop(result);
}
String backPath(String path){
Uri uri = Uri.parse(path);
if(uri.pathSegments.length==1) return path;
List<String> parts = List.of(uri.pathSegments)..removeLast();
return '/${parts.join('/')}';
}
@override
Future<void> setNewRoutePath(configuration) async {}
}
// class AppRouterDelegate extends RouterDelegate<String> with ChangeNotifier, PopNavigatorRouterDelegateMixin {
//
// List<String> _value = ['/'];
//
//
// List<String> get value => _value;
//
// set value(List<String> value){
// _value = value;
// notifyListeners();
// }
//
// @override
// Widget build(BuildContext context) {
// return Navigator(
// onPopPage: _onPopPage,
// pages: _value.map((e) => _pageMap[e]!).toList(),
// );
// }
//
// final Map<String, Page> _pageMap = const {
// '/': MaterialPage(child: HomePage()),
// 'a': MaterialPage(child: PageA()),
// 'b': MaterialPage(child: PageB()),
// 'c': MaterialPage(child: PageC()),
// };
//
// bool _onPopPage(Route route, result) {
// _value = List.of(_value)..removeLast();
// notifyListeners();
// return route.didPop(result);
// }
//
// @override
// GlobalKey<NavigatorState>? navigatorKey = GlobalKey<NavigatorState>();
//
// @override
// Future<void> setNewRoutePath(String configuration) async{
// }
// }

View File

@@ -0,0 +1,37 @@
class IRoute {
final String path;
final List<IRoute> children;
const IRoute({required this.path, this.children = const []});
@override
String toString() {
return 'IRoute{path: $path, children: $children}';
}
List<String> list(){
return [];
}
}
const List<IRoute> kDestinationsIRoutes = [
IRoute(
path: '/color',
children: [
IRoute(path: '/color/add'),
IRoute(path: '/color/detail'),
],
),
IRoute(
path: '/counter',
),
IRoute(
path: '/user',
),
IRoute(
path: '/settings',
),
];

View File

@@ -0,0 +1,53 @@
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:flutter/material.dart';
class FadeTransitionPage<T> extends Page<T> {
final Widget child;
final Duration duration;
const FadeTransitionPage({
super.key,
required this.child,
this.duration = const Duration(milliseconds: 300),
});
@override
Route<T> createRoute(BuildContext context) =>
PageBasedFadeTransitionRoute<T>(this);
}
class PageBasedFadeTransitionRoute<T> extends PageRoute<T> {
final FadeTransitionPage<T> _page;
PageBasedFadeTransitionRoute(this._page) : super(settings: _page);
@override
Color? get barrierColor => null;
@override
String? get barrierLabel => null;
@override
Duration get transitionDuration => _page.duration;
@override
bool get maintainState => true;
@override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
var curveTween = CurveTween(curve: Curves.easeIn);
return FadeTransition(
opacity: animation.drive(curveTween),
child: (settings as FadeTransitionPage).child,
);
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) =>
child;
}

View File

@@ -0,0 +1,47 @@
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:flutter/material.dart';
class NoTransitionPage<T> extends Page<T> {
final Widget child;
const NoTransitionPage({
super.key,
required this.child,
});
@override
Route<T> createRoute(BuildContext context) => NoTransitionRoute<T>(this);
}
class NoTransitionRoute<T> extends PageRoute<T> {
final NoTransitionPage<T> _page;
NoTransitionRoute(this._page) : super(settings: _page);
@override
Color? get barrierColor => null;
@override
String? get barrierLabel => null;
@override
Duration get transitionDuration => const Duration(milliseconds: 0);
@override
bool get maintainState => true;
@override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return (settings as NoTransitionPage).child;
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) =>
child;
}

View File

@@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import '../router/app_router_delegate.dart';
import 'app_navigation_rail.dart';
import 'app_top_bar.dart';
class AppNavigation extends StatelessWidget {
const AppNavigation({super.key});
@override
Widget build(BuildContext context) {
double px1 = 1/View.of(context).devicePixelRatio;
return Scaffold(
body: Row(
children: [
const AppNavigationRail(),
Expanded(
child: Column(
children: [
const AppTopBar(),
Divider(height: px1,),
Expanded(
child: Router(
routerDelegate: router,
backButtonDispatcher: RootBackButtonDispatcher(),
),
),
],
),
),
],
),
);
}
}

View File

@@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:iroute/components/components.dart';
import '../router/app_router_delegate.dart';
class AppNavigationRail extends StatefulWidget {
const AppNavigationRail({super.key});
@override
State<AppNavigationRail> createState() => _AppNavigationRailState();
}
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),
];
@override
void initState() {
super.initState();
router.addListener(_onRouterChange);
}
@override
void dispose() {
router.removeListener(_onRouterChange);
super.dispose();
}
@override
Widget build(BuildContext context) {
return DragToMoveWrap(
child: TolyNavigationRail(
items: deskNavBarMenus,
leading: const Padding(
padding: EdgeInsets.symmetric(vertical: 18.0),
child: FlutterLogo(),
),
tail: Padding(
padding: const EdgeInsets.only(bottom: 6.0),
child: Text('V0.0.5',style: TextStyle(color: Colors.white,fontSize: 12),),
),
backgroundColor: const Color(0xff3975c6),
onDestinationSelected: _onDestinationSelected,
selectedIndex: router.activeIndex,
),
);
}
void _onDestinationSelected(int index) {
// if(index==1){
// router.setPathKeepLive(kDestinationsPaths[index]);
// }else{
// router.path = kDestinationsPaths[index];
// }
router.path = kDestinationsPaths[index];
}
void _onRouterChange() {
setState(() {});
}
}

View File

@@ -0,0 +1,64 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:iroute/components/toly_ui/button/hover_icon_button.dart';
import '../router/app_router_delegate.dart';
class AppRouterEditor extends StatefulWidget {
final ValueChanged<String>? onSubmit;
const AppRouterEditor({super.key, this.onSubmit});
@override
State<AppRouterEditor> createState() => _AppRouterEditorState();
}
class _AppRouterEditorState extends State<AppRouterEditor> {
final TextEditingController _controller = TextEditingController();
@override
void initState() {
super.initState();
_onRouteChange();
router.addListener(_onRouteChange);
}
void _onRouteChange() {
_controller.text=router.path;
}
@override
void dispose() {
_controller.dispose();
router.removeListener(_onRouteChange);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.centerRight,
children: [
SizedBox(
child: CupertinoTextField(
controller: _controller,
style: TextStyle(fontSize: 14),
padding: EdgeInsets.only(left:12,top: 6,bottom: 6,right: 32),
placeholder: '输入路由地址导航',
onSubmitted: widget.onSubmit,
decoration: BoxDecoration(color: Color(0xffF1F2F3),borderRadius: BorderRadius.circular(6)),
),
),
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: HoverIconButton(
icon: Icons.directions_outlined,
defaultColor: Color(0xff68696B),
onPressed:()=>widget.onSubmit?.call(_controller.text),
size: 20
),
)
],
);
}
}

View File

@@ -0,0 +1,104 @@
import 'package:flutter/material.dart';
import 'package:iroute/components/components.dart';
import '../router/app_router_delegate.dart';
import 'app_router_editor.dart';
class AppTopBar extends StatelessWidget {
const AppTopBar({super.key});
@override
Widget build(BuildContext context) {
return DragToMoveWrap(
child: Container(
alignment: Alignment.center,
height: 46,
child: Row(
children: [
const SizedBox(width: 16),
const RouterIndicator(),
Expanded(
child: Row(children: [
const Spacer(),
SizedBox(
width: 250,
child: AppRouterEditor(
onSubmit: (path) => router.path = path,
)),
const Padding(
padding: EdgeInsets.symmetric(vertical: 12.0),
child: VerticalDivider(
width: 32,
),
)
])),
const WindowButtons()
],
),
),
);
}
}
class RouterIndicator extends StatefulWidget {
const RouterIndicator({super.key});
@override
State<RouterIndicator> createState() => _RouterIndicatorState();
}
Map<String, String> kRouteLabelMap = {
'/color': '颜色板',
'/color/add': '添加颜色',
'/color/detail': '颜色详情',
'/counter': '计数器',
'/user': '我的',
'/settings': '系统设置',
};
class _RouterIndicatorState extends State<RouterIndicator> {
@override
void initState() {
super.initState();
router.addListener(_onRouterChange);
}
@override
void dispose() {
router.removeListener(_onRouterChange);
super.dispose();
}
@override
Widget build(BuildContext context) {
return TolyBreadcrumb(
items: pathToBreadcrumbItems(router.path),
onTapItem: (item) {
if (item.to != null) {
router.path = item.to!;
}
},
);
}
void _onRouterChange() {
setState(() {});
}
List<BreadcrumbItem> pathToBreadcrumbItems(String path) {
Uri uri = Uri.parse(path);
List<BreadcrumbItem> result = [];
String to = '';
String distPath = '';
for (String segment in uri.pathSegments) {
distPath += '/$segment';
}
for (String segment in uri.pathSegments) {
to += '/$segment';
String label = kRouteLabelMap[to] ?? '未知路由';
result.add(BreadcrumbItem(to: to, label: label, active: to == distPath));
}
return result;
}
}

View File

@@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
import 'navigation/router/app_router_delegate.dart';
import 'navigation/views/app_navigation.dart';
import 'navigation/views/app_navigation_rail.dart';
class UnitApp extends StatelessWidget {
const UnitApp({super.key});
@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()
);
}
}

View File

@@ -0,0 +1,99 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
class ColorAddPage extends StatefulWidget {
const ColorAddPage({super.key});
@override
State<ColorAddPage> createState() => _ColorAddPageState();
}
class _ColorAddPageState extends State<ColorAddPage> {
late Color _color;
@override
void initState() {
super.initState();
_color = randomColor;
}
@override
Widget build(BuildContext context) {
String text = '# ${_color.value.toRadixString(16)}';
return Scaffold(
bottomNavigationBar: Container(
margin: EdgeInsets.only(right:20,bottom: 20),
// color: Colors.redAccent,
child: Row(
textDirection:TextDirection.rtl,
children: [
ElevatedButton(onPressed: (){
Navigator.of(context).pop(_color);
}, child: Text('添加')),
SizedBox(width: 12,),
OutlinedButton(onPressed: (){
Navigator.of(context).pop();
}, child: Text('取消')),
],
),
),
body: Column(
// mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0,vertical: 16),
child: Row(
children: [
Expanded(child: Text(text,style: TextStyle(color: _color,fontSize: 24,letterSpacing: 4),)),
Container(
margin: EdgeInsets.only(left: 10),
width: 40,
height: 40,
child: Icon(
Icons.sell_outlined,
color: Colors.white,
),
decoration: BoxDecoration(
color: _color,
borderRadius: BorderRadius.circular(8),
),
),
],
),
),
ColorPicker(
colorPickerWidth:200,
// enableAlpha: false,
displayThumbColor:true,
pickerColor: _color,
paletteType: PaletteType.hueWheel,
onColorChanged: changeColor,
),
],
),
);
}
Random _random = Random();
Color get randomColor {
return Color.fromARGB(
255,
_random.nextInt(256),
_random.nextInt(256),
_random.nextInt(256),
);
}
void changeColor(Color value) {
_color = value;
setState(() {
});
}
}

View File

@@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class ColorDetailPage extends StatelessWidget {
final Color color;
const ColorDetailPage({super.key, required this.color});
@override
Widget build(BuildContext context) {
const TextStyle style = TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.white
);
String text = '# ${color.value.toRadixString(16)}';
return Scaffold(
body: Container(
alignment: Alignment.center,
color: color,
child: Text(text ,style: style,),
),
);
}
}

View File

@@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:iroute/components/project/colors_panel.dart';
import '../../app/navigation/router/app_router_delegate.dart';
class ColorPage extends StatefulWidget {
const ColorPage({super.key});
@override
State<ColorPage> createState() => _ColorPageState();
}
class _ColorPageState extends State<ColorPage> {
final List<Color> _colors = [
Colors.red, Colors.black, Colors.blue, Colors.green, Colors.orange,
Colors.pink, Colors.purple, Colors.indigo, Colors.amber, Colors.cyan,
Colors.redAccent, Colors.grey, Colors.blueAccent, Colors.greenAccent, Colors.orangeAccent,
Colors.pinkAccent, Colors.purpleAccent, Colors.indigoAccent, Colors.amberAccent, Colors.cyanAccent,
];
@override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(title:const Text('颜色主页')),
floatingActionButton: FloatingActionButton(
onPressed: _toAddPage,
child: const Icon(Icons.add),
),
body: Align(
alignment: Alignment.topCenter,
child: ColorsPanel(
colors: _colors,
onSelect: _selectColor,
),
),
);
}
void _selectColor(Color color){
// String value = color.value.toRadixString(16);
// router.path = '/color/detail?color=$value';
router.setPathForData('/color/detail',color);
}
void _toAddPage() async {
Color? color = await router.changePathForResult('/color/add');
if (color != null) {
setState(() {
_colors.add(color);
});
}
}
}

View File

@@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
class CounterPage extends StatelessWidget {
final int counter;
final VoidCallback onAddTap;
const CounterPage({super.key, required this.counter, required this.onAddTap});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$counter',
style: Theme
.of(context)
.textTheme
.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: onAddTap,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}

View File

@@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
class EmptyPage extends StatelessWidget {
const EmptyPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(
// title: Text('界面走丢了'),
// ),
body: Scaffold(
body: Center(
child: Wrap(
spacing: 16,
crossAxisAlignment: WrapCrossAlignment.center,
direction: Axis.vertical,
children: [
Icon(Icons.nearby_error,size: 64, color: Colors.grey),
Text(
'404 界面丢失',
style: TextStyle(fontSize: 24, color: Colors.grey),
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
class SettingPage extends StatelessWidget {
const SettingPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body:Center(child: Text('SettingPage')));
}
}

View File

@@ -0,0 +1,859 @@
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;
}
}

View File

@@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
class UserPage extends StatelessWidget {
const UserPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body:Center(child: Text('UserPage')));
}
}

1
lib/v6/app.dart Normal file
View File

@@ -0,0 +1 @@
export 'app/unit_app.dart';

View File

@@ -0,0 +1,154 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'iroute.dart';
const List<String> kDestinationsPaths = [
'/color',
'/counter',
'/user',
'/settings',
];
AppRouterDelegate router = AppRouterDelegate();
class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
String _path = '/color';
String get path => _path;
AppRouterDelegate() {
// keepAlivePath.add('/color');
}
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}) {
if (forResult) {
_completerMap[value] = Completer();
}
if (keepAlive) {
if (keepAlivePath.contains(value)) {
keepAlivePath.remove(value);
}
keepAlivePath.add(value);
}
if (extra != null) {
_pathExtraMap[value] = extra;
}
path = value;
if (forResult) {
return _completerMap[value]!.future;
}
}
set path(String value) {
if (_path == value) return;
_path = value;
notifyListeners();
}
@override
Widget build(BuildContext context) {
return Navigator(
onPopPage: _onPopPage,
pages: _buildPages(context, path),
);
}
List<Page> _buildPages(BuildContext context,String path) {
List<Page> pages = [];
List<Page> topPages = _buildPageByPath(context,path);
if (keepAlivePath.isNotEmpty) {
for (String alivePath in keepAlivePath) {
if (alivePath != path) {
pages.addAll(_buildPageByPath(context,alivePath));
}
}
/// 去除和 topPages 中重复的界面
pages.removeWhere(
(element) => topPages.map((e) => e.key).contains(element.key));
}
pages.addAll(topPages);
return pages;
}
Page? _buildPageByPathFromTree(BuildContext context, String path) {
// 1. 根据 path 在 iroute 树中查询节点
IRoute? iroute = root.match(path);
Object? extra = _pathExtraMap[path];
bool keepAlive = keepAlivePath.contains(path);
bool forResult = _completerMap.containsKey(path);
Page? page;
if (iroute != null) {
page = iroute.builder?.call(
context,
IRouteData(
uri: Uri.parse(path),
extra: extra,
keepAlive: keepAlive,
forResult: forResult,
),
);
}
return page;
}
List<Page> _buildPageByPath(BuildContext context,String path) {
List<Page> result = [];
Uri uri = Uri.parse(path);
String dist = '';
for (String segment in uri.pathSegments) {
dist += '/$segment';
Page? page = _buildPageByPathFromTree(context,dist);
if(page!=null){
result.add(page);
}
}
return result;
}
@override
Future<bool> popRoute() async {
print('=======popRoute=========');
return true;
}
bool _onPopPage(Route route, result) {
if (_completerMap.containsKey(path)) {
_completerMap[path]?.complete(result);
_completerMap.remove(path);
}
path = backPath(path);
return route.didPop(result);
}
String backPath(String path) {
Uri uri = Uri.parse(path);
if (uri.pathSegments.length == 1) return path;
List<String> parts = List.of(uri.pathSegments)..removeLast();
return '/${parts.join('/')}';
}
@override
Future<void> setNewRoutePath(configuration) async {}
}

View File

@@ -0,0 +1,131 @@
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';
class IRoute {
final String path;
final IRoutePageBuilder? builder;
final List<IRoute> children;
const IRoute({
required this.path,
this.children = const [],
this.builder,
});
@override
String toString() {
return 'IRoute{path: $path, children: $children}';
}
IRoute? match(String path) {
return matchRoute(this, path);
}
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;
}
}
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,
});
}
IRoute root = IRoute(path: '/', 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(),
);
}),
];

View File

@@ -0,0 +1,53 @@
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:flutter/material.dart';
class FadeTransitionPage<T> extends Page<T> {
final Widget child;
final Duration duration;
const FadeTransitionPage({
super.key,
required this.child,
this.duration = const Duration(milliseconds: 300),
});
@override
Route<T> createRoute(BuildContext context) =>
PageBasedFadeTransitionRoute<T>(this);
}
class PageBasedFadeTransitionRoute<T> extends PageRoute<T> {
final FadeTransitionPage<T> _page;
PageBasedFadeTransitionRoute(this._page) : super(settings: _page);
@override
Color? get barrierColor => null;
@override
String? get barrierLabel => null;
@override
Duration get transitionDuration => _page.duration;
@override
bool get maintainState => true;
@override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
var curveTween = CurveTween(curve: Curves.easeIn);
return FadeTransition(
opacity: animation.drive(curveTween),
child: (settings as FadeTransitionPage).child,
);
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) =>
child;
}

View File

@@ -0,0 +1,47 @@
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:flutter/material.dart';
class NoTransitionPage<T> extends Page<T> {
final Widget child;
const NoTransitionPage({
super.key,
required this.child,
});
@override
Route<T> createRoute(BuildContext context) => NoTransitionRoute<T>(this);
}
class NoTransitionRoute<T> extends PageRoute<T> {
final NoTransitionPage<T> _page;
NoTransitionRoute(this._page) : super(settings: _page);
@override
Color? get barrierColor => null;
@override
String? get barrierLabel => null;
@override
Duration get transitionDuration => const Duration(milliseconds: 0);
@override
bool get maintainState => true;
@override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return (settings as NoTransitionPage).child;
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) =>
child;
}

View File

@@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import '../router/app_router_delegate.dart';
import 'app_navigation_rail.dart';
import 'app_top_bar.dart';
class AppNavigation extends StatelessWidget {
const AppNavigation({super.key});
@override
Widget build(BuildContext context) {
double px1 = 1/View.of(context).devicePixelRatio;
return Scaffold(
body: Row(
children: [
const AppNavigationRail(),
Expanded(
child: Column(
children: [
const AppTopBar(),
Divider(height: px1,),
Expanded(
child: Router(
routerDelegate: router,
backButtonDispatcher: RootBackButtonDispatcher(),
),
),
],
),
),
],
),
);
}
}

View File

@@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
import 'package:iroute/components/components.dart';
import '../router/app_router_delegate.dart';
class AppNavigationRail extends StatefulWidget {
const AppNavigationRail({super.key});
@override
State<AppNavigationRail> createState() => _AppNavigationRailState();
}
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),
];
@override
void initState() {
super.initState();
router.addListener(_onRouterChange);
}
@override
void dispose() {
router.removeListener(_onRouterChange);
super.dispose();
}
@override
Widget build(BuildContext context) {
return DragToMoveWrap(
child: TolyNavigationRail(
items: deskNavBarMenus,
leading: const Padding(
padding: EdgeInsets.symmetric(vertical: 18.0),
child: FlutterLogo(),
),
tail: Padding(
padding: const EdgeInsets.only(bottom: 6.0),
child: Text('V0.0.5',style: TextStyle(color: Colors.white,fontSize: 12),),
),
backgroundColor: const Color(0xff3975c6),
onDestinationSelected: _onDestinationSelected,
selectedIndex: router.activeIndex,
),
);
}
void _onDestinationSelected(int index) {
if(index==1){
router.changePath(kDestinationsPaths[index],keepAlive: true);
}else{
router.path = kDestinationsPaths[index];
}
}
void _onRouterChange() {
setState(() {});
}
}

View File

@@ -0,0 +1,64 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:iroute/components/toly_ui/button/hover_icon_button.dart';
import '../router/app_router_delegate.dart';
class AppRouterEditor extends StatefulWidget {
final ValueChanged<String>? onSubmit;
const AppRouterEditor({super.key, this.onSubmit});
@override
State<AppRouterEditor> createState() => _AppRouterEditorState();
}
class _AppRouterEditorState extends State<AppRouterEditor> {
final TextEditingController _controller = TextEditingController();
@override
void initState() {
super.initState();
_onRouteChange();
router.addListener(_onRouteChange);
}
void _onRouteChange() {
_controller.text=router.path;
}
@override
void dispose() {
_controller.dispose();
router.removeListener(_onRouteChange);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.centerRight,
children: [
SizedBox(
child: CupertinoTextField(
controller: _controller,
style: TextStyle(fontSize: 14),
padding: EdgeInsets.only(left:12,top: 6,bottom: 6,right: 32),
placeholder: '输入路由地址导航',
onSubmitted: widget.onSubmit,
decoration: BoxDecoration(color: Color(0xffF1F2F3),borderRadius: BorderRadius.circular(6)),
),
),
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: HoverIconButton(
icon: Icons.directions_outlined,
defaultColor: Color(0xff68696B),
onPressed:()=>widget.onSubmit?.call(_controller.text),
size: 20
),
)
],
);
}
}

View File

@@ -0,0 +1,104 @@
import 'package:flutter/material.dart';
import 'package:iroute/components/components.dart';
import '../router/app_router_delegate.dart';
import 'app_router_editor.dart';
class AppTopBar extends StatelessWidget {
const AppTopBar({super.key});
@override
Widget build(BuildContext context) {
return DragToMoveWrap(
child: Container(
alignment: Alignment.center,
height: 46,
child: Row(
children: [
const SizedBox(width: 16),
const RouterIndicator(),
Expanded(
child: Row(children: [
const Spacer(),
SizedBox(
width: 250,
child: AppRouterEditor(
onSubmit: (path) => router.path = path,
)),
const Padding(
padding: EdgeInsets.symmetric(vertical: 12.0),
child: VerticalDivider(
width: 32,
),
)
])),
const WindowButtons()
],
),
),
);
}
}
class RouterIndicator extends StatefulWidget {
const RouterIndicator({super.key});
@override
State<RouterIndicator> createState() => _RouterIndicatorState();
}
Map<String, String> kRouteLabelMap = {
'/color': '颜色板',
'/color/add': '添加颜色',
'/color/detail': '颜色详情',
'/counter': '计数器',
'/user': '我的',
'/settings': '系统设置',
};
class _RouterIndicatorState extends State<RouterIndicator> {
@override
void initState() {
super.initState();
router.addListener(_onRouterChange);
}
@override
void dispose() {
router.removeListener(_onRouterChange);
super.dispose();
}
@override
Widget build(BuildContext context) {
return TolyBreadcrumb(
items: pathToBreadcrumbItems(router.path),
onTapItem: (item) {
if (item.to != null) {
router.path = item.to!;
}
},
);
}
void _onRouterChange() {
setState(() {});
}
List<BreadcrumbItem> pathToBreadcrumbItems(String path) {
Uri uri = Uri.parse(path);
List<BreadcrumbItem> result = [];
String to = '';
String distPath = '';
for (String segment in uri.pathSegments) {
distPath += '/$segment';
}
for (String segment in uri.pathSegments) {
to += '/$segment';
String label = kRouteLabelMap[to] ?? '未知路由';
result.add(BreadcrumbItem(to: to, label: label, active: to == distPath));
}
return result;
}
}

30
lib/v6/app/unit_app.dart Normal file
View File

@@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
import 'navigation/router/app_router_delegate.dart';
import 'navigation/views/app_navigation.dart';
import 'navigation/views/app_navigation_rail.dart';
class UnitApp extends StatelessWidget {
const UnitApp({super.key});
@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()
);
}
}

View File

@@ -0,0 +1,99 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
class ColorAddPage extends StatefulWidget {
const ColorAddPage({super.key});
@override
State<ColorAddPage> createState() => _ColorAddPageState();
}
class _ColorAddPageState extends State<ColorAddPage> {
late Color _color;
@override
void initState() {
super.initState();
_color = randomColor;
}
@override
Widget build(BuildContext context) {
String text = '# ${_color.value.toRadixString(16)}';
return Scaffold(
bottomNavigationBar: Container(
margin: EdgeInsets.only(right:20,bottom: 20),
// color: Colors.redAccent,
child: Row(
textDirection:TextDirection.rtl,
children: [
ElevatedButton(onPressed: (){
Navigator.of(context).pop(_color);
}, child: Text('添加')),
SizedBox(width: 12,),
OutlinedButton(onPressed: (){
Navigator.of(context).pop();
}, child: Text('取消')),
],
),
),
body: Column(
// mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0,vertical: 16),
child: Row(
children: [
Expanded(child: Text(text,style: TextStyle(color: _color,fontSize: 24,letterSpacing: 4),)),
Container(
margin: EdgeInsets.only(left: 10),
width: 40,
height: 40,
child: Icon(
Icons.sell_outlined,
color: Colors.white,
),
decoration: BoxDecoration(
color: _color,
borderRadius: BorderRadius.circular(8),
),
),
],
),
),
ColorPicker(
colorPickerWidth:200,
// enableAlpha: false,
displayThumbColor:true,
pickerColor: _color,
paletteType: PaletteType.hueWheel,
onColorChanged: changeColor,
),
],
),
);
}
Random _random = Random();
Color get randomColor {
return Color.fromARGB(
255,
_random.nextInt(256),
_random.nextInt(256),
_random.nextInt(256),
);
}
void changeColor(Color value) {
_color = value;
setState(() {
});
}
}

View File

@@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class ColorDetailPage extends StatelessWidget {
final Color color;
const ColorDetailPage({super.key, required this.color});
@override
Widget build(BuildContext context) {
const TextStyle style = TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.white
);
String text = '# ${color.value.toRadixString(16)}';
return Scaffold(
body: Container(
alignment: Alignment.center,
color: color,
child: Text(text ,style: style,),
),
);
}
}

View File

@@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:iroute/components/project/colors_panel.dart';
import '../../app/navigation/router/app_router_delegate.dart';
class ColorPage extends StatefulWidget {
const ColorPage({super.key});
@override
State<ColorPage> createState() => _ColorPageState();
}
class _ColorPageState extends State<ColorPage> {
final List<Color> _colors = [
Colors.red, Colors.black, Colors.blue, Colors.green, Colors.orange,
Colors.pink, Colors.purple, Colors.indigo, Colors.amber, Colors.cyan,
Colors.redAccent, Colors.grey, Colors.blueAccent, Colors.greenAccent, Colors.orangeAccent,
Colors.pinkAccent, Colors.purpleAccent, Colors.indigoAccent, Colors.amberAccent, Colors.cyanAccent,
];
@override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(title:const Text('颜色主页')),
floatingActionButton: FloatingActionButton(
onPressed: _toAddPage,
child: const Icon(Icons.add),
),
body: Align(
alignment: Alignment.topCenter,
child: ColorsPanel(
colors: _colors,
onSelect: _selectColor,
),
),
);
}
void _selectColor(Color color){
// String value = color.value.toRadixString(16);
// router.path = '/color/detail?color=$value';
router.changePath('/color/detail',extra: color);
}
void _toAddPage() async {
Color? color = await router.changePath('/color/add',forResult: true);
if (color != null) {
setState(() {
_colors.add(color);
});
}
}
}

View File

@@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
class CounterPage extends StatefulWidget {
const CounterPage({super.key});
@override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}

View File

@@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
class EmptyPage extends StatelessWidget {
const EmptyPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(
// title: Text('界面走丢了'),
// ),
body: Scaffold(
body: Center(
child: Wrap(
spacing: 16,
crossAxisAlignment: WrapCrossAlignment.center,
direction: Axis.vertical,
children: [
Icon(Icons.nearby_error,size: 64, color: Colors.grey),
Text(
'404 界面丢失',
style: TextStyle(fontSize: 24, color: Colors.grey),
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
class SettingPage extends StatelessWidget {
const SettingPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body:Center(child: Text('SettingPage')));
}
}

View File

@@ -0,0 +1,859 @@
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;
}
}

View File

@@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
class UserPage extends StatelessWidget {
const UserPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body:Center(child: Text('UserPage')));
}
}

1
lib/v6_/app.dart Normal file
View File

@@ -0,0 +1 @@
export 'app/unit_app.dart';

View File

@@ -0,0 +1,245 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import '../../../pages/color/color_detail_page.dart';
import '../../../pages/color/color_page.dart';
import '../../../pages/empty/empty_page.dart';
import '../../../pages/settings/settings_page.dart';
import '../../../pages/user/user_page.dart';
import '../../../pages/counter/counter_page.dart';
import '../../../pages/sort/views/sort_page.dart';
import '../transition/fade_transition_page.dart';
import '../../../pages/color/color_add_page.dart';
const List<String> kDestinationsPaths = [
'/color',
'/counter',
'/sort',
'/user',
'/settings',
];
AppRouterDelegate router = AppRouterDelegate();
class AppRouterDelegate extends RouterDelegate<Object> with ChangeNotifier {
AppRouterDelegate({String initial = '/'}){
changePath('/color',keepAlive: true);
}
String _path = '/color';
String get path => _path;
int? get activeIndex {
if(path.startsWith('/color')) return 0;
if(path.startsWith('/counter')) return 1;
if(path.startsWith('/sort')) return 2;
if(path.startsWith('/user')) return 3;
if(path.startsWith('/settings')) return 4;
return null;
}
final Map<String,Completer<dynamic>> _completerMap = {};
Completer<dynamic>? completer;
final Map<String,List<Page>> _alivePageMap = {};
void setPathKeepLive(String value){
_alivePageMap[value] = _buildPageByPath(value);
path = value;
}
final Map<String,dynamic> _pathExtraMap = {};
FutureOr<dynamic> changePath(String value,{bool forResult=false,Object? extra,bool keepAlive = false}){
if(forResult){
_completerMap[value] = Completer();
}
if(keepAlive){
_alivePageMap[value] = _buildPageByPath(value);
}
if(extra!=null){
_pathExtraMap[value] = extra;
}
path = value;
if(forResult){
return _completerMap[value]!.future;
}
}
set path(String value) {
if (_path == value) return;
_path = value;
notifyListeners();
}
@override
Widget build(BuildContext context) {
List<Page> pages = [];
if(_alivePageMap.containsKey(path)){
for (String key in _alivePageMap.keys) {
if(path!=key){
pages.addAll(_alivePageMap[key]!);
}
}
pages.addAll(_alivePageMap[path]!) ;
}else{
for (var element in _alivePageMap.values) {
pages.addAll(element);
}
List<Page> currentPages = _buildPageByPath(path);
pages.removeWhere((element) {
return currentPages.map((e) => e.key).contains(element.key);
});
// 移除 pages 中和 currentPages 具有相同key 的元素
pages.addAll(_buildPageByPath(path));
}
return Navigator(
onPopPage: _onPopPage,
pages: pages.toSet().toList(),
);
}
List<Page> _buildPageByPath(String path) {
Widget? child;
if(path.startsWith('/color')){
return buildColorPages(path);
}
if (path == kDestinationsPaths[1]) {
child = const CounterPage();
}
if (path == kDestinationsPaths[2]) {
child = SortPage();
}
if (path == kDestinationsPaths[3]) {
child = const UserPage();
}
if (path == kDestinationsPaths[4]) {
child = const SettingPage();
}
return [
FadeTransitionPage(
key: ValueKey(path),
child: child ?? const EmptyPage(),
)
];
}
List<Page> buildColorPages(String path){
List<Page> result = [];
Uri uri = Uri.parse(path);
for (String segment in uri.pathSegments) {
if(segment == 'color'){
result.add( const FadeTransitionPage(
key: ValueKey('/color'),
child:ColorPage(),
));
}
if(segment =='detail'){
final Map<String, String> queryParams = uri.queryParameters;
String? selectedColor = queryParams['color'];
if (selectedColor != null) {
Color color = Color(int.parse(selectedColor, radix: 16));
result.add( FadeTransitionPage(
key: const ValueKey('/color/detail'),
child:ColorDetailPage(color: color),
));
}else{
Color? selectedColor = _pathExtraMap[path];
if (selectedColor != null) {
result.add( FadeTransitionPage(
key: const ValueKey('/color/detail'),
child:ColorDetailPage(color: selectedColor),
));
_pathExtraMap.remove(path);
}
}
}
if(segment == 'add'){
result.add( const FadeTransitionPage(
key: ValueKey('/color/add'),
child:ColorAddPage(),
));
}
}
return result;
}
@override
Future<bool> popRoute() async {
print('=======popRoute=========');
return true;
}
bool _onPopPage(Route route, result) {
if(_completerMap.containsKey(path)){
_completerMap[path]?.complete(result);
_completerMap.remove(path);
}
path = backPath(path);
return route.didPop(result);
}
String backPath(String path){
Uri uri = Uri.parse(path);
if(uri.pathSegments.length==1) return path;
List<String> parts = List.of(uri.pathSegments)..removeLast();
return '/${parts.join('/')}';
}
@override
Future<void> setNewRoutePath(configuration) async {}
}
// class AppRouterDelegate extends RouterDelegate<String> with ChangeNotifier, PopNavigatorRouterDelegateMixin {
//
// List<String> _value = ['/'];
//
//
// List<String> get value => _value;
//
// set value(List<String> value){
// _value = value;
// notifyListeners();
// }
//
// @override
// Widget build(BuildContext context) {
// return Navigator(
// onPopPage: _onPopPage,
// pages: _value.map((e) => _pageMap[e]!).toList(),
// );
// }
//
// final Map<String, Page> _pageMap = const {
// '/': MaterialPage(child: HomePage()),
// 'a': MaterialPage(child: PageA()),
// 'b': MaterialPage(child: PageB()),
// 'c': MaterialPage(child: PageC()),
// };
//
// bool _onPopPage(Route route, result) {
// _value = List.of(_value)..removeLast();
// notifyListeners();
// return route.didPop(result);
// }
//
// @override
// GlobalKey<NavigatorState>? navigatorKey = GlobalKey<NavigatorState>();
//
// @override
// Future<void> setNewRoutePath(String configuration) async{
// }
// }

View File

@@ -0,0 +1,105 @@
import 'package:flutter/cupertino.dart';
import '../../../pages/color/color_add_page.dart';
import '../../../pages/color/color_detail_page.dart';
import '../../../pages/color/color_page.dart';
import '../transition/fade_transition_page.dart';
class IRoute {
final String path;
final IRoutePageBuilder builder;
final List<IRoute> children;
const IRoute({
required this.path,
this.children = const [],
required this.builder,
});
@override
String toString() {
return 'IRoute{path: $path, children: $children}';
}
List<String> list() {
return [];
}
}
typedef IRoutePageBuilder = Page? Function(
BuildContext context, IRouteData data);
class IRouteData {
final Object? extra;
final bool forResult;
final Uri uri;
final bool keepAlive;
IRouteData({
required this.extra,
required this.uri,
required this.forResult,
required this.keepAlive,
});
}
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'];
if (selectedColor != null) {
Color color = Color(int.parse(selectedColor, radix: 16));
return FadeTransitionPage(
key: const ValueKey('/color/detail'),
child: ColorDetailPage(color: color),
);
}
return null;
},
),
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: ColorAddPage(),
);
}),
IRoute(
path: '/user',
builder: (ctx, data) {
return const FadeTransitionPage(
key: ValueKey('/user'),
child: ColorAddPage(),
);
}),
IRoute(
path: '/settings',
builder: (ctx, data) {
return const FadeTransitionPage(
key: ValueKey('/settings'),
child: ColorAddPage(),
);
}),
];

View File

@@ -0,0 +1,53 @@
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:flutter/material.dart';
class FadeTransitionPage<T> extends Page<T> {
final Widget child;
final Duration duration;
const FadeTransitionPage({
super.key,
required this.child,
this.duration = const Duration(milliseconds: 300),
});
@override
Route<T> createRoute(BuildContext context) =>
PageBasedFadeTransitionRoute<T>(this);
}
class PageBasedFadeTransitionRoute<T> extends PageRoute<T> {
final FadeTransitionPage<T> _page;
PageBasedFadeTransitionRoute(this._page) : super(settings: _page);
@override
Color? get barrierColor => null;
@override
String? get barrierLabel => null;
@override
Duration get transitionDuration => _page.duration;
@override
bool get maintainState => true;
@override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
var curveTween = CurveTween(curve: Curves.easeIn);
return FadeTransition(
opacity: animation.drive(curveTween),
child: (settings as FadeTransitionPage).child,
);
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) =>
child;
}

View File

@@ -0,0 +1,47 @@
// Copyright 2021, the Flutter project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:flutter/material.dart';
class NoTransitionPage<T> extends Page<T> {
final Widget child;
const NoTransitionPage({
super.key,
required this.child,
});
@override
Route<T> createRoute(BuildContext context) => NoTransitionRoute<T>(this);
}
class NoTransitionRoute<T> extends PageRoute<T> {
final NoTransitionPage<T> _page;
NoTransitionRoute(this._page) : super(settings: _page);
@override
Color? get barrierColor => null;
@override
String? get barrierLabel => null;
@override
Duration get transitionDuration => const Duration(milliseconds: 0);
@override
bool get maintainState => true;
@override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return (settings as NoTransitionPage).child;
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) =>
child;
}

View File

@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import '../../../pages/sort/sort_setting.dart';
import '../router/app_router_delegate.dart';
import 'app_navigation_rail.dart';
import 'app_top_bar.dart';
class AppNavigation extends StatelessWidget {
const AppNavigation({super.key});
@override
Widget build(BuildContext context) {
double px1 = 1/View.of(context).devicePixelRatio;
return Scaffold(
endDrawer: Drawer(
child: SortSettings(),
),
body: Row(
children: [
const AppNavigationRail(),
Expanded(
child: Column(
children: [
const AppTopBar(),
Divider(height: px1,),
Expanded(
child: Router(
routerDelegate: router,
backButtonDispatcher: RootBackButtonDispatcher(),
),
),
],
),
),
],
),
);
}
}

View File

@@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
import 'package:iroute/components/components.dart';
import '../router/app_router_delegate.dart';
class AppNavigationRail extends StatefulWidget {
const AppNavigationRail({super.key});
@override
State<AppNavigationRail> createState() => _AppNavigationRailState();
}
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.sort),
MenuMeta(label: '我的', icon: Icons.person),
MenuMeta(label: '设置', icon: Icons.settings),
];
@override
void initState() {
super.initState();
router.addListener(_onRouterChange);
}
@override
void dispose() {
router.removeListener(_onRouterChange);
super.dispose();
}
@override
Widget build(BuildContext context) {
return DragToMoveWrap(
child: TolyNavigationRail(
items: deskNavBarMenus,
leading: const Padding(
padding: EdgeInsets.symmetric(vertical: 18.0),
child: FlutterLogo(),
),
tail: Padding(
padding: const EdgeInsets.only(bottom: 6.0),
child: Text('V0.0.4',style: TextStyle(color: Colors.white,fontSize: 12),),
),
backgroundColor: const Color(0xff3975c6),
onDestinationSelected: _onDestinationSelected,
selectedIndex: router.activeIndex,
),
);
}
void _onDestinationSelected(int index) {
if(index==1){
router.changePath(kDestinationsPaths[index],keepAlive: true);
}else{
router.path = kDestinationsPaths[index];
}
}
void _onRouterChange() {
setState(() {});
}
}

View File

@@ -0,0 +1,79 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:iroute/components/toly_ui/button/hover_icon_button.dart';
import 'package:iroute/components/toly_ui/popable/drop_selectable_widget.dart';
import '../../../pages/sort/functions.dart';
import '../../../pages/sort/views/sort_bar.dart';
import '../../../pages/sort/views/sort_button.dart';
import '../router/app_router_delegate.dart';
class AppRouterEditor extends StatefulWidget {
final ValueChanged<String>? onSubmit;
const AppRouterEditor({super.key, this.onSubmit});
@override
State<AppRouterEditor> createState() => _AppRouterEditorState();
}
class _AppRouterEditorState extends State<AppRouterEditor> {
final TextEditingController _controller = TextEditingController();
@override
void initState() {
super.initState();
_onRouteChange();
router.addListener(_onRouteChange);
}
void _onRouteChange() {
_controller.text=router.path;
setState(() {
});
}
@override
void dispose() {
_controller.dispose();
router.removeListener(_onRouteChange);
super.dispose();
}
@override
Widget build(BuildContext context) {
print(router.path);
if(router.path=='/sort'){
return SortBar();
}
return SizedBox(
width: 250,
child: Stack(
alignment: Alignment.centerRight,
children: [
SizedBox(
child: CupertinoTextField(
controller: _controller,
style: TextStyle(fontSize: 14),
padding: EdgeInsets.only(left:12,top: 6,bottom: 6,right: 32),
placeholder: '输入路由地址导航',
onSubmitted: widget.onSubmit,
decoration: BoxDecoration(color: Color(0xffF1F2F3),borderRadius: BorderRadius.circular(6)),
),
),
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: HoverIconButton(
icon: Icons.directions_outlined,
defaultColor: Color(0xff68696B),
onPressed:()=>widget.onSubmit?.call(_controller.text),
size: 20
),
)
],
),
);
}
}

View File

@@ -0,0 +1,106 @@
import 'package:flutter/material.dart';
import 'package:iroute/components/components.dart';
import '../../../pages/sort/functions.dart';
import '../router/app_router_delegate.dart';
import 'app_router_editor.dart';
class AppTopBar extends StatelessWidget {
const AppTopBar({super.key});
@override
Widget build(BuildContext context) {
return DragToMoveWrap(
child: Container(
alignment: Alignment.center,
height: 46,
child: Row(
children: [
const SizedBox(width: 16),
const RouterIndicator(),
Expanded(
child: Row(children: [
const Spacer(),
SizedBox(
// width: 200,
child: AppRouterEditor(
onSubmit: (path) => router.path = path,
)),
const Padding(
padding: EdgeInsets.symmetric(vertical: 12.0),
child: VerticalDivider(
width: 32,
),
)
])),
const WindowButtons()
],
),
),
);
}
}
class RouterIndicator extends StatefulWidget {
const RouterIndicator({super.key});
@override
State<RouterIndicator> createState() => _RouterIndicatorState();
}
Map<String, String> kRouteLabelMap = {
'/color': '颜色板',
'/color/add': '添加颜色',
'/color/detail': '颜色详情',
'/counter': '计数器',
'/user': '我的',
'/sort': '可视化排序',
'/settings': '系统设置',
};
class _RouterIndicatorState extends State<RouterIndicator> {
@override
void initState() {
super.initState();
router.addListener(_onRouterChange);
}
@override
void dispose() {
router.removeListener(_onRouterChange);
super.dispose();
}
@override
Widget build(BuildContext context) {
return TolyBreadcrumb(
items: pathToBreadcrumbItems(router.path),
onTapItem: (item) {
if (item.to != null) {
router.path = item.to!;
}
},
);
}
void _onRouterChange() {
setState(() {});
}
List<BreadcrumbItem> pathToBreadcrumbItems(String path) {
Uri uri = Uri.parse(path);
List<BreadcrumbItem> result = [];
String to = '';
String distPath = '';
for (String segment in uri.pathSegments) {
distPath += '/$segment';
}
for (String segment in uri.pathSegments) {
to += '/$segment';
String label = kRouteLabelMap[to] ?? '未知路由';
result.add(BreadcrumbItem(to: to, label: label, active: to == distPath));
}
return result;
}
}

33
lib/v6_/app/unit_app.dart Normal file
View File

@@ -0,0 +1,33 @@
import 'package:flutter/material.dart';
import '../pages/sort/provider/state.dart';
import 'navigation/router/app_router_delegate.dart';
import 'navigation/views/app_navigation.dart';
import 'navigation/views/app_navigation_rail.dart';
class UnitApp extends StatelessWidget {
const UnitApp({super.key});
@override
Widget build(BuildContext context) {
return SortStateScope(
notifier: SortState(),
child: 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()
),
);
}
}

View File

@@ -0,0 +1,102 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
class ColorAddPage extends StatefulWidget {
const ColorAddPage({super.key});
@override
State<ColorAddPage> createState() => _ColorAddPageState();
}
class _ColorAddPageState extends State<ColorAddPage> {
late Color _color;
@override
void initState() {
super.initState();
_color = randomColor;
}
@override
Widget build(BuildContext context) {
String text = '# ${_color.value.toRadixString(16)}';
return Scaffold(
bottomNavigationBar: Container(
margin: EdgeInsets.only(right:20,bottom: 20),
// color: Colors.redAccent,
child: Row(
textDirection:TextDirection.rtl,
children: [
ElevatedButton(onPressed: (){
Navigator.of(context).pop(_color);
}, child: Text('添加')),
SizedBox(width: 12,),
OutlinedButton(onPressed: (){
Navigator.of(context).pop();
}, child: Text('取消')),
],
),
),
body: Column(
// mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0,vertical: 16),
child: Row(
children: [
Expanded(child: Text(text,style: TextStyle(color: _color,fontSize: 24,letterSpacing: 4),)),
Container(
margin: EdgeInsets.only(left: 10),
width: 40,
height: 40,
child: Icon(
Icons.sell_outlined,
color: Colors.white,
),
decoration: BoxDecoration(
color: _color,
borderRadius: BorderRadius.circular(8),
),
),
],
),
),
ColorPicker(
colorPickerWidth:200,
// enableAlpha: false,
displayThumbColor:true,
pickerColor: _color,
paletteType: PaletteType.hueWheel,
onColorChanged: changeColor,
),
],
),
);
}
Random _random = Random();
Color get randomColor {
return Color.fromARGB(
255,
_random.nextInt(256),
_random.nextInt(256),
_random.nextInt(256),
);
}
void _selectColor() {
Navigator.of(context).pop(_color);
}
void changeColor(Color value) {
_color = value;
setState(() {
});
}
}

View File

@@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class ColorDetailPage extends StatelessWidget {
final Color color;
const ColorDetailPage({super.key, required this.color});
@override
Widget build(BuildContext context) {
const TextStyle style = TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.white
);
String text = '# ${color.value.toRadixString(16)}';
return Scaffold(
// appBar: AppBar(
// systemOverlayStyle: SystemUiOverlayStyle(
// statusBarColor: Colors.transparent,
// statusBarIconBrightness: Brightness.light
// ),
// iconTheme: IconThemeData(color: Colors.white),
// titleTextStyle:TextStyle(color: Colors.white,fontSize: 18) ,
// backgroundColor: color,
// title: Text('颜色界面',),),
body: Container(
alignment: Alignment.center,
color: color,
child: Text(text ,style: style,),
),
);
}
}

View File

@@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:iroute/components/project/colors_panel.dart';
import '../../app/navigation/router/app_router_delegate.dart';
class ColorPage extends StatefulWidget {
const ColorPage({super.key});
@override
State<ColorPage> createState() => _ColorPageState();
}
class _ColorPageState extends State<ColorPage> {
final List<Color> _colors = [
Colors.red, Colors.black, Colors.blue, Colors.green, Colors.orange,
Colors.pink, Colors.purple, Colors.indigo, Colors.amber, Colors.cyan,
Colors.redAccent, Colors.grey, Colors.blueAccent, Colors.greenAccent, Colors.orangeAccent,
Colors.pinkAccent, Colors.purpleAccent, Colors.indigoAccent, Colors.amberAccent, Colors.cyanAccent,
];
@override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(title:const Text('颜色主页')),
floatingActionButton: FloatingActionButton(
onPressed: _toAddPage,
child: const Icon(Icons.add),
),
body: Align(
alignment: Alignment.topCenter,
child: ColorsPanel(
colors: _colors,
onSelect: _selectColor,
),
),
);
}
void _selectColor(Color color){
String value = color.value.toRadixString(16);
router.path = '/color/detail?color=$value';
// router.setPathForData('/color/detail',color);
// router.setPathKeepLive('/color/detail?color=$value');
}
void _toAddPage() async {
Color? color = await router.changePath('/color/add',forResult: true);
if (color != null) {
setState(() {
_colors.add(color);
});
}
}
}

View File

@@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
class CounterPage extends StatefulWidget {
const CounterPage({super.key});
@override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}

View File

@@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
class EmptyPage extends StatelessWidget {
const EmptyPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(
// title: Text('界面走丢了'),
// ),
body: Scaffold(
body: Center(
child: Wrap(
spacing: 16,
crossAxisAlignment: WrapCrossAlignment.center,
direction: Axis.vertical,
children: [
Icon(Icons.nearby_error,size: 64, color: Colors.grey),
Text(
'404 界面丢失',
style: TextStyle(fontSize: 24, color: Colors.grey),
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
class SettingPage extends StatelessWidget {
const SettingPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body:Center(child: Text('SettingPage')));
}
}

View File

@@ -0,0 +1,859 @@
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;
}
}

Some files were not shown because too many files have changed in this diff Show More