[Flutter翻译]在Flutter Desktop中使用键盘快捷键。一个简单的指南。

3,169 阅读6分钟

原文地址: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(免费版)翻译