日益努力,而后风生水起|

Flutter桌面端开发——系统托盘

注意:查看本文章前请先查看更新日志,以至于知晓该文章是否适合插件的最新版本

更新日志
详情日期
更新了system_tray2.0.2的用法,修正了tray_manager部分用法2022-12-10
更新了tray_manager0.1.5后MenuItem的用法2022-05-10

在我们日常使用的桌面软件中,有大部分都会在系统托盘中占一席地。当然,通过第三方插件,我们使用 Flutter 也能实现这种效果。

image

tray_manager

安装🛠

点击tray_manager获取最新版本。以下是在编写本文章时的最新版本:

tray_manager: ^0.2.0

👻注意:在开发 Linux 端的程序时,还需要额外的操作,具体可以查看这里

使用🥩

先实例化 TrayManager 对象:

final TrayManager _trayManager = TrayManager.instance;

设置图标🐱‍💻

在系统托盘显示,当然是先设置个图标了。

实例化 TrayManager 后,我们可以使用 setIcon来设置图标。

在这里,我们需要准备两种格式的图片:.png 和 .ico。png格式的图片用来在其他平台的托盘区显示,ico 用在 windows 平台显示。

final String _iconPathWin = 'assets/images/tray_manager.ico';
final String _iconPathOther = 'assets/images/tray_manager.png';

要是我们在 windows 平台用 png 格式显示会怎么样🤔?

await _trayManager.setIcon(_iconPathOther);

image

😲虽然占了位置,但是完全不会显示呢!我们还是改为正确的格式:

await _trayManager.setIcon(Platform.isWindows ? _iconPathWin : _iconPathOther);

image

这样就能正确显示我们设置好的图标了😀

既然这样,那我们就可以模拟QQ和微信来消息时的闪烁效果了!

先准备好两个不同格式的完全透明的图片。

final String _iconNullWin = 'assets/images/null.ico';
final String _iconNullOther = 'assets/images/null.png';

要想知道是否已经设置了图标,还需要一个布尔值判断:

bool _hasIcon = false;

更改一下设置图标的代码:

void _generateIcon() async {
  await _trayManager.setIcon(Platform.isWindows ? _iconPathWin : _iconPathOther);
  _hasIcon = true;
}

现在来写一个闪烁图片的方法,每过300毫秒换一次图片,这里需要用到 Timer 对象:

Timer? _timer;

别忘了在 dispose 方法中取消:

@override
void dispose() {
  // TODO: implement dispose
  _timer?.cancel();
  super.dispose();
}
void _iconFlash() {
  _timer = Timer.periodic(const Duration(milliseconds: 300), (timer) async {
    if (_hasIcon) {
      await _trayManager.setIcon(Platform.isWindows ? _iconNullWin : _iconNullOther);
    } else {
      await _trayManager.setIcon(Platform.isWindows ? _iconPathWin : _iconPathOther);
    }
    _hasIcon = !_hasIcon;
    setState(() {});
  });
}

有了开启闪烁动画的方法,还需要一个关闭闪烁效果的方法:

void _closeIconFlash() {
  _timer?.cancel();
  _generateIcon();
}

一切准备妥当,让我们来看看效果。

image

完美😎

设置提示信息🐾

当我们把鼠标移到托盘区的软件图标上,一般会有一个提示信息。这里我们也可以设置:

void _generateToolTip() async {
  await _trayManager.setToolTip('你想干嘛😒');
}

image

设置菜单项🕸

托盘区图标是有了,但是除了图标好像什么也没有,这不是纯粹恶心人吗?站着茅坑不拉💩。现在,我们就来帮它一把。

我们可以使用 setContextMenu方法来设置菜单,该方法需要传递一个 MenuItem 的数组对象,接下来我们就扒扒这个对象到底要传入些什么。MenuItem 中一共有以下多个参数可以传递:

  • String? key:组件唯一标识
  • String type:菜单项的类型,主要有normal、checkbox、separator、submenu几个值。默认为normal
  • String? label:菜单项显示的文本
  • String? sublabel:菜单项显示的二级文本
  • String? toolTip:菜单项的提示
  • String? icon:菜单项的图标(猜测,目前该插件版本还有待更新,文档并没有对该值进行描述)
  • bool? checked:是否被选择。仅在type为checkbox时生效
  • bool disabled = false:是否禁用选项。默认为false
  • Menu? submenu:子菜单项
  • void Function(MenuItem)? onClick:菜单项被点击后的方法

