flutter 桌面應用之系統托盤

?系統托盤(Tray)

系統托盤就是狀態欄里面對應的圖標點擊菜單


?

主要有兩款框架

框架一句話評價
tray_manager輕量、簡單、易用,適合常規托盤功能
system_tray更底層、更強大、支持圖標/菜單/消息彈窗等更多功能,但復雜度更高

🧱 基礎能力對比

能力tray_manager ?system_tray ?
添加系統托盤圖標??
托盤菜單支持?(基本菜單)?(更豐富,支持子菜單、圖標)
點擊托盤事件監聽?(click, right click 等)?(支持更多系統原生事件)
更改托盤圖標??
彈出系統原生消息(Balloon)??(僅 Windows 支持)
子菜單圖標/多級菜單??
多平臺支持(Win/macOS/Linux)?(全支持)?(全支持)
桌面通知??(Windows balloon)
設置 Tooltip??

?? 技術架構對比

屬性tray_managersystem_tray
底層語言通過 Dart FFI 調用 C++/Objective-C更底層,直接使用 C/C++ 實現系統調用
依賴框架flutter, ffiflutter, ffi, tray_system(C庫)
項目大小小、純 Flutter稍大,構建依賴更復雜
接入簡單性更簡單,API 清晰功能強大但 API 比較底層,寫法偏原生

🔧 示例代碼對比

(添加托盤圖標 + 菜單)

tray_manager 簡潔示例:

await trayManager.setIcon('assets/tray_icon.png');
await trayManager.setContextMenu(Menu(items: [MenuItem(key: 'show', label: 'Show Window'),MenuItem.separator(),MenuItem(key: 'exit', label: 'Exit'),
]));trayManager.addListener(MyTrayListener());

system_tray 強大示例:

final SystemTray tray = SystemTray();
await tray.initSystemTray(iconPath: 'assets/tray_icon.png',toolTip: 'My Tray App',
);final Menu menu = Menu();
await menu.buildFrom([MenuItemLabel(label: 'Show Window'),MenuSeparator(),MenuItemLabel(label: 'Exit'),
]);await tray.setContextMenu(menu);
tray.registerSystemTrayEventHandler((eventName) {print("Tray event: $eventName");
});

? 推薦場景(選型建議)

你的需求推薦插件理由
只要托盤圖標 + 簡單菜單tray_manager簡單好用,集成快
要顯示系統消息、子菜單、圖標菜單system_tray功能強大,系統級集成更全面
要更復雜的原生交互(例如原生通知)system_tray支持 Windows Balloon/原生消息
想快速開發 MVP,功能不復雜tray_manager更容易快速上線
在意插件活躍度 / 維護頻率system_trayJetBrains 社區更認可(大佬多)

hello word

