Flutter桌面应用程序定义系统托盘Tray

文章目录

  • 概念
  • 实现方案
    • 1. tray_manager
      • 依赖库
      • 支持平台
      • 实现步骤
    • 2. system_tray
      • 依赖库
      • 支持平台
      • 实现步骤
    • 3. 两种方案对比
    • 4. 注意事项
    • 5. 话题拓展

概念

系统托盘:系统托盘是一种用户界面元素,通常出现在操作系统的任务栏或桌面顶部。它是一个水平的狭长区域,用于显示各种图标和通知,以提供快速访问和操作特定应用程序或系统功能。系统托盘通常包含操作系统或第三方应用程序的图标,这些图标可以显示有关应用程序状态、提醒和通知等信息。用户可以通过单击这些图标来打开应用程序的主窗口、执行特定功能或查看详细信息。系统托盘的设计旨在提供一种方便的方式来管理和访问常用的应用程序和系统功能,以提高用户的工作效率。
效果展示
在这里插入图片描述

作为现代操作系统中常见的一个组件,系统托盘能够让用户方便地访问常用的应用程序或者系统功能。对于Flutter桌面应用程序开发者来说,如何在应用程序中定义系统托盘是一个值得探讨的问题。本文将简介系统托盘的概念,并介绍两种可用的Flutter桌面应用程序系统托盘方案。

实现方案

1. tray_manager

依赖库

tray_manager

支持平台

Windows, macOS & Linux

实现步骤

  1. 在pubspec.yaml中添加依赖
dependencies:
  ...
  tray_manager: ^0.2.0
  1. 导入依赖
import 'package:flutter/material.dart' hide MenuItem;
import 'package:tray_manager/tray_manager.dart';
  1. 配置系统托盘特性
Future<void> _init() async {
  //设置系统托盘图标,Windows图标必须文件后缀必须是.ico
  await 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);
}
  1. 监听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 onTrayIconRightMouseUp
  super.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

实现步骤

  1. 在pubspec.yaml中添加依赖
dependencies:
  ...
  system_tray: ^2.0.3
  1. 导入依赖
import 'package:system_tray/system_tray.dart';
  1. 配置系统托盘特性
//创建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: "窗口设置",
        //创建为菜单子项添加图标,格式必须是bmp
        image: 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);
}
  1. 完整代码
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系统中直接使用,也可以在网页或应用程序中使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/221491.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vscode git管理

vscode添加了git管理 1、如下按钮&#xff0c;可以看到本次的修改部分 2、安装git history 就可以查看每次的不同部分了

阿里云环境下的配置DNS和SLB的几种实践示例

一、背景 对于大多中小型公司来说&#xff0c;生产环境大多是购买阿里云或者腾讯云等等&#xff0c;也就存在以下需求&#xff1a; 外网域名内网域名SLB容器化部署 特别是前两项&#xff0c;一定是跳不过的。容器化部署&#xff0c;现在非K8S莫属了。 既然是购买阿里云&…

Codeforces Round 913 (Div. 3)(A~G)

1、编程模拟 2、栈模拟 3、找规律&#xff1f;&#xff08;从终止状态思考&#xff09; 4、二分 5、找规律&#xff0c;数学题 6、贪心&#xff08;思维题&#xff09; 7、基环树 A - Rook 题意&#xff1a; 直接模拟 // Problem: A. Rook // Contest: Codeforces - C…