好了,摸清了 MenuItem 的家长里短,我们现在就来使用它:

Menu _menu = Menu(items: [
  MenuItem(label: '语文'),
  MenuItem(label: '数学', toolTip: '躲不掉的'),
  MenuItem.checkbox(
    label: '英语',
    checked: true,
    onClick: (menuItem) {
      menuItem.checked = !(menuItem.checked == true);
    },
  ),
  MenuItem.separator(),
  MenuItem.submenu(
    key: 'science',
    label: '理科',
    submenu: Menu(items: [
      MenuItem(label: '物理'),
      MenuItem(label: '化学'),
      MenuItem(label: '生物'),
    ]),
  ),
  MenuItem.separator(),
  MenuItem.submenu(
    key: 'arts',
    label: '文科',
    submenu: Menu(items: [
      MenuItem(label: '政治'),
      MenuItem(label: '历史'),
      MenuItem(label: '地理'),
    ]),
  ),
]);

把它们装填上去:

await trayManager.setContextMenu(_menu);

image

嗯😶怎么没用???看看了控制台,也没有错误。

其实菜单已经被我们设置好了,只是还没有写让它显示出来的方法。

注册鼠标事件🐹

一般什么情况下我们会让它显示出来呢🤔?鼠标右键单击图标时!要想监听鼠标右键单击图标的事件,我们先修改一下代码:

class _UseTrayManagerPageState extends State<UseTrayManagerPage> with TrayListener {
  
  @override
  void initState() {
    super.initState();
    _trayManager.addListener(this);
  }

  @override
  void dispose() {
    _timer?.cancel();
    _trayManager.removeListener(this);
    super.dispose();
  }
  
  ...
}

通过 with 关键字混入 TrayListener 类,我们就可以对图标进行更多的事件操作了。

  • onTrayMenuItemClick:菜单选项点击事件
  • onTrayIconRightMouseUp:按理说右键单击后抬起事件,没试出来😕
  • onTrayIconRightMouseDown:图标右键单击事件
  • onTrayIconMouseUp:按理说左键单击后抬起事件,没试出来😕
  • onTrayIconMouseDown:图标左键单击事件

设置右键单击事件:

@override
void onTrayIconRightMouseDown() async {
  await _trayManager.popUpContextMenu();
}

设置菜单选项点击事件:

@override
void onTrayMenuItemClick(MenuItem menuItem) {
  BotToast.showText(text: '你选择了${menuItem.title}');
}

设置图标鼠标左键按下事件(显示程序):

@override
void onTrayIconMouseDown() {
  windowManager.show(); // 该方法来自window_manager插件
}

image

获取图标所在位置🐣

该方法将会返回图标在屏幕中的位置(Rect对象)

await _trayManager.getBounds();

移除托盘区程序🦝

除了关闭程序将程序从托盘区移除以外,我们也可以主动移除:

await _trayManager.destroy();

image

system_tray

安装🛠

点击system_tray获取最新版本。以下是在编写本文章时的最新版本:

system_tray: ^2.0.2

👻注意:在开发 Linux 端的程序时,还需要安装以下内容:

sudo apt-get install appindicator3-0.1 libappindicator3-dev

使用🥩

我们先准备好要使用的常量资源:

final String _title = '第一个Desktop应用';
final String _toolTip = '看什么看';
final String _iconPathWin = 'assets/images/system_tray.ico';
final String _iconPathOther = 'assets/images/system_tray.png';

然后实例化一个SystemTray对象:

final SystemTray _systemTray = SystemTray();

初始化🐱‍💻

有了SystemTray对象,我们就要使用它的initSystemTray方法来进行初始化。该方法有3个参数:

  • required String iconPath:程序在托盘显示的图标
  • String? title:程序名称(仅macos)
  • String? toolTip:鼠标移动到托盘图标时显示的提示信息(仅win、macos)
