Flutter系统托盘交互插件system_tray的使用
Flutter系统托盘交互插件system_tray的使用
system_tray 插件简介
system_tray
是一个用于Flutter桌面应用的插件,支持Windows、macOS和Linux平台的系统托盘菜单功能。
安装
在 pubspec.yaml
文件中添加依赖:
dependencies:
...
system_tray: ^2.0.3
在代码文件中导入:
import 'package:system_tray/system_tray.dart';
Linux平台前置条件
对于Linux平台,需要安装以下依赖项:
sudo apt-get install appindicator3-0.1 libappindicator3-dev
或针对Ubuntu 22.04及以上版本:
sudo apt-get install libayatana-appindicator3-dev
示例应用
初始化系统托盘
以下是初始化系统托盘并设置上下文菜单的完整示例:
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:flutter/material.dart';
import 'package:system_tray/system_tray.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
doWhenWindowReady(() {
final win = appWindow;
const initialSize = Size(600, 450);
win.minSize = initialSize;
win.size = initialSize;
win.alignment = Alignment.center;
win.title = "How to use system tray with Flutter";
win.show();
});
}
String getTrayImagePath(String imageName) {
return Platform.isWindows ? 'assets/$imageName.ico' : 'assets/$imageName.png';
}
String getImagePath(String imageName) {
return Platform.isWindows ? 'assets/$imageName.bmp' : 'assets/$imageName.png';
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final AppWindow _appWindow = AppWindow();
final SystemTray _systemTray = SystemTray();
final Menu _menuMain = Menu();
final Menu _menuSimple = Menu();
Timer? _timer;
bool _toogleTrayIcon = true;
bool _toogleMenu = true;
@override
void initState() {
super.initState();
initSystemTray();
}
@override
void dispose() {
super.dispose();
_timer?.cancel();
}
Future<void> initSystemTray() async {
List<String> iconList = ['darts_icon', 'gift_icon'];
// 初始化系统托盘
await _systemTray.initSystemTray(iconPath: getTrayImagePath('app_icon'));
_systemTray.setTitle("system tray");
_systemTray.setToolTip("How to use system tray with Flutter");
// 处理系统托盘事件
_systemTray.registerSystemTrayEventHandler((eventName) {
debugPrint("eventName: $eventName");
if (eventName == kSystemTrayEventClick) {
Platform.isWindows ? _appWindow.show() : _systemTray.popUpContextMenu();
} else if (eventName == kSystemTrayEventRightClick) {
Platform.isWindows ? _systemTray.popUpContextMenu() : _appWindow.show();
}
});
// 构建主菜单
await _menuMain.buildFrom([
MenuItemLabel(
label: 'Change Context Menu',
image: getImagePath('darts_icon'),
onClicked: (menuItem) {
debugPrint("Change Context Menu");
_toogleMenu = !_toogleMenu;
_systemTray.setContextMenu(_toogleMenu ? _menuMain : _menuSimple);
},
),
MenuSeparator(),
MenuItemLabel(
label: 'Show',
image: getImagePath('darts_icon'),
onClicked: (menuItem) => _appWindow.show()),
MenuItemLabel(
label: 'Hide',
image: getImagePath('darts_icon'),
onClicked: (menuItem) => _appWindow.hide()),
MenuItemLabel(
label: 'Start flash tray icon',
image: getImagePath('darts_icon'),
onClicked: (menuItem) {
debugPrint("Start flash tray icon");
_timer ??= Timer.periodic(
const Duration(milliseconds: 500),
(timer) {
_toogleTrayIcon = !_toogleTrayIcon;
_systemTray.setImage(
_toogleTrayIcon ? "" : getTrayImagePath('app_icon'));
},
);
},
),
MenuItemLabel(
label: 'Stop flash tray icon',
image: getImagePath('darts_icon'),
onClicked: (menuItem) {
debugPrint("Stop flash tray icon");
_timer?.cancel();
_timer = null;
_systemTray.setImage(getTrayImagePath('app_icon'));
},
),
MenuSeparator(),
SubMenu(
label: "Test API",
image: getImagePath('gift_icon'),
children: [
SubMenu(
label: "setSystemTrayInfo",
image: getImagePath('darts_icon'),
children: [
MenuItemLabel(
label: 'setTitle',
image: getImagePath('darts_icon'),
onClicked: (menuItem) {
final String text = WordPair.random().asPascalCase;
debugPrint("click 'setTitle' : $text");
_systemTray.setTitle(text);
},
),
MenuItemLabel(
label: 'setImage',
image: getImagePath('gift_icon'),
onClicked: (menuItem) {
String iconName = iconList[Random().nextInt(iconList.length)];
String path = getTrayImagePath(iconName);
debugPrint("click 'setImage' : $path");
_systemTray.setImage(path);
},
),
MenuItemLabel(
label: 'setToolTip',
image: getImagePath('darts_icon'),
onClicked: (menuItem) {
final String text = WordPair.random().asPascalCase;
debugPrint("click 'setToolTip' : $text");
_systemTray.setToolTip(text);
},
),
MenuItemLabel(
label: 'getTitle',
image: getImagePath('gift_icon'),
onClicked: (menuItem) async {
String title = await _systemTray.getTitle();
debugPrint("click 'getTitle' : $title");
},
),
],
),
MenuItemLabel(
label: 'disabled Item',
name: 'disableItem',
image: getImagePath('gift_icon'),
enabled: false),
],
),
MenuSeparator(),
MenuItemLabel(
label: 'Set Item Image',
onClicked: (menuItem) async {
debugPrint("click 'SetItemImage'");
String iconName = iconList[Random().nextInt(iconList.length)];
String path = getImagePath(iconName);
await menuItem.setImage(path);
debugPrint(
"click name: ${menuItem.name} menuItemId: ${menuItem.menuItemId} label: ${menuItem.label} image: ${menuItem.image}");
},
),
MenuItemCheckbox(
label: 'Checkbox 1',
name: 'checkbox1',
checked: true,
onClicked: (menuItem) async {
debugPrint("click 'Checkbox 1'");
MenuItemCheckbox? checkbox1 =
_menuMain.findItemByName<MenuItemCheckbox>("checkbox1");
await checkbox1?.setCheck(!checkbox1.checked);
MenuItemCheckbox? checkbox2 =
_menuMain.findItemByName<MenuItemCheckbox>("checkbox2");
await checkbox2?.setEnable(checkbox1?.checked ?? true);
debugPrint(
"click name: ${checkbox1?.name} menuItemId: ${checkbox1?.menuItemId} label: ${checkbox1?.label} checked: ${checkbox1?.checked}");
},
),
MenuItemCheckbox(
label: 'Checkbox 2',
name: 'checkbox2',
onClicked: (menuItem) async {
debugPrint("click 'Checkbox 2'");
await menuItem.setCheck(!menuItem.checked);
await menuItem.setLabel(WordPair.random().asPascalCase);
debugPrint(
"click name: ${menuItem.name} menuItemId: ${menuItem.menuItemId} label: ${menuItem.label} checked: ${menuItem.checked}");
},
),
MenuItemCheckbox(
label: 'Checkbox 3',
name: 'checkbox3',
checked: true,
onClicked: (menuItem) async {
debugPrint("click 'Checkbox 3'");
await menuItem.setCheck(!menuItem.checked);
debugPrint(
"click name: ${menuItem.name} menuItemId: ${menuItem.menuItemId} label: ${menuItem.label} checked: ${menuItem.checked}");
},
),
MenuSeparator(),
MenuItemLabel(
label: 'Exit', onClicked: (menuItem) => _appWindow.close()),
]);
// 构建简化菜单
await _menuSimple.buildFrom([
MenuItemLabel(
label: 'Change Context Menu',
image: getImagePath('app_icon'),
onClicked: (menuItem) {
debugPrint("Change Context Menu");
_toogleMenu = !_toogleMenu;
_systemTray.setContextMenu(_toogleMenu ? _menuMain : _menuSimple);
},
),
MenuSeparator(),
MenuItemLabel(
label: 'Show',
image: getImagePath('app_icon'),
onClicked: (menuItem) => _appWindow.show()),
MenuItemLabel(
label: 'Hide',
image: getImagePath('app_icon'),
onClicked: (menuItem) => _appWindow.hide()),
MenuItemLabel(
label: 'Exit',
image: getImagePath('app_icon'),
onClicked: (menuItem) => _appWindow.close(),
),
]);
_systemTray.setContextMenu(_menuMain);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: WindowBorder(
color: const Color(0xFF805306),
width: 1,
child: Column(
children: [
const TitleBar(),
ContentBody(
systemTray: _systemTray,
menu: _menuMain,
),
],
),
),
),
);
}
}
const backgroundStartColor = Color(0xFFFFD500);
const backgroundEndColor = Color(0xFFF6A00C);
class TitleBar extends StatelessWidget {
const TitleBar({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return WindowTitleBarBox(
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [backgroundStartColor, backgroundEndColor],
stops: [0.0, 1.0]),
),
child: Row(
children: [
Expanded(
child: MoveWindow(),
),
const WindowButtons()
],
),
),
);
}
}
class ContentBody extends StatelessWidget {
final SystemTray systemTray;
final Menu menu;
const ContentBody({
Key? key,
required this.systemTray,
required this.menu,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Expanded(
child: Container(
color: const Color(0xFFFFFFFF),
child: ListView(
padding: const EdgeInsets.symmetric(vertical: 4.0),
children: [
Card(
elevation: 2.0,
margin: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 8.0,
),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'systemTray.initSystemTray',
style: TextStyle(
fontWeight: FontWeight.w600,
),
),
const Text(
'Create system tray.',
),
const SizedBox(
height: 12.0,
),
ElevatedButton(
child: const Text("initSystemTray"),
onPressed: () async {
if (await systemTray.initSystemTray(
iconPath: getTrayImagePath('app_icon'))) {
systemTray.setTitle("new system tray");
systemTray.setToolTip(
"How to use system tray with Flutter");
systemTray.setContextMenu(menu);
}
},
),
],
),
),
),
Card(
elevation: 2.0,
margin: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 8.0,
),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'systemTray.destroy',
style: TextStyle(
fontWeight: FontWeight.w600,
),
),
const Text(
'Destroy system tray.',
),
const SizedBox(
height: 12.0,
),
ElevatedButton(
child: const Text("destroy"),
onPressed: () async {
await systemTray.destroy();
},
),
],
),
),
),
],
),
),
);
}
}
final buttonColors = WindowButtonColors(
iconNormal: const Color(0xFF805306),
mouseOver: const Color(0xFFF6A00C),
mouseDown: const Color(0xFF805306),
iconMouseOver: const Color(0xFF805306),
iconMouseDown: const Color(0xFFFFD500));
final closeButtonColors = WindowButtonColors(
mouseOver: const Color(0xFFD32F2F),
mouseDown: const Color(0xFFB71C1C),
iconNormal: const Color(0xFF805306),
iconMouseOver: Colors.white);
class WindowButtons extends StatelessWidget {
const WindowButtons({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: [
MinimizeWindowButton(colors: buttonColors),
MaximizeWindowButton(colors: buttonColors),
CloseWindowButton(
colors: closeButtonColors,
onPressed: () {
showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Exit Program?'),
content: const Text(
('The window will be hidden, to exit the program you can use the system menu.')),
actions: <Widget>[
TextButton(
child: const Text('OK'),
onPressed: () {
Navigator.of(context).pop();
appWindow.hide();
},
),
],
);
},
);
},
),
],
);
}
}
额外资源
推荐使用以下库来控制窗口:
常见问题解答 (Q&A)
- 问:如果遇到以下编译错误
答:添加Undefined symbols for architecture x86_64: "___gxx_personality_v0", referenced from: ...
libc++.tbd
1. 打开 example/macos/Runner.xcodeproj 2. 添加 'libc++.tbd' 到 TARGET runner 'Link Binary With Libraries'
通过以上内容,您可以快速上手使用 system_tray
插件为您的Flutter桌面应用程序添加系统托盘功能。希望这些信息对您有所帮助!
更多关于Flutter系统托盘交互插件system_tray的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter系统托盘交互插件system_tray的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何在Flutter中使用system_tray
插件来实现系统托盘交互的代码示例。这个插件允许你在桌面平台(如Windows、Linux和macOS)上与系统托盘进行交互,比如显示图标、菜单项以及处理托盘点击事件。
首先,确保你已经在你的Flutter项目中添加了system_tray
依赖。你可以在你的pubspec.yaml
文件中添加以下依赖:
dependencies:
flutter:
sdk: flutter
system_tray: ^x.y.z # 请替换为最新版本号
然后运行flutter pub get
来安装依赖。
接下来,我们编写一个示例应用,展示如何使用system_tray
插件。
main.dart
import 'package:flutter/material.dart';
import 'package:system_tray/system_tray.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'System Tray Example',
home: Scaffold(
appBar: AppBar(
title: Text('System Tray Example'),
),
body: Center(
child: Text('Check the system tray for interactions!'),
),
),
);
}
}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
late SystemTray? _systemTray;
late bool _isTrayIconSet = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance!.addObserver(this);
_initSystemTray();
}
@override
void dispose() {
WidgetsBinding.instance!.removeObserver(this);
_systemTray?.destroy();
super.dispose();
}
void _initSystemTray() async {
if (!kIsDesktop) {
return;
}
_systemTray = await SystemTray.instance;
// 设置托盘图标
final iconPath = 'assets/icon.png'; // 请确保你有这个图标文件
_systemTray?.setIconFromPath(iconPath);
// 设置托盘菜单项
_systemTray?.setContextMenu([
SystemTrayMenuItem(
title: 'Item 1',
onClick: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Item 1 clicked')),
);
},
),
SystemTrayMenuItem(
title: 'Quit',
onClick: () {
exit(0);
},
),
]);
// 处理托盘图标点击事件
_systemTray?.onTrayIconClicked.listen((_) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Tray icon clicked')),
);
});
setState(() {
_isTrayIconSet = true;
});
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.detached && _isTrayIconSet) {
// 当应用进入后台时,可以添加一些清理代码
// 例如:_systemTray?.destroy();
}
}
}
注意事项
-
图标文件:确保在
assets
文件夹中有一个名为icon.png
的图标文件,并且已经在pubspec.yaml
中声明了资源文件。flutter: assets: - assets/icon.png
-
平台支持:这个插件目前只支持桌面平台(Windows、Linux、macOS)。在移动平台上运行这段代码将不会有任何效果。
-
权限:在某些平台上(特别是Linux和macOS),你可能需要额外的权限或配置来显示系统托盘图标。确保按照插件的文档进行必要的配置。
这个示例展示了如何使用system_tray
插件来设置托盘图标、菜单项以及处理托盘点击事件。你可以根据自己的需求进一步扩展和定制这些功能。