Flutter桌面端开发——发送本地悬浮通知🔔

6,324 阅读3分钟

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

更新日志
详情日期
更新了win_toast版本2022-12-10
更新了local_notifier在0.1.5版本的用法2022-06-13

在我用的大部分桌面端中,发送本地悬浮通知的软件可以说是屈指可数。但是,这不妨碍到我们学习✊,奋斗说不定以后就能用到呢!

在选择该使用哪些插件开发桌面端的时候,由 lijy91 主导的 LeanFlutter 可以说是帮了很大的忙,有需要的可以自己去看看。

接下来要介绍的两个发送通知的插件,也是从 LeanFlutter 下的 awesome-flutter-desktop 仓库中找的。

local_notifier

安装🛠

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

local_notifier: ^0.1.5

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

使用🍖

要想实现发送通知的功能,需要用到一个实例化的 LocalNotifier 对象,并调用setAppName方法:

void main() async{
  WidgetsFlutterBinding.ensureInitialized();
  await localNotifier.setup(
    appName: 'Flutter桌面应用',
    // 仅 Windows
    shortcutPolicy: ShortcutPolicy.requireCreate,
  );
  runApp(const MyApp());
}

setup方法有两个参数可以传入:

  • String appName:应用程序的名称
  • shortcutPolicy shortcutPolicy:该属性一共有3个值:
    • ignore:不检查、创建或修改快捷方式
    • requireNoCreate:需要与 AUMI 匹配的快捷方式,不要创建或修改现有的快捷方式
    • requireNoCreate:默认值。需要具有匹配 AUMI 的快捷方式,如果缺少则创建,如果不匹配则修改

要想编辑想发送的内容,需要用到 LocalNotification 类。实例化该类可以传入6个参数:

  • String? identifier:用来当做通用唯一识别码
  • required String title:发送通知的标题,一般是软件名称
  • String? subtitle:发送的通知内容的标题
  • String? body:发送的内容的主体
  • bool silent = false:在发送通知时是否静音
  • List<LocalNotificationAction>? actions:通知中显示的按钮

实例化后的对象有5个方法:

  • onShow:通知显示时调用
  • onClose:通知关闭时调用
  • onClick:通知点击时调用
  • onClickAction:通知中的按钮被点击时调用
  • show:显示通知
void _localNot() {
  final notification = LocalNotification(
    // 用来生成通用唯一识别码
    identifier: '12345',
    title: '古诗鉴赏从',
    subtitle: '桃夭 - 佚名〔先秦〕',
    body: '桃之夭夭,灼灼其华。之子于归,宜其室家。\n桃之夭夭,有蕡其实。之子于归,宜其家室。\n桃之夭夭,其叶蓁蓁。之子于归,宜其家人。',
    // 用来设置是否静音
    silent: true,
  );
  notification.onShow = () {
    BotToast.showText(text: '显示了一条通知');
  };
  notification.onClose = () {
    BotToast.showText(text: '通知已经关闭');
  };
  notification.onClick = () {
    BotToast.showText(text: '通知被点击了');
  };
  notification.onClickAction = (index) {
    BotToast.showText(text: '你点击了通知的第$index个选项');
  };
  notification.show();
}

现在我们就能愉快地发送一条自定义的通知了🎉

1.gif

其中只有title参数是必传的,我们就试一下只传入这个参数:

final notification = LocalNotification(
  identifier: '12345',
  title: '古诗鉴赏从',
);
notification.show();

2-16507619225802.gif

我们发现,只传 title 参数,它会自动将 title 的参数值赋值给 subtitle,而 body 参数会以“新通知”代替。而且onCloseonClick两个方法都会触发关闭通知的功能,但是不会同时调用。这两个操作其实是有区别的。

3-16507623640163.gif

由以上试验可得,onClick才会真正一次性把通知删除,而onClose只会把通知收起来,需要再次调用onClose才能关闭通知。

接下来来看看LocalNotificationAction对象,它有2个可选参数:

  • String type:通知中显示动作的类型,值为button
  • String? text:按钮中显示的文本。虽然是可选,但是必填,否则会出错
final notification = LocalNotification(
  ...
  actions: [
    LocalNotificationAction(text: '学废了'),
    LocalNotificationAction(text: '没学废'),
  ],
);

4-16507629991694.gif

我们可以发现,点击的按钮可以通过onClickAction获取到,然后就可以愉快地编写按钮点击后的操作了。😁

win_toast

该插件0.2.0以后用法有较大差距,通过xml文件来自定义通知样式,需要的可以自行查看官方文档和案例。这里只记录到0.1.1的用法。

安装🛠

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

win_toast: ^0.1.1

使用🍖

在全局使用该插件,需要在app初始化时初始化。在某个页面使用,只要在页面初始化时初始化就行。

初始化时需要传递3个参数:

  • required String appName:程序名称
  • required String productName:产品名称
  • required String companyName:公司名称
await WinToast.instance().initialize(
  appName: '第一个Desktop应用',
  productName: '第一个Desktop应用',
  companyName: 'Hiden Intelligence',
);

