flutter-macos:基于flutter3.19+window_manager桌面端os实例

Flutter3-MacOS基于flutter3.x+dart3.3+getx+window_manager实战macOS桌面系统。

技术栈

  • 编码工具: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. 整体模糊毛玻璃背景效果
  3. 程序坞Dock菜单(macos或windows11)
  4. 程序坞Dock菜单可拖拽式排序、支持二级弹窗式菜单
  5. 丰富视觉效果,自定义桌面主题换肤背景
  6. 可视化多窗口路由,支持弹窗方式打开新路由页面

项目结构

整个项目采用window_manager组件进行窗口管理。
pub-web.flutter-io.cn/packages/win...

有下面几个组件是文档没有写的:

  • DragToMoveArea 拖拽窗口
  • DragToResizeArea 缩放窗口
  • VirtualWindowFrame(仅Linux)
  • WindowCaption 自定义顶部导航栏
  • WindowCaptionButtonIcon 导航栏右上角按钮组

flutter-macos:基于flutter3.19+window_manager桌面端os实例

flutter-macos:基于flutter3.19+window_manager桌面端os实例

return Container(
  height: widget.titlebarHeight,
  decoration: BoxDecoration(
    backgroundBlendMode: widget.backgroundBlendMode,
    color: widget.backgroundColor,
    gradient: widget.gradient,
  ),
  child: Stack(
    children: [
      Row(
        children: [
          // 头部
          Container(
            child: widget.leading,
          ),
          // 可拖动区(标题)
          Expanded(
            child: DragToMoveArea(
              child: SizedBox(
                height: double.infinity,
                child: Row(
                  children: [
                    // 标题(不居中)
                    customTitle(!widget.centerTitle),
                  ],
                ),
              ),
            ),
          ),
          // 尾部
          Container(
            child: widget.trailing,
          ),
          // 按钮组
          WinBtns(theme: widget.actionButtonTheme, buttonSize: widget.actionButtonSize),
        ],
      ),
      // 标题(居中)
      customTitle(widget.centerTitle),
    ],
  ),
);

flutter3桌面布局

flutter-macos:基于flutter3.19+window_manager桌面端os实例

整个项目分为顶部导航条+桌面菜单+底部Dock菜单三大模块。

/*
 * flutter桌面os布局  Q:282310962
*/

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菜单

flutter-macos:基于flutter3.19+window_manager桌面端os实例

flutter-macos:基于flutter3.19+window_manager桌面端os实例

flutter-macos:基于flutter3.19+window_manager桌面端os实例

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),
                    ),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    ),
  ),
)

dock菜单采用json配置。

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'},
];

flutter-macos:基于flutter3.19+window_manager桌面端os实例

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: () {
                                  // ...
                                },
                              ),
                            );
                          }),
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ),
        ],
      );
    },
  );
}

flutter3自定义桌面菜单

flutter-macos:基于flutter3.19+window_manager桌面端os实例

flutter-macos:基于flutter3.19+window_manager桌面端os实例

@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(
              ...
            ),
          ),
        );
      }),
    ),
  );
}

弹窗方式打开路由页面。

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, // 鼠标滑出弹窗是否销毁关闭
    ),
  ));
}

整个项目开发下来,涉及到的知识点还是蛮多的。希望以上分享对大家有所帮助!

博客:基于flutter3+getx+bitsdojo_window仿微信客户端|flutter聊天EXE
博客:flutter-dylive:基于flutter3.x+getx短视频直播|Flutter3仿抖音app

本作品采用《CC 协议》,转载必须注明作者和本文链接
本文为原创文章,未经作者允许不得转载,欢迎大家一起交流 QQ(282310962) wx(xy190310)
讨论数量: 1

该项目已经正式上架工房,如果有需要的话可以去瞅瞅~

gf.bilibili.com/item/detail/110567...