void _initTray() async {
  String _iconPath = Platform.isWindows ? _iconPathWin : _iconPathOther;
  await _systemTray.initSystemTray(title: _title, iconPath: _iconPath, toolTip: _toolTip);
}

image

这些内容都在初始化时设置了,那我们后期要修改怎么办?别着急,可以使用setSystemTrayInfo,参数和initSystemTray一样:

先准备好要换的内容:

final String _newToolTip = '怎么肥四啊';
final String _anotherIconWin = 'assets/images/another.ico';
final String _anotherIconOther = 'assets/images/another.png';

开始动手:

void _modifySystemTrayInfo() {
  String iconPath = Platform.isWindows ? _anotherIconWin : _anotherIconOther;
  _systemTray.setSystemTrayInfo(iconPath: iconPath, toolTip: _newToolTip);
}

image

设置菜单项🕸

初始化Menu对象:

final Menu _menu = Menu();

设置菜单项需要使用buildFrom方法:

await _menu.buildFrom([
  ...
]);

该方法中传入一个MenuItem对象数组。该插件中一共有以下几种菜单项:

  • MenuItemLabel:文本菜单
    • required String label:显示文本标签
    • String? image:选项前的图片
    • String? name:用来区分的名称
    • bool enabled:是否启用,默认为true
    • void Function(MenuItem)? onClicked:菜单被点击事件,返回一个MenuItem对象,可以获取设置image、name等属性
  • MenuItemCheckbox:复选框菜单
    • required String label:显示文本标签
    • String? image:选项前的图片
    • String? name:用来区分的名称
    • bool enabled:是否启用,默认为true
    • bool checked:是否选中,默认为false
    • void Function(MenuItem)? onClicked:菜单被点击事件,返回一个MenuItem对象,可以获取设置image、name等属性
  • SubMenu:二级菜单
    • required String label:显示文本标签
    • required List<MenuItem> children:子菜单列表
    • String? image:选项前的图片
  • MenuSeparator:菜单分隔符
void _setMenu() async {
  await _menu.buildFrom([
    MenuItemLabel(label: '五楼'),
    MenuItemLabel(label: '四楼'),
    SubMenu(label: '三楼', children: []),
    MenuItemCheckbox(label: '二楼'),
    MenuItemCheckbox(label: '一楼'),
    MenuSeparator(),
    MenuItemLabel(label: '地下室'),
  ]);
  await _systemTray.setContextMenu(_menu);
}

当然,和 tray_manager 一样,这样只设置了,但是没有让它什么时候显示。所以我们需要一个鼠标右键图标的事件。

注册鼠标事件🐹

注册事件需要用到registerSystemTrayEventHandler方法,注册好事件后一共有以下几种情况(Linux不适用):

  • kSystemTrayEventClick:鼠标左击
  • kSystemTrayEventRightClick:鼠标右击
  • kSystemTrayEventDoubleClick:鼠标双击(仅win)

需要注意的是,该插件中有一个AppWindow对象,可以用来控制Windows端的窗口显示、隐藏和关闭:

final AppWindow _appWindow = AppWindow();
void _setMenu() async {
  ...
  _systemTray.registerSystemTrayEventHandler((eventName) {
    if (eventName == kSystemTrayEventClick) {
      Platform.isWindows ? _appWindow.show() : _systemTray.popUpContextMenu();
    } else if (eventName == kSystemTrayEventRightClick) {
      Platform.isWindows ? _systemTray.popUpContextMenu() : _appWindow.show();
    } else if (eventName == kSystemTrayEventDoubleClick) {
      BotToast.showText(text: '点一次就够了!');
    }
  });
}

image

菜单项类型

我们可以通过上文得知,菜单项有不同样式和属性,我们修改代码如下:

await _menu.buildFrom([
  MenuItemLabel(label: '五楼'),
  MenuItemLabel(label: '四楼', image: getImagePath('another')),
  SubMenu(
    label: '三楼',
    children: [MenuItemLabel(label: '教室'), MenuItemLabel(label: '自习室')],
  ),
  MenuItemCheckbox(label: '二楼', checked: true),
  MenuItemCheckbox(label: '一楼'),
  MenuSeparator(),
  MenuItemLabel(label: '地下室'),
]);

