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

来源:https://www.cnblogs.com/xiaoyan2017/p/18132176
-Advertisement-
Play Games

原創力作flutter3+getX+window_manager仿Mac桌面系統平臺Flutter-MacOS。 flutter3_macui基於最新跨端技術flutter3.19+dart3.3+window_manager+system_tray構建的一款桌面端仿MacOS風格os系統項目。支持 ...


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

 

本文為博主原創文章,未經博主允許不得轉載,歡迎大家一起交流 QQ(282310962) wx(xy190310)
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 4月10日,以“Data+AI,構建新質生產力”為主題的袋鼠雲春季發佈會圓滿落幕。大會中,袋鼠雲帶來了一系列“+AI”的數字化產品與最新行業沉澱,旨在將數據與AI緊密結合,打破傳統的生產力邊界,賦能企業實現更高質量、更高效率的數字化發展。 2部白皮書:聚焦行業沉澱 《行業指標體系白皮書》:系統闡述了 ...
  • 添加一套針對MuSQL資料庫CRUD的Dao和Mapper代碼,同時我們寫了一個註解以切麵的方式實現根據配置實例化Oracle的Dao、MySQl的Dao、同時調用Oracle和MySQLDao的功能。​ 首先,我們需要設置一個新的MySQL資料庫環境,這將作為我們的新數據源。我們有額外的數據核對方... ...
  • 很高興和大家宣佈,Apache DolphinScheduler 社區今年再次成功入選入選由中國科學院軟體研究所開源軟體供應鏈點亮計劃發起的“開源之夏”活動。 入選公示鏈接:https://mp.weixin.qq.com/s/9ExBWGoFPzZ0_SrpAcosZg 此活動旨在鼓勵和引導在校學 ...
  • 本次監控將採用Prometheus、Grafana可視化工具以及postgres_exporter對OpenTenBase進行全面監控和優化。首先,通過Docker安裝了Prometheus,配置了必要的文件形式進行服務發現,實現了系統正常監控。接著,使用Docker啟動Grafana,並配置數據源... ...
  • 替代變數說明 在Oracle命令行中預設&為替代變數,只要在執行的sql中出現&符號,那麼&符號後面的sql則會失效; 這就意味著,包含&符號的sql將不會正確的執行 比如: insert into XXX_DB.XXX_TABLE (COLUMN1,COLUMN2) value ('AB&CD', ...
  • ​ 一、MySQL的下載 下載地址:http://dev.mysql.com/downloads/mysql 進入下載頁面,選擇所需版本,這裡示範MySQL8.0 圖一 選擇版本,下載MSI(軟體安裝和配置一同進行) 圖二 二、MySQL的安裝 雙擊下載好的mysql-installer-commu ...
  • Oracle數據類型 簡要說明 字元類型 char和varchar2,可表達任何字元串 數字類型 number(m,n),可表達任何數字,m是數字的精度,n是小數點後的位數,如果n為0則表示是一個整數。 日期類型 date,存放日期和時間,包括年(yyyy)、月(mm)、日(dd)、小時(hh24) ...
  • 目錄一、關閉MySQL服務1、win+R打開運行,輸入services.msc回車2、服務里找到MySQL並停止二、卸載MySQL軟體1、打開控制模板--卸載程式--卸載MySQL相關的所有組件三、刪除MySQL在物理硬碟上的所有文件1、刪除MySQL的安裝目錄(預設在C盤下的Program Fil ...
一周排行
    -Advertisement-
    Play Games
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...
  • 目錄前言PostgreSql安裝測試額外Nuget安裝Person.cs模擬運行Navicate連postgresql解決方案Garnet為什麼要選擇Garnet而不是RedisRedis不再開源Windows版的Redis是由微軟維護的Windows Redis版本老舊,後續可能不再更新Garne ...
  • C#TMS系統代碼-聯表報表學習 領導被裁了之後很快就有人上任了,幾乎是無縫銜接,很難讓我不想到這早就決定好了。我的職責沒有任何變化。感受下來這個系統封裝程度很高,我只要會調用方法就行。這個系統交付之後不會有太多問題,更多應該是做小需求,有大的開發任務應該也是第二期的事,嗯?怎麼感覺我變成運維了?而 ...
  • 我在隨筆《EAV模型(實體-屬性-值)的設計和低代碼的處理方案(1)》中介紹了一些基本的EAV模型設計知識和基於Winform場景下低代碼(或者說無代碼)的一些實現思路,在本篇隨筆中,我們來分析一下這種針對通用業務,且只需定義就能構建業務模塊存儲和界面的解決方案,其中的數據查詢處理的操作。 ...
  • 對某個遠程伺服器啟用和設置NTP服務(Windows系統) 打開註冊表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer 將 Enabled 的值設置為 1,這將啟用NTP伺服器功 ...
  • title: Django信號與擴展:深入理解與實踐 date: 2024/5/15 22:40:52 updated: 2024/5/15 22:40:52 categories: 後端開發 tags: Django 信號 松耦合 觀察者 擴展 安全 性能 第一部分:Django信號基礎 Djan ...
  • 使用xadmin2遇到的問題&解決 環境配置: 使用的模塊版本: 關聯的包 Django 3.2.15 mysqlclient 2.2.4 xadmin 2.0.1 django-crispy-forms >= 1.6.0 django-import-export >= 0.5.1 django-r ...
  • 今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap和LazyMap鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap鏈,主要調用鏈為: 調用鏈詳細描述: ObjectInputStream.readObject() DefaultedMap.readObject ...
  • 後端應用級開發者該如何擁抱 AI GC?就是在這樣的一個大的浪潮下,我們的傳統的應用級開發者。我們該如何選擇職業或者是如何去快速轉型,跟上這樣的一個行業的一個浪潮? 0 AI金字塔模型 越往上它的整個難度就是職業機會也好,或者說是整個的這個運作也好,它的難度會越大,然後越往下機會就會越多,所以這是一 ...
  • @Autowired是Spring框架提供的註解,@Resource是Java EE 5規範提供的註解。 @Autowired預設按照類型自動裝配,而@Resource預設按照名稱自動裝配。 @Autowired支持@Qualifier註解來指定裝配哪一個具有相同類型的bean,而@Resourc... ...