flutter desktop tray 桌面端实现可爱的小太阳系统托盘
flutter desktop tray 桌面端实现可爱的小太阳系统托盘
系统托盘功能对于桌面程序是很重要的,我们可以在系统托盘对程序进行简单控制,也可以在托盘向用户展示一些信息。我们这次基于system_tray
插件实现windows系统下的可爱小太阳系统托盘功能,并使它像QQ来新消息时那样进行图标闪烁。效果图如下所示:
system_tray插件简介
system_tray
支持在Windows、macOS 和 Linux 上实现系统托盘功能,与之相似的还有一个tray_manager
插件,但是这个最近更新时间比较久远,所以我们选择system_tray插件进行实现。下边是官方展示的例子:
开始使用system_tray
首先实例化一个SystemTray对象:final SystemTray systemTray = SystemTray();
,为了方便后边使用,我们直接定义全局变量,后边都只针对这个对象进行操作。
接着我们定义initSystemTray
函数,在这个函数中进行托盘功能的设置。 由于windwos与linux支持的图标格式不同,我们要根据操作系统类型选择对应的图标String path = Platform.isWindows ? 'assets/sun.ico' : 'assets/app_icon.png';
,如果操作系统是windwos的话,就选icon格式图标,其他操作系统的情况就使用png格式就可以了。
system_tray初始化
上边实例化的SystemTray对象,接着我们就要使用它的initSystemTray方法来进行初始化。传入的参数分别是图标路径,macos下显示的title,以及鼠标悬浮时的提示文字。
required String iconPath:托盘显示的图标路径
String? title:名称(仅macos)
String? toolTip:鼠标悬停到托盘图标时的提示信息(仅win、macos)
// We first init the systray menu
await systemTray.initSystemTray(
title: "system tray",
toolTip: "system tray toolTip",
iconPath: path,
);
这样就可以实现一个简单的系统托盘图标啦:
右键菜单栏
只显示图标肯定是不够的,我们还可以实现右键点击图标展示菜单栏,实现一些简单的功能交互。实现也是实例化菜单对象final Menu _menu = Menu();
,接着使用菜单对象的buildFrom方法进行设置。
await _menu.buildFrom([
...
]);
buildFrom中放置的是以下几种菜单项:
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:菜单分隔符
我们实现两级的菜单,功能包括设置标题、设置提示文字,闪烁图标、复选框菜单、显示/隐藏窗口等功能:
// create context menu
final Menu menu = Menu();
await menu.buildFrom([
SubMenu(
label: "submenu",
children: [
MenuItemLabel(
label: 'setTitle',
onClicked: (menuItem) {
systemTray.setTitle("setTitle");
},
),
。。。
MenuItemLabel(
label: 'Start flash tray icon',
image: "assets/icons8-sun-30.BMP",
onClicked: (menuItem) {
debugPrint("Start flash tray icon");
_timer ??= Timer.periodic(
const Duration(milliseconds: 500),
(timer) {
_toogleTrayIcon = !_toogleTrayIcon;
systemTray.setImage(_toogleTrayIcon ? "" : "assets/sun.ico");
},
);
},
),
],
),
MenuItemCheckbox(
label: "MenuItemCheckbox",
name: "MenuItemCheckbox name",
checked: true,
onClicked: (menuItem) async {
await menuItem.setCheck(!menuItem.checked);
},
),
MenuSeparator(),
。。。
MenuItemLabel(
label: 'Exit',
onClicked: (menuItem) => appWindow.close(),
),
]);
别忘了使用await systemTray.setContextMenu(menu);
函数使之生效。效果图如下:
右键菜单栏实现图标闪烁功能
我们在右键菜单中实现一个点击开始/停止图标闪烁的功能,开始菜单中的onClicked
函数中添加一个计时器,每隔一段时间触发systemTray.setImage
函数重新设置托盘图标路径,路径在空白和正确的icons路径之间切换,实现图标闪烁的效果。
MenuItemLabel(
label: 'Stop flash tray icon',
image: "assets/sun.ico",
onClicked: (menuItem) {
debugPrint("Stop flash tray icon");
_timer?.cancel();
_timer = null;
systemTray.setImage("assets/sun.ico");
},
),
MenuItemLabel(
label: 'Start flash tray icon',
image: "assets/icons8-sun-30.BMP",
onClicked: (menuItem) {
debugPrint("Start flash tray icon");
_timer ??= Timer.periodic(
const Duration(milliseconds: 500),
(timer) {
_toogleTrayIcon = !_toogleTrayIcon;
systemTray.setImage(_toogleTrayIcon ? "" : "assets/sun.ico");
},
);
},
),
效果图:
窗口操作和鼠标事件监控
实例化final AppWindow appWindow = AppWindow();
对象,windows窗口的一些操作依赖这个对象,我们可以利用它实现窗口显示和隐藏。结合鼠标事件使用。appWindow.show()可以显示窗口,appWindow.hide()隐藏窗口,appWindow.close()关闭窗口。
鼠标注册事件需要用到registerSystemTrayEventHandler方法,有以下几种鼠标情况可以检测(Linux不适用),默认设置点击图标显示窗口。此事件是针对托盘图标而言,右键菜单鼠标事件与这个鼠标事件无关、菜单有自己的事件监控。
kSystemTrayEventClick:左击
kSystemTrayEventRightClick:右击
kSystemTrayEventDoubleClick:双击(仅win)
// handle system tray event
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();
}
});
添加动画,控制按钮
为了美观,我们选择一个lottie动画放到程序中,让我们的编程不那么枯燥,增添几分乐趣。同时我们添加两个文字按钮,在主界面控制图标是否闪烁。
TextButton(
onPressed: () {
iconFlash();
},
child: const Text("Start Desktop Tray Icon Spalsh"),
),
TextButton(
onPressed: () {
closeIconFlash();
},
child: const Text("Close Desktop Tray Icon Spalsh"),
),
iconFlash()函数与closeIconFlash()函数分别控制了图标闪烁的开始和停止,与右键菜单控制图标闪烁的函数内容和原理基本相同,具体实现可以在完整代码中查看。 效果图:
完整代码
最终效果图:
import 'package:system_tray/system_tray.dart';
final SystemTray systemTray = SystemTray();
Future<void> initSystemTray() async {
Timer? _timer;
bool _toogleTrayIcon = true;
String path = Platform.isWindows ? 'assets/sun.ico' : 'assets/app_icon.png';
final AppWindow appWindow = AppWindow();
await systemTray.initSystemTray(
title: "system tray",
toolTip: "system tray toolTip",
iconPath: path,
);
// create context menu
final Menu menu = Menu();
await menu.buildFrom([
SubMenu(
label: "submenu",
children: [
MenuItemLabel(
label: 'setTitle',
onClicked: (menuItem) {
systemTray.setTitle("setTitle");
},
),
MenuItemLabel(
label: 'setImage null',
onClicked: (menuItem) {
systemTray.setImage("");
},
),
MenuItemLabel(
label: 'setToolTip',
onClicked: (menuItem) {
systemTray.setToolTip("setToolTip");
},
),
MenuItemLabel(
label: 'Stop flash tray icon',
image: "assets/sun.ico",
onClicked: (menuItem) {
debugPrint("Stop flash tray icon");
_timer?.cancel();
_timer = null;
systemTray.setImage("assets/sun.ico");
},
),
MenuItemLabel(
label: 'Start flash tray icon',
image: "assets/icons8-sun-30.BMP",
onClicked: (menuItem) {
debugPrint("Start flash tray icon");
_timer ??= Timer.periodic(
const Duration(milliseconds: 500),
(timer) {
_toogleTrayIcon = !_toogleTrayIcon;
systemTray.setImage(_toogleTrayIcon ? "" : "assets/sun.ico");
},
);
},
),
],
),
MenuItemCheckbox(
label: "MenuItemCheckbox",
name: "MenuItemCheckbox name",
checked: true,
onClicked: (menuItem) async {
await menuItem.setCheck(!menuItem.checked);
},
),
MenuSeparator(),
MenuItemLabel(
label: 'Show',
image: "assets/icons8-sun-30.BMP",
onClicked: (menuItem) => appWindow.show(),
),
MenuItemLabel(
label: 'Hide',
onClicked: (menuItem) => appWindow.hide(),
),
MenuItemLabel(
label: 'Exit',
onClicked: (menuItem) => appWindow.close(),
),
]);
// set context menu
await systemTray.setContextMenu(menu);
// handle system tray event
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();
}
});
}
void main() {
runApp(
const MyApp(),
);
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Timer? timer;
bool _hasIcon = true;
void closeIconFlash() {
timer?.cancel();
systemTray
.setImage(Platform.isWindows ? "assets/sun.ico" : "assets/sun.JPG");
_hasIcon = true;
}
void iconFlash() {
timer = Timer.periodic(
const Duration(milliseconds: 300),
(timer) async {
if (_hasIcon) {
await systemTray.setImage(
Platform.isWindows ? "assets/ .ico" : "assets/sun.JPG",
);
} else {
await systemTray.setImage(
Platform.isWindows ? "assets/sun.ico" : "assets/sun.JPG",
);
}
_hasIcon = !_hasIcon;
setState(() {});
},
);
}
@override
void initState() {
super.initState();
initSystemTray();
}
@override
void dispose() {
timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Container(
child: Stack(
children: [
Lottie.asset(
'assets/147963-high-five.json',
width: 100,
height: 100,
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Padding(
padding: EdgeInsets.all(10),
),
const Text("Desktop tray"),
const Padding(
padding: EdgeInsets.all(10),
),
TextButton(
onPressed: () {
iconFlash();
},
child: const Text("Start Desktop Tray Icon Spalsh"),
),
TextButton(
onPressed: () {
closeIconFlash();
},
child: const Text("Close Desktop Tray Icon Spalsh"),
),
],
)
],
),
),
),
);
}
}
转载自:https://juejin.cn/post/7268790232140890169