Flutter桌面应用程序定义系统托盘Tray
文章目录
- 概念
- 实现方案
- 1. tray_manager
- 依赖库
- 支持平台
- 实现步骤
- 2. system_tray
- 依赖库
- 支持平台
- 实现步骤
- 3. 两种方案对比
- 4. 注意事项
- 5. 话题拓展
概念
系统托盘:系统托盘是一种用户界面元素,通常出现在操作系统的任务栏或桌面顶部。它是一个水平的狭长区域,用于显示各种图标和通知,以提供快速访问和操作特定应用程序或系统功能。系统托盘通常包含操作系统或第三方应用程序的图标,这些图标可以显示有关应用程序状态、提醒和通知等信息。用户可以通过单击这些图标来打开应用程序的主窗口、执行特定功能或查看详细信息。系统托盘的设计旨在提供一种方便的方式来管理和访问常用的应用程序和系统功能,以提高用户的工作效率。
效果展示

作为现代操作系统中常见的一个组件,系统托盘能够让用户方便地访问常用的应用程序或者系统功能。对于Flutter桌面应用程序开发者来说,如何在应用程序中定义系统托盘是一个值得探讨的问题。本文将简介系统托盘的概念,并介绍两种可用的Flutter桌面应用程序系统托盘方案。
实现方案
1. tray_manager
依赖库
tray_manager
支持平台
Windows, macOS & Linux
实现步骤
- 在pubspec.yaml中添加依赖
dependencies:...tray_manager: ^0.2.0
- 导入依赖
import 'package:flutter/material.dart' hide MenuItem;
import 'package:tray_manager/tray_manager.dart';
- 配置系统托盘特性
Future<void> _init() async {//设置系统托盘图标,Windows图标必须文件后缀必须是.icoawait trayManager.setIcon(Platform.isWindows? 'assets/images/tray_icon_original.ico': 'assets/images/img_1.png',);//设置系统托盘的标题trayManager.setTitle("system tray");//设置系统托盘的标题trayManager.setToolTip("How to use system tray with Flutter:鼠标滑过提示");//设置系统托盘的菜单Menu menu = Menu(items: [//设置系统托盘的子菜单MenuItem.submenu(// key key: 'window_settings',label: '窗口设置',//trayManager 不支持菜单项添加图标,该配置无效icon: Platform.isWindows? 'assets/images/app_icon.bmp': 'assets/images/img_1.png',submenu: Menu(items: [MenuItem.checkbox(checked: true,label: "毛玻璃效果",onClick: (MenuItem menuItem) {menuItem.checked = !(menuItem.checked == true);if (kDebugMode) {print("毛玻璃效果 onClick ${menuItem.checked}");}},),MenuItem.checkbox(checked: true,label: "窗口置顶",onClick: (MenuItem menuItem) {menuItem.checked = !(menuItem.checked == true);if (kDebugMode) {print("窗口置顶 onClick ${menuItem.checked}");}},),MenuItem.checkbox(checked: true,label: "自启动",onClick: (MenuItem menuItem) {menuItem.checked = !(menuItem.checked == true);if (kDebugMode) {print("自启动 onClick ${menuItem.checked}");}},),//可选类型的菜单栏MenuItem.checkbox(checked: true,label: "图标闪烁",onClick: (MenuItem menuItem) {menuItem.checked = !(menuItem.checked == true);if (kDebugMode) {print("图标闪烁 onClick ${menuItem.checked}");}},),])),//分割线MenuItem.separator(),MenuItem(key: 'open_app',label: 'Open App',onClick: (MenuItem menuItem) {}),MenuItem(key: 'exit_app',label: 'Exit App',onClick: (MenuItem menuItem) {}),],);if (kDebugMode) {print("menu:${menu.toJson()}");}//为系统托盘配置菜单await trayManager.setContextMenu(menu);
}
- 监听TrayListener
import 'package:flutter/material.dart';
import 'package:tray_manager/tray_manager.dart';class HomePage extends StatefulWidget {_HomePageState createState() => _HomePageState();
}class _HomePageState extends State<HomePage> with TrayListener {void initState() {trayManager.addListener(this);super.initState();_init();}void dispose() {trayManager.removeListener(this);super.dispose();}void _init() {// ...}Widget build(BuildContext context) {// ...}
//未触发该事件
void onTrayIconRightMouseUp() {// TODO: implement onTrayIconRightMouseUpsuper.onTrayIconRightMouseUp();if (kDebugMode) {print("onTrayIconMouseUp");}
}//未触发该事件
void onTrayIconMouseUp() {super.onTrayIconMouseUp();if (kDebugMode) {print("onTrayIconMouseUp");}
}
void onTrayIconMouseDown() {if (kDebugMode) {print("onTrayIconMouseDown");}//弹出托盘的菜单栏trayManager.popUpContextMenu();
}
void onTrayIconRightMouseDown() {if (kDebugMode) {print("onTrayIconRightMouseDown");}//弹出托盘的菜单栏trayManager.popUpContextMenu();
}//弹出托盘的菜单栏点击事件
void onTrayMenuItemClick(MenuItem menuItem) {if (kDebugMode) {print("menuItem:${menuItem.key}-${menuItem.label}");}
}
}
2. system_tray
依赖库
system_tray
支持平台
Windows, macOS & Linux
实现步骤
- 在pubspec.yaml中添加依赖
dependencies:...system_tray: ^2.0.3
- 导入依赖
import 'package:system_tray/system_tray.dart';
- 配置系统托盘特性
//创建SystemTray 对象
final SystemTray _systemTray = SystemTray();
Timer? _timer;//配置系统托盘
Future<void> _initSystemTray() async {//设置系统托盘的图标,必须是.ico后缀的图片await _systemTray.initSystemTray(iconPath: Platform.isWindows? 'assets/images/tray_icon_original.ico': 'assets/images/img_1.png',);//设置系统托盘的标题_systemTray.setTitle("system tray");//设置系统托盘的提示_systemTray.setToolTip("How to use system tray with Flutter");//注册系统托盘事件_systemTray.registerSystemTrayEventHandler((eventName) {debugPrint("eventName: $eventName");//注册系统托盘事件:点击事件if (eventName == kSystemTrayEventClick) {//Windows系统:显示主窗口 其他系统弹出托盘菜单弹框Platform.isWindows? windowManager.show(): _systemTray.popUpContextMenu();//注册系统托盘事件:鼠键右键} else if (eventName == kSystemTrayEventRightClick) {//Windows系统:弹出托盘菜单弹框 其他系统: 显示主窗口Platform.isWindows? _systemTray.popUpContextMenu(): windowManager.show();}});//创建托盘的菜单final Menu _menuMain = Menu();await _menuMain.buildFrom([//创建子菜单SubMenu(label: "窗口设置",//创建为菜单子项添加图标,格式必须是bmpimage: Platform.isWindows? 'assets/images/app_icon.bmp': 'assets/images/img_1.png',children: [//创建可选框类型的菜单项MenuItemCheckbox(label: "毛玻璃效果",checked: true,name: 'acrylic_cb',onClicked: (MenuItemBase menuItem) async {//更新MenuItemCheckbox的状态await menuItem.setCheck(!menuItem.checked);if (kDebugMode) {print("毛玻璃效果 onClick ${menuItem.checked}");}if (menuItem.checked == true) {showAcrylic(color);} else {closeAcrylic();}}),MenuItemCheckbox(label: "窗口置顶",checked: true,onClicked: (MenuItemBase menuItem) async {await menuItem.setCheck(!menuItem.checked);if (kDebugMode) {print("窗口置顶 onClick ${menuItem.checked}");}if (menuItem.checked == true) {windowManager.setAlwaysOnTop(true);} else {windowManager.setAlwaysOnTop(false);}}),MenuItemCheckbox(label: "自启动",checked: true,name: 'auto_start_cb',onClicked: (MenuItemBase menuItem) async {// menuItem.checked = !(menuItem.checked == true);await menuItem.setCheck(!menuItem.checked);if (kDebugMode) {print("自启动 onClick ${menuItem.checked}");}}),MenuItemCheckbox(label: "图标闪烁",checked: true,name: "flash_cb",onClicked: (MenuItemBase menuItem) async {MenuItemCheckbox? flashCb =_menuMain.findItemByName<MenuItemCheckbox>("flash_cb");await flashCb?.setCheck(!menuItem.checked);if (kDebugMode) {print("图标闪烁 onClick ${menuItem.checked}");}if (menuItem.checked) {startFlashIcon();} else {stopFlashIcon();}}),]),//菜单分割线MenuSeparator(),//菜单项MenuItemLabel(label: 'Open App',image: Platform.isWindows? 'assets/images/app_icon.bmp': 'assets/images/img_1.png',onClicked: (MenuItemBase menuItem) {windowManager.show();}),MenuItemLabel(label: 'Exit App',image: Platform.isWindows? 'assets/images/app_icon.bmp': 'assets/images/img_1.png',onClicked: (MenuItemBase menuItem) {windowManager.close();}),]);if (kDebugMode) {print("menu:${_menuMain.toString()}");}//为系统托盘设置菜单项await _systemTray.setContextMenu(_menuMain);
}
- 完整代码
import 'package:flutter/material.dart';
import 'package:tray_manager/tray_manager.dart';class HomePage extends StatefulWidget {_HomePageState createState() => _HomePageState();
}class _HomePageState extends State<HomePage> {Timer? _timer;final SystemTray _systemTray = SystemTray();void initState() {super.initState();_initSystemTray();}void dispose() {_timer.cancel();super.dispose();}void _initSystemTray() {// ...}//开始图标闪烁
void startFlashIcon() {if (kDebugMode) {print("startFlashIcon");}var imageList = const ["assets/images/tray_icon_original.ico","assets/images/tray_icon.ico"];var index = 0;_timer =Timer.periodic(const Duration(milliseconds: 500), (Timer timer) async {if (kDebugMode) {print("path:${imageList[index]}");}await _systemTray.setImage(imageList[index]);index = (index == 0) ? 1 : 0;});
}//停止图标闪烁
void stopFlashIcon() async {if (kDebugMode) {print("stopFlashIcon");}_timer?.cancel();_timer = null;await _systemTray.setImage("assets/images/tray_icon_original.ico");
}Widget build(BuildContext context) {// ...}
}
3. 两种方案对比
system_tray 支持菜单项添加图标,tray_manager不支持菜单项添加图标
4. 注意事项
Windows平台系统托盘图标需要是以.ico后缀的图片,菜单项图标需要是.bmp后缀的图片,否则图片无法显示;
5. 话题拓展
- BMP格式(Bitmap):BMP是一种无损的位图图像格式,最初由Microsoft开发。它可以存储图像的像素颜色和位置信息,并支持不同的色彩深度。BMP文件通常较大,因为它们不经过压缩,保留了图像的每个像素的完整信息。BMP格式适用于Windows系统和一些图像编辑软件。
- ICO格式(Icon):ICO是一种用于存储图标的文件格式。ICO文件通常用于表示计算机系统上的各种图标,例如文件夹、应用程序和网站等的图标。ICO文件可以包含多个图标大小和颜色深度的版本,以适应不同的显示需求。ICO文件可以在Windows系统中直接使用,也可以在网页或应用程序中使用。
相关文章:
Flutter桌面应用程序定义系统托盘Tray
文章目录 概念实现方案1. tray_manager依赖库支持平台实现步骤 2. system_tray依赖库支持平台实现步骤 3. 两种方案对比4. 注意事项5. 话题拓展 概念 系统托盘:系统托盘是一种用户界面元素,通常出现在操作系统的任务栏或桌面顶部。它是一个水平的狭长区…...
docker:安装mysql以及最佳实践
文章目录 1、拉取镜像2、运行容器3、进入容器方式一方式二方式三容器进入后连接mysql和在宿主机连接mysql的区别 持久化数据持久化数据最佳实践 1、拉取镜像 docker pull mysql2、运行容器 docker run -d -p 3307:3306 --name mysql-container -e MYSQL_ROOT_PASSWORD123456 …...
uniapp实战 —— 自定义顶部导航栏
效果预览 下图中的红框区域 范例代码 src\pages.json 配置隐藏默认顶部导航栏 "navigationStyle": "custom", // 隐藏默认顶部导航src\pages\index\components\CustomNavbar.vue 封装自定义顶部导航栏的组件(要点在于:获取屏幕边界…...
中国移动频段划分
1、900MHz(Band8)上行:889-904MHz,下行:934-949MHz,带宽共计15MHz,目前部署:2G/NB-IoT/4G 2、1800MHz(Band3)上行:1710-1735MHz,下行…...
《PySpark大数据分析实战》-01.关于数据
📋 博主简介 💖 作者简介:大家好,我是wux_labs。😜 热衷于各种主流技术,热爱数据科学、机器学习、云计算、人工智能。 通过了TiDB数据库专员(PCTA)、TiDB数据库专家(PCTP…...
Qt/C++视频监控拉流显示/各种rtsp/rtmp/http视频流/摄像头采集/视频监控回放/录像存储
一、前言 本视频播放组件陆陆续续写了6年多,一直在持续更新迭代,视频监控行业客户端软件开发首要需求就是拉流显示,比如给定一个rtsp视频流地址,你需要在软件上显示实时画面,其次就是录像保存,再次就是一些…...
Vue.js - 界面设计工具和UI组件库
ViewDesign ViewDesign是一款开源的在线设计工具,它主要提供了一种可视化的界面设计方法,可以帮助设计师和开发人员更高效地完成界面设计和开发工作。 ViewDesign的特点是支持在线协作,可以多人同时进行设计,提高了设计效率&…...
【贪心算法】 Opponents
这道题写伪代码就好了! Description Arya has n opponents in the school. Each day he will fight with all opponents who are present this day. His opponents have some fighting plan that guarantees they will win, but implementing this plan requires pr…...
【git 相关操作】
git status - 查看当前状态 git add - 将文件添加到暂存区 git commit -m "msg" - 提交暂存区文件到本地仓库 git push origin master - 本地仓库文件推送到远程仓库 git merge - 合并分支 git clone - 从指定地址克隆项目 git log - 查看commit日志 git stash push …...
流媒体音视频/安防视频云平台/可视化监控平台EasyCVR无法启动且打印panic报错,是什么原因?
国标GB视频监控管理平台/视频集中存储/云存储EasyCVR能在复杂的网络环境中,将分散的各类视频资源进行统一汇聚、整合、集中管理,实现视频资源的鉴权管理、按需调阅、全网分发、智能分析等。AI智能大数据视频分析EasyCVR平台已经广泛应用在工地、工厂、园…...
H264之NALU结构详解
摘要:本文详细描述了AVC的NALU的码流结构,以及各个层面上NALU详细的构成。 关键字:AVC,NALU 1 NALU简介 NAL层即网络抽象层(Network Abstraction Layer),是为了方便在网络上传输的一种抽象…...
快速整合EasyExcel实现Excel的上传下载
1.EasyExcel 2.Excel的上传(读Excel) 3.Excel的下载(写Excel) 4.结语 1.EasyExcel 首先,这里给出EasyExcel的官方文档:https://easyexcel.opensource.alibaba.com/ alibaba.com不用我多说了吧,大…...
MongoDB的条件操作符
本文主要介绍MongoDB的条件操作符。 目录 MongoDB条件操作符1.比较操作符2.逻辑操作符3.元素操作符4.数组操作符5.文本搜索操作符 MongoDB条件操作符 MongoDB的条件操作符主要分为比较操作符、逻辑操作符、元素操作符、数组操作符、文本搜索操作符等几种类型。 以下是这些操作…...
【Linux】探索Linux进程状态 | 僵尸进程 | 孤儿进程
最近,我发现了一个超级强大的人工智能学习网站。它以通俗易懂的方式呈现复杂的概念,而且内容风趣幽默。我觉得它对大家可能会有所帮助,所以我在此分享。点击这里跳转到网站。 目录 一、进程状态1.1运行状态1.2阻塞状态1.3挂起状态 二、具体L…...
大数据股票简单分析
目录标题 内容说明解题量化金融的含义量化交易策略 点击直接资料领取 内容 1解释量化金融的含义,调研并给出至少 5种量化交易的策略或方法 2.完成Tushare Pro 的安装、注册,获取自己的 Token,查阅网站内的接口讲解和示例; 3通过Python 编程完…...
从零开始搭建链上dex自动化价差套利程序(11)
风险控制 需要将仓位杠杆控制到3倍以内,由于dydx与apex没有获取仓位杠杆的接口,但是每次发送交易的数额可以决定,故而可以设置每次发送总仓位1.5倍杠杆的数额,然后设置一个变量保证每个方向上的交易不超过2次,即可保证…...
2023.12面试题汇总小结
文章目录 Java字节码都包括哪些内容Java双亲委派机制如何打破Java Memory Model是什么synchronized的锁优化是什么CountDownLatch、CyclicBarrier、Semaphore有啥区别,什么场景下使用MySQL MVCC原理MySQL RR隔离级别,会出现幻读吗MySQL的RR隔离级别下&am…...
Linux权限命令详解
Linux权限命令详解 文章目录 Linux权限命令详解一、什么是权限?二、权限的本质三、Linux中的用户四、linux中文件的权限4.1 文件访问者的分类(人)4.2 文件类型和访问权限(事物属性) 五、快速掌握修改权限的做法【第一种…...
【Android】Glide的简单使用(下)
文章目录 缓存设置内存缓存硬盘缓存自定义磁盘缓存行为图片请求优先级缩略图旋转图片Glide的回调:TargetsBaseTargetTarget注意事项设置具体尺寸的Target 调试及Debug获取异常信息 配置第三方网络库自定义缓存 缓存设置 GlideApp .with(context).load(gifUrl).asGif().error(…...
TCP对数据的拆分
应用程序的数据一般都比较大,因此TCP会按照网络包的大小对数据进行拆分。 当发送缓冲区中的数据超过MSS的长度,数据会被以MSS长度为单位进行拆分,拆分出来的数据块被放进单独的网路包中。 根据发送缓冲区中的数据拆分情况,当判断…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
【JVM】Java虚拟机(二)——垃圾回收
目录 一、如何判断对象可以回收 (一)引用计数法 (二)可达性分析算法 二、垃圾回收算法 (一)标记清除 (二)标记整理 (三)复制 (四ÿ…...
宇树科技,改名了!
提到国内具身智能和机器人领域的代表企业,那宇树科技(Unitree)必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...
解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...
Ubuntu Cursor升级成v1.0
0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开,快捷键也不好用,当看到 Cursor 升级后,还是蛮高兴的 1. 下载 Cursor 下载地址:https://www.cursor.com/cn/downloads 点击下载 Linux (x64) ,…...
破解路内监管盲区:免布线低位视频桩重塑停车管理新标准
城市路内停车管理常因行道树遮挡、高位设备盲区等问题,导致车牌识别率低、逃费率高,传统模式在复杂路段束手无策。免布线低位视频桩凭借超低视角部署与智能算法,正成为破局关键。该设备安装于车位侧方0.5-0.7米高度,直接规避树枝遮…...
C++_哈希表
本篇文章是对C学习的哈希表部分的学习分享 相信一定会对你有所帮助~ 那咱们废话不多说,直接开始吧! 一、基础概念 1. 哈希核心思想: 哈希函数的作用:通过此函数建立一个Key与存储位置之间的映射关系。理想目标:实现…...