image

注意:要想显示图片必须使用bmp格式,且最好制作bmp格式的软件为Windows自带的画图软件(不是3D画图)。win10及以上系统可以通过搜索打开该软件。(不要搜索那些在线转换bmp格式的网站,我认为它们只是单纯改了后缀名,害得我在这被坑了很久🤕)。

在菜单选项的onClicked方法中,会返回一个MenuItem对象,我们可以通过该对象对菜单项进行修改:

  • setCheck:是否选中
  • setEnable:是否启用
  • setImage:设置选项前的图片
  • setLabel:设置标签

这里只讲setCheck,剩余的几个很容易。设置是否选中有以下两种方法:

// 方法一
MenuItemCheckbox(
  label: '一楼',
  onClicked: (menuItem) async {
    await menuItem.setCheck(!menuItem.checked);
  },
),

// 方法二
MenuItemCheckbox(
  label: '二楼',
  name: 'second',
  checked: true,
  onClicked: (menuItem) async {
    MenuItemCheckbox? second = _menu.findItemByName<MenuItemCheckbox>('second');
    await second?.setCheck(!second.checked);
  },
),

image

根据方法二,我们也可以设置其他菜单项的属性。还有托盘区图标闪烁(使用该插件可以直接将图标设置为"",而不需要另外一张空白图片)、最小化显示应用移除图标(使用await _systemTray.destroy();)的功能这里就不演示了,可以参考tray_manager的使用方法。

🛫OK,以上就是这篇文章的全部内容,仅针对插件的当前版本,并不能保证适用于以后插件用法的更新迭代。

最后,感谢 lijy91antler119 对以上插件的维护和开发😁。本应用代码已上传至 githubgitee,有需要的可以下载下来查看学习。

本文作者:菠萝橙子丶

本文链接:https://www.cnblogs.com/ilgnefz/p/16003313.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   菠萝橙子丶  阅读(1957)  评论(5编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起
  1. 1 Shining For One Thing 赵贝尔
Shining For One Thing - 赵贝尔
00:00 / 00:00
An audio error has occurred.

Shining For One Thing (《一闪一闪亮星星》影视剧歌曲) - 赵贝尔

词:萨吉

曲:金大洲

编曲:金大洲D-Jin

制作人:金大洲D-Jin

吉他/Bass:D-Jin

合声编写/合声:赵贝尔

人声录音/编辑:张德龙@D-Jin Music Studio

混音/母带处理:George Dum

宣传推广:杨慧颖/杨佩

封面设计:HOO

OP/音乐制作出品:D-Jin Music(北京翊辰文化传媒有限公司)

(未经著作权人许可,不得翻唱、翻录或使用)

夏夜的花火

夏夜的花火

因为你在身边而深刻

因为你在身边而深刻

幸运的是我

在宇宙之间听见承诺

在宇宙之间听见承诺

嗨 是我

这一次别再错过

这一次别再错过

喜欢你该由我主动了

喜欢你该由我主动了

星星那么多

星星那么多

有数不尽的浪漫闪烁

注定这一颗

会让你刻在手臂左侧

属于我

星形心率的贴合

幸有你总在守护我

幸有你总在守护我

I fall in love

I fall in love

I see your love

遇见你才发现我在

等你到来

等你到来

Fallen star

The wonder of you

我会永远在你天空

我会永远在你天空

为你闪烁 my love

为你闪烁 my love

Shining for one thing

Shining for one thing

Shining for one thing

Shining for one thing

It's you

It's you

星星那么多

星星那么多

有数不尽的浪漫闪烁

注定这一颗

会让你刻在手臂左侧

属于我

星形心率的贴合

幸有你总在守护我

幸有你总在守护我

I fall in love

I fall in love

I see your love

遇见你才发现我在

等你到来

等你到来

Fallen star

The wonder of you

我会永远在你天空

我会永远在你天空

为你闪烁 my love

为你闪烁 my love

Shining for one thing

Shining for one thing

Shining for one thing

Shining for one thing

It's you

It's you