原创力作flutter3+getX+window_manager仿Mac桌面系统平台Flutter-MacOS。
flutter3_macui基于最新跨端技术flutter3.19+dart3.3+window_manager+system_tray构建的一款桌面端仿MacOS风格os系统项目。支持自定义主题换肤、毛玻璃虚化背景、程序坞Dock菜单多级嵌套+自由拖拽排序、可拖拽路由弹窗等功能。
FlutterMacOS系统是自研原创多级菜单、支持可拖拽弹窗打开路由页面模板。
使用技术
- 编辑器:vscode
- 框架技术:Flutter3.19.2+Dart3.3.0
- 窗口管理:window_manager^0.3.8
- 路由/状态管理:get^4.6.6
- 缓存服务:get_storage^2.1.1
- 拖拽排序:reorderables^0.6.0
- 图表组件:fl_chart^0.67.0
- 托盘管理:system_tray^2.0.3
功能特色
- 桌面菜单支持JSON配置/二级弹窗菜单
- 采用ios虚化毛玻璃背景效果
- 经典程序坞Dock菜单
- 程序坞Dock菜单可拖拽式排序、支持二级弹窗式菜单
- 丰富视觉效果,自定义桌面主题换肤背景
- 可视化多窗口路由,支持弹窗方式打开新路由页面
- 自定义路由弹窗支持全屏、自由拖拽
- 支持macOS和windows 11两种风格Dock菜单
项目结构
通过flutter create flutter_macos命令即可快速创建一个flutter空项目模板。
通过flutter run -d windows命令即可运行到windows桌面。
在开始开发项目之前,需要自己配置好flutter sdk开发环境。具体配置大家可以去官网查阅资料,有详细的配置步骤。
Flutter3桌面os布局模板
桌面os布局整体分为顶部导航条+桌面菜单+底部Dock菜单三大模块。
return Scaffold( key: scaffoldKey, body: Container( // 背景图主题 decoration: skinTheme(), // DragToResizeArea自定义缩放窗口 child: DragToResizeArea( child: Flex( direction: Axis.vertical, crossAxisAlignment: CrossAxisAlignment.start, children: [ // 导航栏 WindowTitlebar( onDrawer: () { // 自定义打开右侧drawer scaffoldKey.currentState?.openEndDrawer(); }, ), // 桌面区域 Expanded( child: GestureDetector( child: Container( color: Colors.transparent, child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: GestureDetector( child: const WindowDesktop(), onSecondaryTapDown: (TapDownDetails details) { posDX = details.globalPosition.dx; posDY = details.globalPosition.dy; }, onSecondaryTap: () { debugPrint('桌面图标右键'); showDeskIconContextmenu(); }, ), ), ], ), ), onSecondaryTapDown: (TapDownDetails details) { posDX = details.globalPosition.dx; posDY = details.globalPosition.dy; }, onSecondaryTap: () { debugPrint('桌面右键'); showDeskContextmenu(); }, ), ), // Dock菜单 settingController.settingData['dock'] == 'windows' ? const WindowTabbar() : const WindowDock() , ], ), ), ), endDrawer: Drawer( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0.0)), width: 300, child: const Settings(), ), );
Flutter3实现程序坞Dock菜单
底部Dock菜单支持macOS和windows11两种风格。采用毛玻璃虚化背景、支持拖拽排序和二级弹窗菜单。
鼠标滑过图标,该图标带动画效果放大,采用MouseRegion和ScaleTransition缩放动画组件一起实现功能。
// 动画控制器 late AnimationController controller = AnimationController(duration: const Duration(milliseconds: 300), vsync: this);
MouseRegion( cursor: SystemMouseCursors.click, onEnter: (event) { setState(() { hoveredIndex = index; }); controller.forward(from: 0.0); }, onExit: (event) { setState(() { hoveredIndex = -1; }); controller.stop(); }, child: GestureDetector( onTapDown: (TapDownDetails details) { anchorDx = details.globalPosition.dx; }, onTap: () { if(item!['children'] != null) { showDockDialog(item!['children']); } }, // 缩放动画 child: ScaleTransition( alignment: Alignment.bottomCenter, scale: hoveredIndex == index ? controller.drive(Tween(begin: 1.0, end: 1.5).chain(CurveTween(curve: Curves.easeOutCubic))) : Tween(begin: 1.0, end: 1.0).animate(controller) , child: UnconstrainedBox( child: Stack( alignment: AlignmentDirectional.topCenter, children: [ // tooltip提示 Visibility( visible: hoveredIndex == index && !draggable, child: Positioned( top: 0, child: SizedOverflowBox( size: Size.zero, child: Container( alignment: Alignment.center, padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 1.0), margin: const EdgeInsets.only(bottom: 20.0), decoration: BoxDecoration( color: Colors.black54, borderRadius: BorderRadius.circular(3.0), ), child: Text('${item!['tooltip']}', style: const TextStyle(color: Colors.white, fontSize: 8.0, fontFamily: 'arial')), ), ), ), ), // 图片/图标 item!['children'] != null ? thumbDock(item!['children']) : SizedBox( height: 35.0, width: 35.0, child: item!['type'] != null && item!['type'] == 'icon' ? IconTheme( data: const IconThemeData(color: Colors.white, size: 32.0), child: item!['imgico'], ) : Image.asset('${item!['imgico']}') , ), // 圆点 服务器托管 Visibility( visible: item!['active'] != null, child: Positioned( bottom: 0, child: SizedOverflowBox( size: Size.zero, child: Container( margin: const EdgeInsets.only(top: 2.0), height: 4.0, width: 4.0, decoration: BoxDecoration( color: Colors.black87, borderRadius: BorderRadius.circular(10.0), ), ), ), ), ), ], ), ), ), ), )
菜单JSON格式配置项,图标支持Icon图标和Image图片。
List dockList = [ {'tooltip': 'Flutter3.19', 'imgico': 'assets/images/logo.png'}, {'tooltip': 'Safari', 'imgico': 'assets/images/mac/safari.png', 'active': true}, { 'tooltip': 'Launchpad', 'imgico': 'assets/images/mac/launchpad.png', 'children': [ {'tooltip': 'Podcasts', 'imgico': 'assets/images/mac/podcasts.png'}, {'tooltip': 'Quicktime', 'imgico': 'assets/images/mac/quicktime.png'}, {'tooltip': 'Notes', 'imgico': 'assets/images/mac/notes.png'}, {'tooltip': 'Reminder', 'imgico': 'assets/images/mac/reminders.png'}, {'tooltip': 'Calc', 'imgico': 'assets/images/mac/calculator.png'}, ] }, {'tooltip': 'Appstore', 'imgico': 'assets/images/mac/appstore.png',}, {'tooltip': 'Messages', 'imgico': 'assets/images/mac/messages.png', 'active': true}, {'type': 'divider'}, ... {'tooltip': 'Recycle Bin', 'imgico': 'assets/images/mac/bin.png'}, ];
二级菜单采用showDialog组件实现三列排版,支持背景虚化、可滚动列表。定位采用Positioned组件实现功能。
void showDockDialog(data) { anchorDockOffset(); showDialog( context: context, barrierColor: Colors.transparent, builder: (context) { return Stack( children: [ Positioned( top: anchorDy - 210, left: anchorDx - 120, width: 240.0, height: 210, child: ClipRRect( borderRadius: BorderRadius.circular(16.0), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 20.0, sigmaY: 20.0), child: Container( padding: const EdgeInsets.symmetric(vertical: 10.0), decoration: const BoxDecoration( backgroundBlendMode: BlendMode.overlay, color: Colors.white, ), child: ListView( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 10.0,), child: Wrap( runSpacing: 5.0, spacing: 5.0, children: List.generate(data.length, (index) { final item = data[index]; return MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( child: Column( children: [ // 图片/图标 SizedBox( height: 40.0, width: 40.0, child: item!['type'] != null && item!['type'] == 'icon' ? IconTheme( data: const IconThemeData(color: Colors.black87, size: 35.0), child: item!['imgico'], ) : Image.asset('${item!['imgico']}') , ), SizedBox( width: 70, child: Text(item['tooltip'], style: const TextStyle(color: Colors.black87, fontSize: 12.0), maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center,), ) ], ), onTap: () { // ... }, ), ); }), ), ), ], ), ), ), ), ), ], ); }, ); }
Flutter实现桌面多级菜单
桌面菜单采用Wrap组件竖向排列显示。
@override Widget build(BuildContext context) { return Container( paddi服务器托管ng: const EdgeInsets.all(10.0), child: Wrap( direction: Axis.vertical, spacing: 5.0, runSpacing: 5.0, children: List.generate(deskList.length, (index) { final item = deskList[index]; return MouseRegion( cursor: SystemMouseCursors.click, onEnter: (event) { setState(() { hoveredIndex = index; }); }, onExit: (event) { setState(() { hoveredIndex = -1; }); }, child: GestureDetector( onTapDown: (TapDownDetails details) { anchorDx = details.globalPosition.dx; anchorDy = details.globalPosition.dy; }, onTap: () { if(item!['children'] != null) { showDeskDialog(item!['children']); }else { showRouteDialog(item); } }, child: Container( ... ), ), ); }), ), ); }
桌面菜单缩略二级弹窗菜单和Dock菜单实现思路差不多。点击桌面菜单通过弹窗方式显示配置的页面。
/** 桌面弹窗式路由页面 Q:282310962 */ void showRouteDialog(item) async { // 链接 if(item!['link'] != null) { await launchUrl(Uri.parse(item!['link'])); return; } // 弹窗图标 Widget dialogIcon() { if(item!['type'] != null && item!['type'] == 'icon') { return IconTheme( data: const IconThemeData(size: 16.0), child: item!['imgico'], ); }else { return Image.asset('${item!['imgico']}', height: 16.0, width: 16.0, fit: BoxFit.cover); } } // Fdialog参数 dynamic dialog = item!['dialog'] ?? {}; navigator?.push(FdialogRoute( child: Fdialog( // 标题 title: dialog!['title'] ?? Row( children: [ dialogIcon(), const SizedBox(width: 5.0,), Text('${item!['title']}',), ], ), // 内容 content: dialog!['content'] ?? ListView( padding: const EdgeInsets.all(10.0), children: [ item!['component'] ?? const Center(child: Column(children: [Icon(Icons.layers,), Text('Empty~'),],)), ], ), titlePadding: dialog!['titlePadding'], // 标题内间距 backgroundColor: dialog!['backgroundColor'] ?? Colors.white.withOpacity(.85), // 弹窗背景色 barrierColor: dialog!['barrierColor'], // 弹窗遮罩层颜色 offset: dialog!['offset'], // 弹窗位置(坐标点) width: dialog!['width'] ?? 800, // 宽度 height: dialog!['height'] ?? 500, // 高度 radius: dialog!['radius'], // 圆角 fullscreen: dialog!['fullscreen'] ?? false, // 是否全屏 maximizable: dialog!['maximizable'] ?? true, // 是否显示最大化按钮 closable: dialog!['closable'] ?? true, // 是否显示关闭按钮 customClose: dialog!['customClose'], // 自定义关闭按钮 closeIcon: dialog!['closeIcon'], // 自定义关闭图标 actionColor: dialog!['actionColor'], // 右上角按钮组颜色 actionSize: dialog!['actionSize'], // 右上角按钮组大小 draggable: dialog!['draggable'] ?? true, // 是否可拖拽 destroyOnExit: dialog!['destroyOnExit'] ?? false, // 鼠标滑出弹窗是否销毁关闭 ), )); }
桌面菜单json配置和Dock菜单配置差不多,只不过多了component和dialog两个参数。component参数是弹窗打开需要显示的页面,dialog是自定义弹窗配置参数。
List deskList = [ {'title': 'Flutter3.19', 'imgico': 'assets/images/logo.png', 'link': 'https://flutter.dev/'}, { 'title': '首页', 'imgico': const Icon(Icons.home_outlined), 'type': 'icon', 'component': const Home(), 'dialog': { 'fullscreen': true } }, { 'title': '工作台', 'imgico': const Icon(Icons.poll_outlined), 'type': 'icon', 'component': const Dashboard(), }, { 'title': '组件', 'imgico': const Icon(Icons.apps), 'type': 'icon', 'children': [ {'title': 'Mail', 'imgico': 'assets/images/mac/mail.png'}, {'title': 'Info', 'imgico': 'assets/images/mac/info.png'}, {'title': 'Editor', 'imgico': 'assets/images/mac/scripteditor.png'}, {'title': '下载', 'imgico': const Icon(Icons.download_outlined), 'type': 'icon'}, {'title': 'Bug统计', 'imgico': const Icon(Icons.bug_report_outlined), 'type': 'icon'}, {'title': '计算器', 'imgico': const Icon(Icons.calculate), 'type': 'icon'}, {'title': '图表', 'imgico': const Icon(Icons.bar_chart), 'type': 'icon'}, {'title': '打印', 'imgico': const Icon(Icons.print), 'type': 'icon'}, {'title': '站内信', 'imgico': const Icon(Icons.campaign), 'type': 'icon'}, {'title': '云存储', 'imgico': const Icon(Icons.cloud_outlined), 'type': 'icon'}, {'title': '裁剪', 'imgico': const Icon(Icons.crop_outlined), 'type': 'icon'}, ] }, { 'title': '私密空间', 'imgico': const Icon(Icons.camera_outlined), 'type': 'icon', 'component': const Uzone(), }, ... { 'title': '公众号', 'imgico': const Icon(Icons.qr_code), 'type': 'icon', 'dialog': { 'title': const Text('QRcode', style: TextStyle(color: Colors.white60, fontSize: 14.0, fontFamily: 'arial')), 'content': Padding( padding: const EdgeInsets.all(10.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ Image.asset('assets/images/qrcode_white.png', height: 120.0, fit: BoxFit.contain,), const Spacer(), const Text('扫一扫,关注公众号', style: TextStyle(color: Colors.white60, fontSize: 12.0,),), ], ), ), 'backgroundColor': const Color(0xff07c160), 'actionColor': Colors.white54, 'width': 300, 'height': 220, 'maximizable': false, 'closable': true, 'draggable': true, } }, ];
好了,综上就是flutter3+window_manager实战桌面端仿MacOS系统的一些分享,希望对大家有所帮助!
附上最近两个实例项目
https://www.cnblogs.com/xiaoyan2017/p/17938517
https://www.cnblogs.com/xiaoyan2017/p/18048244
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net
项目背景 我们的APP是一个数字藏品平台,里面的很多藏品需要展示3D模型,3D模型里面可能会包含场景,动画,交互。而对应3D场景来说,考虑到要同时支持iOS端,安卓端,Unity是个天然的优秀方案。 对于Unity容器来说,需要满足如下的功能: 1.在APP启…