没写过原生,插件作者贴出Pick a unique AUMID that will identify your Win32 app告诉我们为什么要填这些内容👀,想了解的可以看一下。

要想发送一条通知,需要使用 showToast 方法,该方法有5个参数可以传:

  • required ToastType type:传入toast显示的类型,一共有8种:

    • imageAndText01至imageAndText04
    • text01至text04

    至于这些类型的异同,可以点这里👀

  • required String title:通知显示的标题

  • String subtitle = '':通知显示的主要内容

  • String imagePath = '':选择 imageAndText 类型时,要显示的图片

  • List actions = const []:显示通知中的按钮

使用text类型

先定义几个变量和常量:

Toast? toast;
final List<String> _title = 'Shining For One Thing(《一闪一闪亮星星》影视剧歌曲) - 赵贝尔';
final List<String> _subtitle = 'I fall in love\nI see your love\n遇见你才发现\n我在等你到来';
final List<String> _actione = ['上一首', '播放/暂停', '下一首'];

先来看看只传入文字的 text01 类型:

toast = await WinToast.instance().showToast(
  type: ToastType.text01,
  title: _title,
  actions: _actione,
);

👻注意:当使用 ToastType.text01 或 ToastType.imageAndText01 时不能传入 subtitle 参数。

1

再来看看只传入文字的 text02 类型:

toast = await WinToast.instance().showToast(
  type: ToastType.text02,
  title: _title,
  subtitle: _subtitle,
  actions: _actione,
);

2

用了一下 ToastType.text03 和 ToastType.text04,发现显示的效果和 ToastType.text02 没有差别。大家可以自己试试。

使用imageAndText类型

修改一下常量的值(非必要):

Toast? toast;
final List<String> _title = '又下雨了,你的心情怎么样?';
final List<String> _subtitle = '偷偷告诉你,明天就天晴了😏\n好雨知时节,当春乃发生。随风潜入夜,润物细无声。野径云俱黑,江船火独明。晓看红湿处,花重锦官城。';
final List<String> _actione = ['不开森😭', '只想睡觉🥱', '非常高兴😃'];

还需要传入一张图片,目前无法得知应该传入图片的路径怎么填,所以先准备一张资源图片传入它的相对路径:

final String _imagePath = 'assets/images/pdx.jpg';

来看看 imageAndText01 类型:

toast = await WinToast.instance().showToast(
  type: ToastType.imageAndText01,
  title: _titles * 3,
  imagePath: _imagePath,
  actions: _action,
);

3

😲嗯?我们传入的图片怎么没显示?换个网络图片的链接试试:

final String _imagePath = 'https://gitee.com/ilgnefz/image-house/raw/master/images/pdx.jpg';

发现效果还是一样的。通过查看文档里的第一个链接中的例子,可以发现,这里需要传入图片的绝对路径。

那在 Flutter 中怎么获取文件的绝对路径呢🤔?当然,可以直接在 Android Studio 中选中图片点右键的 Copy Path,但是程序被打包安装后就不一定在这个位置了。学过Node.js,在里面获取文件的绝对路径要用到 Path 模块,那么 Flutter 是否也用同样的插件。打开 pub.dev 搜索,还真有。复制到 pubspec.yaml 进行安装,报错,告诉我们 Flutter Desktop 中已经集成了该插件,但是版本不一样。😀那不就好办了,第一步导入:

import 'package:path/path.dart' as path;

path 中没有 __dirname 方法,可以通过path. 查看提示,发现有一个 current的方法。虽然我们不知道这个方法是干什么的,但也不妨试试。修改 imagePath 为如下代码:

final String _imagePath = path.join(path.current, 'assets/images/pdx.jpg');

3

成功🎉🎉🎉🎉🎉

接下来使用 imageAndText02 类型来看看:

toast = await WinToast.instance().showToast(
  type: ToastType.imageAndText02,
  title: _titles,
  subtitle: _subtitle,
  imagePath: _imagePath,
  actions: _action,
);

3

imageAndText03 和 imageAndText04 显示的效果也和 imageAndText02 无差别,这里就不放图了。

大家可能已经发现,通知中的3个按钮是由 actions 参数决定的,但是这个参数传入的是 String 类型,那我们要怎么才能获取到用户对这些按钮的点击事件呢?

前期我们定义了一个toast对象用来赋值,接下来就要用到这个参数:

if (toast != null) {
  toast.eventStream.listen((event) {
    if (event is ActivatedEvent) {
      print(event);
    }
  });
}

在这里我们会获得一个 event 对象,通过打印会发现该对象下面只有一个属性actionIndex,返回的是 int? 类型。通过该属性,我们就可以获取到用户点击的是第几个按钮:

WinToast.instance().bringWindowToFront(); // 用户点击后关闭弹窗通知
BotToast.showText(text: '你当前的状态是${_action[event.actionIndex!]}');

4

知道了用户点击的是哪个按钮,接下来编写事件的代码就容易了。

🛫OK,以上就是这篇文章的全部内容,仅针对插件的当前版本,并不能保证适用于以后插件用法的更新迭代。本人只处于对代码的实践部分,如某些内容的概念或叫法出错还请指正🙏。

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