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

news2024/11/25 16:41:25

文章目录

  • 概念
  • 实现方案
    • 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系统中直接使用,也可以在网页或应用程序中使用。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

物联网水表和4G水表的区别有哪些?

随着科技的发展&#xff0c;水表也不再是传统的机械表&#xff0c;而是经过数字化和智能化改造的物联网水表和4G水表。这两种水表具有很多的不同点。那么&#xff0c;物联网水表和4G水表的区别有哪些&#xff1f; 首先&#xff0c;物联网水表和4G水表的通信方式不同。物联网水表…

指针进阶知识超详细讲解(C语言)(中)

前言 苦苦等待&#xff0c;终于迎来了我们的指针讲解第二期&#xff0c;如果有还没有看过上一期的小伙伴&#xff0c;那赶紧点击这个链接学习后再来看这篇博客吧。链接&#xff1a;指针基础知识超详细讲解&#xff08;C 语言&#xff09;&#xff08;上&#xff09; 如果对学习…

Leetcode1423. 可获得的最大点数

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

21、pytest参数化中标记单独的测试用例

官方实例 # content of test_expectation_xfail import pytestpytest.mark.parametrize("test_input, expected",[("35",8),("24",6),pytest.param("6*9",42,markspytest.mark.xfail)], ) def test_eval(test_input, expected):asser…

Linux基本指令(2.0)

周边知识&#xff1a; 1.Linux中&#xff0c; 一切皆文件 构建大文件 输入如下shell命令 i1; while [ $i -le 10000]; do echo "hello Linux $i"; let i; done 此时大文件已经创建在big.txt 此时我们发现cat查看无法查看开始内容 我们使用more 当占满一屏之后就不…

JavaScript <md5加密的两种不同输出结果分析>--案例(二点一)

前言: 问题是这样的,在浏览器中看到这段代码 然后在控制台进行输出.得到: 紧接着,就在,js文件里面进行转译: 可是,得到的结果是: 这是问题!!! 正题: 为什么相同的js代码,在 .js 文件中的输出与 Chrome 控制台中的输出不一样? 环境差异&#xff1a;不同的JavaScript环境&…

STM32F1中断NVIC

目录 1. 中断系统 2. 中断向量表 3. NVIC基本结构 4. NVIC优先级分组 5. NVIC程序编写 5.1 中断分组 5.2 中断结构体变量 5.3 中断通道选择 5.4 抢占优先级和响应优先级配置 6. 中断程序执行 1. 中断系统 中断&#xff1a;在主程序运行过程中&#xff0…

Java基础数据类型

Java有八种基础的数据类型&#xff0c;它们被分为两个主要的类别&#xff1a;原始类型和引用类型。原始类型又被分为四类&#xff1a;整型、浮点型、字符型和布尔型。 整型&#xff08;Integral Types&#xff09;&#xff1a; 这些类型用于存储整数。它们包括&#xff1a; ○…

七大经典高效学习方法

金字塔学习模型 金字塔学习是美国学习专家爱德加戴尔1946年提出的。 他将学习分为主动学习和被动学习两种类型&#xff0c;用数字形象地呈现了采用不同学习方式&#xff0c;学习者在两周后还能记住的内容有多少。 被动学习&#xff1a;通过听讲、阅读、视听、演示这些活动&a…

【Windows本地端口占用脚本自动化】

本地启动多个Java微服务&#xff0c;因为停电或者内存回收原因&#xff0c;IDEA直接退出&#xff1b;再次启动各个服务会提示端口占用。 每次都cmd输入命令手动Kill比较繁琐&#xff0c;可以把此脚本放在桌面上作为一个小工具&#xff0c;运行即可。 代码(核心部分是chatGPT自…

高压放大器研究方向及其应用领域

高压放大器是一种电子设备&#xff0c;用于将输入信号的电压增大到较高的输出电压。它在许多领域中有广泛的应用&#xff0c;包括通信、医疗、科学研究等。 高压放大器的研究方向主要集中在以下几个方面&#xff1a; 提高功率效率&#xff1a;高压放大器需要能够提供足够的输出…

信号可靠性剖析

问题 基于信号发送的进程间通信方式可靠吗&#xff1f;&#xff1f;&#xff1f; 信号查看(kill -l) 信号的分类 不可靠信号 (传统信号) 信号值在 [1, 31] 之间的所有信号 可靠信号 (实时信号) 信号值在 [SIGRTMIN&#xff0c;SIGRTMAX]&#xff0c;即&#xff1a;[34&…

数据挖掘 分类模型选择

选择的模型有&#xff1a; 决策树、朴素贝叶斯、K近邻、感知机 调用的头文件有&#xff1a; import numpy as np import pandas as pd from matplotlib import pyplot as plt from sklearn.linear_model import Perceptron from sklearn.naive_bayes import GaussianNB from s…

二极管:TVS瞬态抑制二极管

一、什么是TVS二极管 TVS&#xff08;Transient Voltage Suppressors&#xff09;&#xff0c;即瞬态电压抑制器&#xff0c;又称雪崩击穿二极管。 TVS二极管的符号如下图所示 什么是雪崩击穿 雪崩击穿是有必要了解一下的&#xff0c;不然后面还有齐纳击穿&#xff0c;搞不…

PySpark开发环境搭建常见问题及解决

PySpark环境搭建常见问题及解决 1、winutils.exe问题2、SparkURL问题3、set_ugi()问题 本文主要收录PySpark开发环境搭建时常见的一些问题及解决方案&#xff0c;并收集一些相关资源 1、winutils.exe问题 报错摘要&#xff1a; WARN Shell: Did not find winutils.exe: {} ja…

阿里云Arthas使用——在日志没有输出异常情况下,如何进行线上bug定位 stack命令 和 trace命令

前言 Arthas 是一款线上监控诊断产品&#xff0c;通过全局视角实时查看应用 load、内存、gc、线程的状态信息&#xff0c;并能在不修改应用代码的情况下&#xff0c;对业务问题进行诊断&#xff0c;包括查看方法调用的出入参、异常&#xff0c;监测方法执行耗时&#xff0c;类…

算法学习系列(五):N皇后、数独

目录 引言一、N皇后问题代码实现测试 二、数独问题代码实现测试 引言 这个N皇后问题是很典型的一个递归问题&#xff0c;就是还是要掌握&#xff0c;所谓递归其实就是dfs&#xff0c;一层一层深入下去。数独和N皇后的思路是一样的&#xff0c;只不过一些细节不同而已。 一、N…

【恋上数据结构】前缀树 Tire 学习笔记

Tire 需求分析 如何判断一堆不重复的字符串是否以某个前缀开头&#xff1f; 用 Set\Map 存储字符串&#xff08;不重复&#xff09;遍历所有字符串进行判断缺点&#xff1a;时间复杂度 O(n) 有没有更优的数据结构实现前缀搜索&#xff1f; Tire&#xff08;和 Tree 同音&a…

Linux 和 macOS 的主要区别在哪几个方面呢?

(꒪ꇴ꒪ )&#xff0c;Hello我是祐言QAQ我的博客主页&#xff1a;C/C语言&#xff0c;数据结构&#xff0c;Linux基础&#xff0c;ARM开发板&#xff0c;网络编程等领域UP&#x1f30d;快上&#x1f698;&#xff0c;一起学习&#xff0c;让我们成为一个强大的攻城狮&#xff0…

Hadoop学习笔记(HDP)-Part.08 部署Ambari集群

目录 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 …