JSON 语法详解:轻松掌握数据结构(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

人、机不同在于变与多

人擅长变&#xff0c;如变模态、变尺度&#xff0c;而机器侧重多&#xff0c;如多模态、多尺度。 人类擅长变化的能力是由于我们的大脑和思维能力的灵活性所决定的。我们可以通过学习和适应&#xff0c;改变我们的态度、行为方式和观点&#xff0c;以适应不同的情境和环境。例如…

python基于轻量级卷积神经网络模型ShuffleNetv2开发构建辣椒病虫害图像识别系统

轻量级识别模型在我们前面的博文中已经有过很多实践了&#xff0c;感兴趣的话可以自行移步阅读&#xff1a; 《移动端轻量级模型开发谁更胜一筹&#xff0c;efficientnet、mobilenetv2、mobilenetv3、ghostnet、mnasnet、shufflenetv2驾驶危险行为识别模型对比开发测试》 《基…

Python实现FA萤火虫优化算法优化卷积神经网络回归模型(CNN回归算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 萤火虫算法&#xff08;Fire-fly algorithm&#xff0c;FA&#xff09;由剑桥大学Yang于2009年提出 , …

dockerdesktop 制作asp.net core webapi镜像-连接sqlserver数据库容器

1.使用visual studio 创建 asp.net core webapi项目 选择启用docker 会生成Dockerfile文件 2.使用efcore连接数据库&#xff0c;安装efcore的包 <ItemGroup><PackageReference Include"Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version&qu…

MySQL 对null 值的特殊处理

需求 需要将不再有效范围内的所有数据都删除&#xff0c;所以用not in (有效list)去实现&#xff0c;但是发现库里&#xff0c;这一列为null的值并没有删除&#xff0c;突然想到是不是跟 anull 不能生效一样&#xff0c;not in 对null不生效&#xff0c;也需要特殊处理。 解决 …

西安安泰Aigtek——ATA-8152射频功率放大器

ATA-8152射频功率放大器简介 ATA-8152是一款射频功率放大器。其P1dB输出功率100W&#xff0c;饱和输出功率200W。增益数控可调&#xff0c;一键保存设置&#xff0c;提供了方便简洁的操作选择&#xff0c;可与主流的信号发生器配套使用&#xff0c;实现射频信号的放大。宽范围供…

数据结构 | 查漏补缺之

DFS&BFS 哈希表-二次探测再散列法 完全二叉树&深度计算 排序 快速排序-挖坑法 插入、选择、冒泡、区别 插入从第一个元素开始&#xff0c;后面的元素与前面以及排序好的元素比较&#xff0c;插入其中&#xff0c;使其有序&#xff0c;最终效果是前面的有序选择选择…

dockerdesktop推送镜像到dockerhub

1.查看镜像(打开powershell) docker ps2.打tag docker tag pengzx/aspnetcoredocker:v1 pengzx/aspnetcoredocker:v2pengzx/aspnetcoredocker:v1:本地的镜像名加版本号 pengzx/aspnetcoredocker:v2&#xff1a;需要上传的镜像名&#xff08;要以dockerhub的用户名开头/本地镜像…

Hadoop学习笔记(HDP)-Part.14 安装YARN+MR

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

根文件系统的开机自启动测试

一. 简介 本文在之前制作的根文件系统可以正常运行的基础上进行的&#xff0c;继上一篇文章地址如下&#xff1a; 完善根文件系统-CSDN博客 在前面测试软件hello 运行时&#xff0c;都是等 Linux 启动进入根文件系统以后手动输入 “./hello” 命令 来完成的。 我们一般做好产…

Leetcode1423. 可获得的最大点数

Every day a Leetcode 题目来源&#xff1a;1423. 可获得的最大点数 解法1&#xff1a;前缀和 后缀和 基于贪心的思想&#xff0c;要使得获得的点数最大&#xff0c;每次拿卡牌都应该选点数尽量高的卡牌。 但是拿卡牌有限制&#xff0c;每次行动&#xff0c;只可以从行的…

国产智能运维操作系统新选择-浪潮KeyarchOS

1.背景 在CentOS停更&#xff0c;国有企业纷纷摒弃原有的开发与运维工具&#xff0c;全面拥抱国产。我司也顺应号召&#xff0c;更换原有CentOS系统。 在新系统选型上&#xff0c;我司有以下要求&#xff1a; 国产、快速更新迭代、社区活跃&#xff1b;拥有一定知名度&#x…

初级数据结构(一)——顺序表

文中代码源文件已上传&#xff1a;数据结构源码 1、顺序表的特点 1.1、数组 现实中数据记录一般都记录在表格中&#xff0c;如进货单、菜单等&#xff0c;它们的最大特点就是有序。表述中可以用第一项、第二项、第 n 项来描述表格中某个数据或者某串数据。在 C 语言中&#…

uniapp基于u-grid-item九宫格实现uCharts秋云图表展示

uniapp基于uView的UI组件u-grid-item九宫格实现uCharts秋云可视化图表展示 这里使用uView的u-grid-item九宫格组件去显示图标排列 九宫格可以做成多列&#xff0c;移动设备上可以通过左右滑动进行展示 <template><div><div style"text-align: center;font…

报错:Parsed mapper file: ‘file mapper.xml

报错 &#xff1a; Logging initialized using class org.apache.ibatis.logging.stdout.StdOutImpl adapter. Registered plugin: com.github.yulichang.interceptor.MPJInterceptor3b2c8bda Parsed mapper file: file [/Mapper.xml] application无法启动 我这边产生原因是项…

python socket编程7 - 使用PyQt6 开发UI界面新增实现UDP server和client单机通讯的例子

在第五篇中&#xff0c;简单实现了命令行下的 TCP/UDP server和client的单机通讯。 在第六篇中&#xff0c;实现了PyQt6开发界面&#xff0c;TCP协议实现的单机server和client的通讯功能。 这一篇&#xff0c;在第六篇的基础上&#xff0c;增加了UDP server和client的单机通讯功…