原文地址:wilsonwilson.dev/keyboard-sh…
原文作者:wilsonwilson.dev/
发布时间:2021年2月1日
几周前,我分享了一个完全用Flutter打造的Pomodoro应用。在正常情况下,这将是无聊的。但这个应用是为了举一个用Flutter构建的基本桌面应用的例子。它拥有典型桌面应用所需要的一切,比如窗口管理、键盘快捷键,甚至可以访问菜单栏。
查看资源库
今天我想写的是其中的一个功能,你可能已经从标题中猜到了,就是Flutter中的键盘快捷键! 如果你想在Flutter中实现更多特定的桌面功能,比如悬浮效果,请注册到我的邮件列表,这样你就不会错过了!
在本教程中,我们将重新创建默认的计数器应用程序,但当我们按下快捷键时,用键盘绑定来增加和减少值。
错误的方法
你可能见过RawKeyboardListener小工具(最近一段时间比较流行),它允许你监听键盘上的 "上键 "和 "下键 "事件。
@override
Widget build(BuildContext context) {
return RawKeyboardListener(
autofocus: true,
onKey: (event) {
if (event.runtimeType == RawKeyDownEvent) {
if (event.physicalKey == PhysicalKeyboardKey.keyX) {
runSomeCode();
}
}
},
focusNode: FocusNode(),
child: child,
);
}
你可以用这个小工具实现键盘快捷键。但我不建议这样做,原因很多。
首先,你不能使用多个键。
你不仅要过滤按键下的事件,而且你将无法将其与其他桌面专用插件集成(主要是由于缺乏统一的实现)。
例如,如果你想使用菜单栏插件与你的快捷键,你将不得不使用LogicalKeySet,而RawKeyboardListener并不是基于此。
另外,你将无法访问很多原生功能,比如按键重复和无效按键通知(声音)。
换句话说,使用这个小部件来处理键盘快捷键是个坏主意 😖。
Don't do this!
这个小部件可以更好地从键盘上获取输入,而不需要使用TextField,这也是非常有用的🚀。
你可以从medium上的这篇文章或文档中阅读更多关于RawKeyboardListener的内容。
更好的方法
一个更好用的widget是FocusableActionDetector,它带有一个非常简单的API来监听键盘快捷键。
它接收快捷键(LogicalKeySet: Intent)和动作(Intent: Action)的映射。
意图和动作
Intent的主要目的是描述一个事件/动作。以一个浏览器应用为例,它的键盘快捷键可以创建一个新的标签。这样的Intent应该是这样的。
class NewTabIntent extends Intent {}
这就是你需要知道的全部内容。因此,在我们的计数器示例中,我们希望通过键盘快捷键来增加和减少数值,我们需要以下的Intent。
class IncrementIntent extends Intent {}
class DecrementIntent extends Intent {}
而Action则是在收到Intent后,用来做一些事情。一旦动作被 "调用",我们就会执行一些代码! 在我们的新标签页的例子中,意图的动作应该是这样的。
CallbackAction(onInvoke: (_) => createNewTab())
不同场景有不同类型的动作和意图。它们主要是描述性的,所以上面的例子可能是您创建应用程序所需要的全部。你可以在源代码中看到更多类型的动作和意图。
让我们来创建计数器应用吧!
为web/桌面创建一个新的flutter项目。我们不会修改太多的原始源码。如果你想使用Dartpad,这里的代码(但没有注释🙃)。
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
由于我们也会递减我们的计数器,我们可能应该创建一个递减计数器的方法。
void _decrementCounter() {
setState(() {
_counter--;
});
}
太棒了!现在我们的计数器所需的所有代码都完成了! 让我们开始创建我们的快捷方式。
首先,我们需要创建我们的意图和快捷键集。添加两个顶层逻辑键集和两个用于递增和递减计数器的Intents。
final incrementKeySet = LogicalKeySet(
LogicalKeyboardKey.meta, // Replace with control on Windows
LogicalKeyboardKey.arrowUp,
);
final decrementKeySet = LogicalKeySet(
LogicalKeyboardKey.meta, // Replace with control on Windows
LogicalKeyboardKey.arrowDown,
);
class IncrementIntent extends Intent {}
class DecrementIntent extends Intent {}
请注意我们是如何使用LogicalKeyboardKey.meta的。这代表了macOS上的CMD键,在windows上,用LogicalKeyboardKey.control代替。在windows上,用LogicalKeyboardKey.control替换。
要查看Flutter中所有可能的逻辑键盘键的列表,请查看文档。
棒极了!现在我们可以开始创建我们的逻辑键盘键了。现在我们可以继续创建我们的Widget,它将监听我们的快捷键,并对它们做出反应。我们将这个小部件称为CounterShortcuts。它需要一个子部件和两个回调......一个用于响应增量事件,一个用于响应减量事件。
class CounterShortcuts extends StatelessWidget {
const CounterShortcuts({
Key key,
@required this.child,
@required this.onIncrementDetected,
@required this.onDecrementDetected,
}) : super(key: key);
final Widget child;
final VoidCallback onIncrementDetected;
final VoidCallback onDecrementDetected;
...
现在我们终于可以用我们的FocusableActionDetector🎉来构建我们的widget了。
我把代码贴出来,之后再解释。
@override
Widget build(BuildContext context) {
return FocusableActionDetector(
autofocus: true,
shortcuts: {
incrementKeySet: IncrementIntent(),
decrementKeySet: DecrementIntent(),
},
actions: {
IncrementIntent:
CallbackAction(onInvoke: (e) => onIncrementDetected?.call()),
DecrementIntent:
CallbackAction(onInvoke: (e) => onDecrementDetected?.call()),
},
child: child,
);
}
FocusableActionDetector首先,需要我们给它一个子节点。在这种情况下,它将是计数器小组件。
接下来,我们需要考虑如何管理它的焦点。在一个较大的应用程序中,有许多文本字段和其他可能需要聚焦的widget,我们最好为FocusableActionDetector提供一个聚焦节点,并自行管理。但由于我们的应用中没有其他可以请求聚焦的东西,我们可以简单地将其自动聚焦参数设置为true。
现在我们需要给它提供我们的快捷键和动作。在快捷键中,我们传入一个Map<LogicalKeySet, Intent>类型的地图。因此,对于我们的增量键集,我们传入IncrementIntent,对于减量键集,我们提供DecrementIntent。
我们的动作采取的是Map<Type, Action>类型的地图。因此,对于我们的IncrementIntent,我们提供了一个CallbackAction,当它被调用时,它将调用我们的onIncrementDetected回调,对于DecrementIntent也是如此。
我们需要做的最后一件事是用我们的CounterShortcuts widget来包装我们的计数器。当onIncrementDetected被调用时,我们对计数器进行增量,对onDecrementDetected也做类似的操作。
@override
Widget build(BuildContext context) {
return CounterShortcuts(
onIncrementDetected: _incrementCounter,
onDecrementDetected: _decrementCounter,
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
...
然后就可以了! 我们现在有了一个功能齐全的计数器应用程序,可以响应快捷键🚀。
(为了演示,我加了一个点心条,显示按了哪些键)。
如果你想学习如何使用菜单栏的快捷键,那就加入我的邮件列表吧! 一个教程即将到来 😃。
通过www.DeepL.com/Translator(免费版)翻译