import 'dart:io';import 'package:flutter/material.dart';
import 'package:system_tray/system_tray.dart';void main() {runApp(const MyApp());initSystemTray();
}Future<void> initSystemTray() async {String path =Platform.isWindows ? 'assets/app_icon.ico' : 'assets/app_icon.png';final AppWindow appWindow = AppWindow();final SystemTray systemTray = SystemTray();// We first init the systray menuawait systemTray.initSystemTray(title: "system tray",iconPath: path,);// create context menufinal Menu menu = Menu();await menu.buildFrom([MenuItemLabel(label: 'Show', onClicked: (menuItem) => appWindow.show()),MenuItemLabel(label: 'Hide', onClicked: (menuItem) => appWindow.hide()),MenuItemLabel(label: 'Exit', onClicked: (menuItem) => appWindow.close()),]);// set context menuawait systemTray.setContextMenu(menu);// handle system tray eventsystemTray.registerSystemTrayEventHandler((eventName) {debugPrint("eventName: $eventName");if (eventName == kSystemTrayEventClick) {Platform.isWindows ? appWindow.show() : systemTray.popUpContextMenu();} else if (eventName == kSystemTrayEventRightClick) {Platform.isWindows ? systemTray.popUpContextMenu() : appWindow.show();}});
}
class MyApp extends StatelessWidget {const MyApp({super.key});// This widget is the root of your application.@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(// This is the theme of your application.//// TRY THIS: Try running your application with "flutter run". You'll see// the application has a purple toolbar. Then, without quitting the app,// try changing the seedColor in the colorScheme below to Colors.green// and then invoke "hot reload" (save your changes or press the "hot// reload" button in a Flutter-supported IDE, or press "r" if you used// the command line to start the app).//// Notice that the counter didn't reset back to zero; the application// state is not lost during the reload. To reset the state, use hot// restart instead.//// This works for code too, not just values: Most code changes can be// tested with just a hot reload.colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),),home: const MyHomePage(title: 'Flutter Demo Home Page'),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key, required this.title});// This widget is the home page of your application. It is stateful, meaning// that it has a State object (defined below) that contains fields that affect// how it looks.// This class is the configuration for the state. It holds the values (in this// case the title) provided by the parent (in this case the App widget) and// used by the build method of the State. Fields in a Widget subclass are// always marked "final".final String title;@overrideState<MyHomePage> createState() => _MyHomePageState();
}class _MyHomePageState extends State<MyHomePage> {int _counter = 0;void _incrementCounter() {setState(() {// This call to setState tells the Flutter framework that something has// changed in this State, which causes it to rerun the build method below// so that the display can reflect the updated values. If we changed// _counter without calling setState(), then the build method would not be// called again, and so nothing would appear to happen._counter++;});}@overrideWidget build(BuildContext context) {// This method is rerun every time setState is called, for instance as done// by the _incrementCounter method above.//// The Flutter framework has been optimized to make rerunning build methods// fast, so that you can just rebuild anything that needs updating rather// than having to individually change instances of widgets.return Scaffold(appBar: AppBar(// TRY THIS: Try changing the color here to a specific color (to// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar// change color while the other colors stay the same.backgroundColor: Theme.of(context).colorScheme.inversePrimary,// Here we take the value from the MyHomePage object that was created by// the App.build method, and use it to set our appbar title.title: Text(widget.title),),body: Center(// Center is a layout widget. It takes a single child and positions it// in the middle of the parent.child: Column(// Column is also a layout widget. It takes a list of children and// arranges them vertically. By default, it sizes itself to fit its// children horizontally, and tries to be as tall as its parent.//// Column has various properties to control how it sizes itself and// how it positions its children. Here we use mainAxisAlignment to// center the children vertically; the main axis here is the vertical// axis because Columns are vertical (the cross axis would be// horizontal).//// TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"// action in the IDE, or press "p" in the console), to see the// wireframe for each widget.mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[const Text('You have pushed the button this many times:'),Text('$_counter',style: Theme.of(context).textTheme.headlineMedium,),],),),floatingActionButton: FloatingActionButton(onPressed: _incrementCounter,tooltip: 'Increment',child: const Icon(Icons.add),), // This trailing comma makes auto-formatting nicer for build methods.);}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/75621.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/75621.shtml
英文地址,請注明出處:http://en.pswp.cn/web/75621.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

修改idea/android studio等編輯器快捷注釋從當前行開頭的反人類行為

不知道什么時候開始&#xff0c;idea編輯的快捷注釋開始從當前行開頭出現了&#xff0c;顯得實在是難受&#xff0c;我只想讓在當前行代碼的部份開始縮進兩個字符開始&#xff0c;這樣才會顯得更舒服。不知道有沒有強迫癥的猴子和我一樣&#xff0c;就像下面的效果&#xff1a;…

MySQL慢查詢全攻略:定位、分析與優化實戰

&#x1f680; MySQL慢查詢全攻略&#xff1a;定位、分析與優化實戰 #數據庫優化 #性能調優 #SQL優化 #MySQL實戰 一、慢查詢定位&#xff1a;找到性能瓶頸 1.1 開啟慢查詢日志 -- 查看當前配置 SHOW VARIABLES LIKE %slow_query%; -- 動態開啟&#xff08;重啟失效&…

當原型圖與文字說明完全不同時,測試要怎么做?

當測試遇上左右手互搏的需求&#xff0c;怎么辦&#xff1f; "這個彈窗樣式怎么和文檔寫的不一樣&#xff1f;"、"按鈕位置怎么跑到左邊去了&#xff1f;"——根據Deloitte的調查&#xff0c;62%的項目存在原型圖與需求文檔不一致的情況。這種"精神分…

關于量化交易在拉盤砸盤方面應用的部分思考

關于“砸盤”的深層解析與操盤邏輯 ??一、砸盤的本質與市場含義?? ??砸盤??指通過集中拋售大量籌碼導致價格快速下跌的行為&#xff0c;其核心目標是??制造恐慌、清洗浮籌或實現利益再分配??。不同場景下的砸盤含義不同&#xff1a; ??主動砸盤&#xff08;操控…

【項目管理】第12章 項目質量管理-- 知識點整理

項目管理-相關文檔,希望互相學習,共同進步 風123456789~-CSDN博客 (一)知識總覽 項目管理知識域 知識點: (項目管理概論、立項管理、十大知識域、配置與變更管理、績效域) 對應:第6章-第19章 第6章 項目管理概論 4分第13章 項目資源管理 3-4分第7章 項目…

一個好看的圖集展示html頁面源碼

源碼介紹 一個好看的圖集展示html頁面源碼&#xff0c;適合展示自己的作品&#xff0c;頁面美觀大氣&#xff0c;也可以作為產品展示或者個人引導頁等等 源碼由HTMLCSSJS組成&#xff0c;記事本打開源碼文件可以進行內容文字之類的修改&#xff0c; 雙擊html文件可以本地運行…

2021第十二屆藍橋杯大賽軟件賽省賽C/C++ 大學 B 組

記錄刷題的過程、感悟、題解。 希望能幫到&#xff0c;那些與我一同前行的&#xff0c;來自遠方的朋友&#x1f609; 大綱&#xff1a; 1、空間-&#xff08;題解&#xff09;-字節單位轉換 2、卡片-&#xff08;題解&#xff09;-可以不用當組合來寫&#xff0c;思維題 3、直…

LabVIEW 中 JSON 數據與簇的轉換

在 LabVIEW 編程中&#xff0c;數據格式的處理與轉換是極為關鍵的環節。其中&#xff0c;將數據在 JSON 格式與 LabVIEW 的簇結構之間進行轉換是一項常見且重要的操作。這里展示的程序片段就涉及到這一關鍵功能&#xff0c;以下將詳細介紹。 一、JSON 數據與簇的轉換功能 &am…

藍橋杯大模板

init.c void System_Init() {P0 0x00; //關閉蜂鳴器和繼電器P2 P2 & 0x1f | 0xa0;P2 & 0x1f;P0 0x00; //關閉LEDP2 P2 & 0x1f | 0x80;P2 & 0x1f; } led.c #include <LED.H>idata unsigned char temp_1 0x00; idata unsigned char temp_old…

通過HTTP協議實現Git免密操作的解決方案

工作中會遇到這樣的問題的。 通過HTTP協議實現Git免密操作的解決方案 方法一&#xff1a;啟用全局憑據存儲&#xff08;推薦&#xff09; 配置憑證存儲? 執行以下命令&#xff0c;讓Git永久保存賬號密碼&#xff08;首次操作后生效&#xff09;&#xff1a; git config --g…

Java常見面試問題

一.Liunx 二.Java基礎 1.final 2.static 3.與equals 三.Collection 1.LIst 2.Map 3.Stream 四、多線程 1.實現方法 2.線程池核心參數 3.應用場景 五、JVM 1.堆 2.棧 六、Spring 1.面向對象 2.IOC 3.AOP 七、Springboot 1.自動裝配 八、SpringCloud 1.Nacos 2.seata 3.ga…

【藍橋杯】第十六屆藍橋杯 JAVA B組記錄

試題 A: 逃離高塔 很簡單&#xff0c;簽到題&#xff0c;但是需要注意精度&#xff0c;用int會有溢出風險 答案&#xff1a;202 package lanqiao.t1;import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWrit…

PyTorch Tensor維度變換實戰:view/squeeze/expand/repeat全解析

本文從圖像數據處理、模型輸入適配等實際場景出發&#xff0c;系統講解PyTorch中view、squeeze、expand和repeat四大維度變換方法。通過代碼演示對比不同方法的適用性&#xff0c;助您掌握數據維度調整的核心技巧。 一、基礎維度操作方法 1. view&#xff1a;內存連續的形狀重…

Kubernetes nodeName Manual Scheduling practice (K8S節點名稱綁定以及手工調度)

Manual Scheduling 在 Kubernetes 中&#xff0c;手動調度框架允許您將 Pod 分配到特定節點&#xff0c;而無需依賴默認調度器。這對于測試、調試或處理特定工作負載非常有用。您可以通過在 Pod 的規范中設置 nodeName 字段來實現手動調度。以下是一個示例&#xff1a; apiVe…

即時編譯器(JIT)的編譯過程是什么?

1. 觸發編譯 JIT編譯的觸發基于熱點代碼檢測&#xff0c;主要通過兩種計數器&#xff1a; ? 方法調用計數器&#xff1a;統計方法被調用的次數&#xff08;默認閾值&#xff1a;C1為1,500次&#xff0c;C2為10,000次&#xff09;。 ? 回邊計數器&#xff1a;統計循環體的執行…

Java基礎:集合List、Map、Set(超詳細版)

集合體系概述 Collection常用方法 補充&#xff1a;addAll() Collection的遍歷方式 迭代器 增強for&#xff08;空集合可以&#xff0c;null不可以&#xff09; lambda 集合對象存儲對象原理 遍歷方式的區別 List集合 特點、特有方法 遍歷方式 &#xff08;同上&#xff09…

Elasticsearch 全面解析

Elasticsearch 全面解析 前言一、簡介核心特性應用場景 二、核心原理與架構設計1. 倒排索引&#xff08;Inverted Index&#xff09;2. 分片與副本機制&#xff08;Sharding & Replication&#xff09;3. 節點角色與集群管理 三、核心特點1. 靈活的查詢語言&#xff08;Que…

【2】k8s集群管理系列--包應用管理器之helm(Chart語法深入應用)

一、Chart模板&#xff1a;函數與管道 常用函數&#xff1a; ? quote&#xff1a;將值轉換為字符串&#xff0c;即加雙引號 ? default&#xff1a;設置默認值&#xff0c;如果獲取的值為空則為默認值 ? indent和nindent&#xff1a;縮進字符串 ? toYaml&#xff1a;引用一…

JVM 字節碼是如何存儲信息的?

JVM 字節碼是 Java 虛擬機 (JVM) 執行的指令集&#xff0c;它是一種與平臺無關的二進制格式&#xff0c;在任何支持 JVM 的平臺上都可運行的Java 程序。 字節碼存儲信息的方式&#xff0c;主要通過以下幾個關鍵組成部分和機制來實現&#xff1a; 1. 指令 (Opcodes) 和 操作數 …

基于51單片機語音實時采集系統

基于51單片機語音實時采集 &#xff08;程序&#xff0b;原理圖&#xff0b;PCB&#xff0b;設計報告&#xff09; 功能介紹 具體功能&#xff1a; 系統由STC89C52單片機ISD4004錄音芯片LM386功放模塊小喇叭LCD1602按鍵指示燈電源構成 1.可通過按鍵隨時選擇相應的錄音進行播…