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


    原创力作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菜单支持macOSwindows11两种风格。采用毛玻璃虚化背景、支持拖拽排序二级弹窗菜单

    鼠标滑过图标,该图标带动画效果放大,采用 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(
        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菜单配置差不多,只不过多了componentdialog两个参数。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

     

  • 相关阅读:
    MIT课程分布式系统学习06——Fault Tolerance raft1
    map-reduce执行过程
    Handler的交互场景
    【分布式金融交易模型】服务调用
    前端必备的 HTTP 知识!看这篇就够了!!
    分类预测 | MATLAB实现基于BiLSTM-AdaBoost双向长短期记忆网络结合AdaBoost多输入分类预测
    sql 8
    常见消息队列分析对比
    SpringMVC-18-异常机制
    Nginx缓存
  • 原文地址:https://www.cnblogs.com/xiaoyan2017/p/18132176