1周前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
文章
47
粉丝
45
喜欢
101
收藏
54
排名:367
访问:2.6 万
私信
所有博文
博客标签
react
1
angular
1
react仿微信
2
react实战开发
2
react+redux
1
react网页聊天
2
angular仿微信
1
angular聊天室
1
angular+node聊天
1
h5仿微信
1
仿微信语音
1
仿微信界面
1
RN弹窗
1
react-native自定义弹窗
1
react-native弹窗
1
React Native
1
reactNative仿微信
1
RN仿微信聊天
1
ReactNative朋友圈
1
uniapp仿微信
1
uniapp聊天室
2
uniapp聊天App
1
uni-app+vue实例
1
uni-app即时通讯
1
uniapp直播
1
uni-app仿抖音
1
uniapp仿陌陌
1
uni-app小视频
1
taro仿微信
1
taro聊天室
1
taro仿微信界面
1
taro+react聊天APP
1
taro即时通讯
1
electron+vue
1
electron-vue仿微信
1
electron聊天
1
electron实例
1
flutter实例
1
flutter仿微信
1
flutter+dart聊天
1
flutter聊天室
1
flutter聊天界面
1
vue自定义弹窗
1
vue全局对话框
1
vue长按弹出框
1
vue右键弹层
1
nuxt对话框
1
vue仿微信弹窗
1
vue仿探探
1
vue仿Tinder
1
vue卡片堆叠
1
vue翻牌滑动
1
nuxt仿探探
1
nuxt聊天室
1
nuxt仿微信
1
nuxt即时聊天
1
vue+nuxt聊天实例
1
nuxt.js朋友圈
1
vue.js自定义对话框
1
vue pc端弹窗
1
vue.js桌面端模态框
1
vue弹窗组件
1
vue仿layer
1
vue.js自定义滚动条
1
vue虚拟滚动条
1
vue美化滚动条
1
vue仿饿了么滚动条
1
Vue-Scrollbar
1
react.js弹窗示例
1
react桌面端弹框
1
react.js自定义对话框
1
react pc自定义弹窗
1
react全局弹框
1
vue3.0自定义组件
1
vue3弹框
1
vue3.x对话框
1
vue3.0弹窗
1
vue3.0实例
1
vue3.0聊天室
1
vue3.0仿微信
2
vue3聊天模板
1
vue3+vant3实战开发
1
vue3.x案例
1
vue3聊天实例
1
vue3.0仿QQ
1
vue3.x实战聊天
1
vue3网页聊天
1
vue3.0仿抖音app
1
vue3短视频
1
vue3.x小视频
1
vue3.0直播实例
1
vue3+vite2+vant3实战
1
vue3跨端开发
1
electron仿QQ
1
electron打包
1
electron聊天室
1
electron仿微信
1
electron仿抖音
1
electron短视频
1
electron直播
1
vite2+electron12
1
vite2+vue3.x+swiper
1
vue3+vite.js+vant3
1
vue3后台系统
1
Electron管理系统
1
vite2+electron后台
1
electron12权限管理
1
electron桌面端后台
1
vue3桌面管理
1
vite2+electron13
1
electron仿mac桌面
1
electron桌面管理
1
vite2桌面应用
1
uniapp短视频
1
uniapp仿抖音
1
uni-app直播
1
uniapp后台
1
uni-app+uview后台系统
1
svelte.js实战开发
1
svelte3仿微信
1
svelte+svelteKit聊天室
1
svelte聊天实例
2
svelte朋友圈
1
svelte.js仿微信
1
svelte.js网页聊天
1
svelte-ui-admin
1
svelte-admin后台管理
1
svelte管理系统
1
tauri桌面应用
1
tauri+vue3
1
vite3+tauri
1
tauri聊天程序
1
tauri仿微信
1
vue3后台管理
1
vite.js管理系统
1
vue3+vite4
1
vite4+pinia
1
vue3+pinia2
1
vue3-chatgpt
2
vite-chatgpt
1
chatgpt-mobile
1
electron-chatgpt
1
electron+vite4+vue3
1
electron25-vue3
1
chatgpt-vite
1
uni-chatgpt
1
uniapp+vue3+pinia
2
vite+uniapp
1
chatgpt-uniapp
1
tauri-admin
1
tauri+rust+vue3
1
tauri后台管理
1
tauri-vite
1
tauri+vue3桌面端后台
1
react18 hooks
2
react18+arco
2
react18+zustand
1
react18-webchat
1
react18-admin
1
react-arco-admin
1
react-vite-admin
1
react18后台管理
1
electron-mateos
1
electron27+react18
1
electron-react-macos
1
electron桌面os
1
react-macos
1
uniapp+vue3直播
1
flutter3-chat
2
flutter3仿微信
2
flutter3聊天
2
flutter3+dart3
1
flutter3桌面端开发
1
flutter3-douyin
1
flutter3仿抖音
1
flutter3短视频
1
flutter3直播
1
flutter3-macos
1
flutter3-osx
1
flutter3桌面os
1
flutter3仿macOS
1
社区赞助商