您的当前位置:首页正文

flutter3-macOS桌面端os系统|flutter3.x+window_manager仿mac桌面管理

2024-10-17 来源:个人技术集锦

原创力作 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

功能特色

  1. 桌面菜单支持JSON配置/二级弹窗菜单
  2. 采用ios虚化毛玻璃背景效果
  3. 经典程序坞Dock菜单
  4. 程序坞Dock菜单可拖拽式排序、支持二级弹窗式菜单
  5. 丰富视觉效果,自定义桌面主题换肤背景
  6. 可视化多窗口路由,支持弹窗方式打开新路由页面
  7. 自定义路由弹窗支持全屏、自由拖拽
  8. 支持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 组件竖向排列显示。

@overrideWidget build(BuildContext context) {  return Container(    padding: 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系统的一些分享,希望对大家有所帮助!

附上最近两个实例项目

显示全文