![見出し画像](https://assets.st-note.com/production/uploads/images/151670347/rectangle_large_type_2_328d33a960c573b5ae0972ad933f60cf.png?width=1200)
【Flutter】キーイベントとキーボードショートカット(非同期処理含む)
概要
Flutterアプリ開発での「キーイベント」及び「キーボードショートカット」の実装(非同期処理含む)についての備忘録を記す。
まあ、事の始まりはテキスト入力後Enterでどんどん追記出来るようなメモアプリを作りたく思い📝
●GitHub
今回は「main.dart」のみと言うこともあり、GitHubには上げてません。
※ソースを直接コピペで動作出来ます。
●参考になったサイト
実装
検証環境
●検証環境
macOS Sonoma 14.3
VSCode 1.92.1
Flutter 3.22.3
Dart 3.4.3
※ 基本的なFlutter開発環境は整っている事を前提とします
プロジェクトを作成
●新規プロジェクトの作成
1.コマンドパレット(Command + Shift + P)を開き「flutter」と入力し、「Flutter: New Project」を選択。
2.一番上の「Application」を選択。
3.ワークスペースとなるディレクトリを選択(この中にプロジェクトが作成されます)
![](https://assets.st-note.com/img/1724387825984-CbUU4QHo7x.png?width=1200)
プロジェクト名は「note_flutter_keyevent」とします。
下準備
import 'package:flutter/services.dart';
今回は特にパッケージの導入は不要ですが、標準ライブラリの「services.dart」のインポートが必要となります。
![](https://assets.st-note.com/img/1724388056496-PuolgOYEEH.png?width=1200)
土台を作成する
まずは土台を作成します。
キー操作関連は、何かのウィジェットに対し「フォーカス状態」にする必要があります。
今回は学習用サンプルなので、ダミーとしてTextを配置しておきましょう。
【main.dart】
import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; // これを追加
void main() {
final text = Text('テキスト');
final body = SafeArea( // ボディー
child: Column(
children: [
text,
],
)
);
final sc = Scaffold(
body: body, // ボディー
);
final app = MaterialApp(home: sc);
runApp(app);
}
相変わらずmain()直下にウィジェットを書きまくるという暴挙に出てますが、検証用なのでご容赦を🐣
![](https://assets.st-note.com/img/1724389564908-DCZ1nQKMLg.png?width=1200)
キーイベント - 基本
●基本
対象ウィジェットを「フォーカス状態」にする必要がある。
これをする為、Focusウィジェットの中に対象ウィジェット(今回はText)入れて使います。
Enterキー押下時の例です
![](https://assets.st-note.com/img/1724392368504-WcEsSKNWk3.png?width=1200)
autofocus:自動フォーカスさせるため trueにします
onKeyEvent:キーイベント発生時のコールバック
if (event is KeyDownEvent) {}:キーイベントは「キーが下に下がった」「キーが戻し上がった」の2種類があるので分岐する(これをしないと同じ処理が2回されてしまう)
child:対象ウィジェットを指定
【注意点】
例えばTextField(テキスト入力欄)にすぐに入力出来るようにフォーカスする場合は
![](https://assets.st-note.com/img/1724392274517-J5YJlY11gi.png?width=1200)
「TextFieldの方に autofocus: true 」を付けます。( ※ Focusの方には付けてはいけない )
【返り値について】
![](https://assets.st-note.com/img/1724390946251-m8NF2yIjJM.png?width=1200)
なお、Focusウィジェットですが「KeyEventResult」を返す必要があります。
![](https://assets.st-note.com/img/1724390960981-5Sqk9lexL0.png?width=1200)
・KeyEventResult.handled
ハンドルした場合に返す(今回はEnterキー押下時)
・KeyEventResult.ignored
特に何もしなかったキーの場合に返す
![](https://assets.st-note.com/img/1724391257682-F5lI9XHw8S.png?width=1200)
KeyEventResultをサジェストして見ると
![](https://assets.st-note.com/img/1724391257709-kXjVI7uN5z.png?width=1200)
ちなみに「KeyEventResult.skipRemainingHandlers」と言うのもあるらしい。
【コード】
import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; // これを追加
void main() {
final text = Text('テキスト');
final textFocus = Focus(
autofocus: true, // 自動フォーカス,
onKeyEvent: (node, event) {
final key = event.logicalKey;
if (event is KeyDownEvent) {
if ( key == LogicalKeyboardKey.enter ) { // Enterキー押下時
print('Enterキーが押されました');
return KeyEventResult.handled;
}
}
return KeyEventResult.ignored;
},
child: text,
);
final body = SafeArea( // ボディー
child: Column(
children: [
textFocus,
],
)
);
final sc = Scaffold(
body: body, // ボディー
);
final app = MaterialApp(home: sc);
runApp(app);
}
・動作確認
![](https://assets.st-note.com/img/1724390345521-FhoZrqK1GE.png?width=1200)
Enterキーを押下
![](https://assets.st-note.com/img/1724390345533-tVDxkzMvcz.png?width=1200)
Enterキーを押下(2回目)
キーイベント - 非同期処理
●非同期処理の場合
例えば、こんな感じで非同期処理をしたいとする
await Future.delayed(Duration(seconds: 10));
print('10秒後に実行');
この為、onKeyEventに「async」指定しようとするも
![](https://assets.st-note.com/img/1724393613299-DvNRSUSuhM.png?width=1200)
『The argument type 'Future<KeyEventResult> Function(FocusNode, KeyEvent)' can't be assigned to the parameter type 'KeyEventResult Function(FocusNode, KeyEvent)?'.』
と言うエラーが発生。
そう、onKeyEventではasyncに対応していないのです。
(onPressedやonTapは対応しているのに ……)
これは困ったぞ。
そもそもEnterキーでメモ登録。つまりDB接続(非同期処理)をしたいと言うのに。
●解決策
色々調べてみた結果「Stack Overflow」に解決策が掲載されていた。
( ※ 概要 - 参考になったサイト にリンクを貼っておきました )
それを参考に改修してみる
![](https://assets.st-note.com/img/1724395487660-hgPFayMkaE.png?width=1200)
非同期的な関数分割をして
![](https://assets.st-note.com/img/1724395487695-fgvZaAeK88.png?width=1200)
こんな感じで呼び出せばオッケー🙆
![](https://assets.st-note.com/img/1724395487683-uukeBcLdav.png?width=1200)
非同期関数の呼び出しと言うことを分かりやすくする為 .then((value) { …… }) を付けたが別に省いても良い。
【コード】
import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; // これを追加
Future<KeyEventResult> manageKeyboard(KeyEvent event) async {
final key = event.logicalKey;
if (event is KeyDownEvent) {
if ( key == LogicalKeyboardKey.enter ) { // Enterキー押下時
await Future.delayed(Duration(seconds: 10));
print('10秒後に実行');
return KeyEventResult.handled;
}
}
return KeyEventResult.ignored;
}
void main() {
final text = Text('テキスト');
final textFocus = Focus(
autofocus: true,
onKeyEvent: (node, event) {
manageKeyboard(event).then((value) {
print(value);
});
// 本来は「KeyEventResult.handled」との出し分けをするべきだが、今回は省略。
return KeyEventResult.ignored;
},
child: text,
);
final body = SafeArea( // ボディー
child: Column(
children: [
textFocus,
],
)
);
final sc = Scaffold(
body: body, // ボディー
);
final app = MaterialApp(home: sc);
runApp(app);
}
キーボードショートカット - 基本
キーボード関連(検知や操作など)のウィジェットを色々ありますが、今回「FocusableActionDetector」ウィジェットを使っていきたいと思います。
ウィジェット名から察するに
Focusable → フォーカス出来る
Action → アクション
Detector → 検出器
といった感じのウィジェットです。
shortcuts:ショートカット及びそれに紐づくインテントを設定。
actions:アクション。「インテント時の処理関数」を設定。
※ 「Control + N」押下時を例とします。
・インテントクラスを定義
![](https://assets.st-note.com/img/1724397821694-7i6F8fpllP.png?width=1200)
・インテント時の処理関数を定義
![](https://assets.st-note.com/img/1724396774341-fe93D4dRoV.png?width=1200)
・ショートカットキーの定義
![](https://assets.st-note.com/img/1724397328092-CAcrsklJan.png?width=1200)
LogicalKeySetオブジェクトを作成(「Control + N」の例)
【FocusableActionDetector】
![](https://assets.st-note.com/img/1724397328073-AOu32sTUkh.png?width=1200)
autofocus:自動フォーカスさせるため trueにします
shortcuts:LogicalKeySetとそれの対応インテントを設定
actions:インテント時の処理関数を設定。
child:対象ウィジェットを指定(bodyに該当するものを丸ごと入れている例)
【コード】
import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; // これを追加
class TestIntent extends Intent {}
void _test() {
print('「Control + N」が押されました');
}
void main() {
final text = Text('テキスト');
final body = SafeArea( // ボディー
child: Column(
children: [
text,
],
)
);
// 「Control + N」
final lksControlN = LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyN);
final faDetector = FocusableActionDetector(
autofocus: true,
shortcuts: <ShortcutActivator, Intent>{
lksControlN:
TestIntent(),
},
actions: {
TestIntent: CallbackAction<TestIntent>(
onInvoke: (intent) => _test(),
),
},
child: body,
);
final sc = Scaffold(
body: faDetector, // FocusableActionDetector
);
final app = MaterialApp(home: sc);
runApp(app);
}
●動作確認
![](https://assets.st-note.com/img/1724397832046-MYba8B3hnk.png?width=1200)
特に問題なくデバッグ文が出力される。
キーボードショートカット - 非同期処処理
非同期処理ですが、すんなりいきました。
![](https://assets.st-note.com/img/1724399941587-I0RWTUOY33.png?width=1200)
↑これを
↓こう
![](https://assets.st-note.com/img/1724399950379-kTDeAiwhOt.png?width=1200)
これで、DB処理とかも問題無さげですね💡
【余談】note「見出し画像」でgifファイルは使えない
余談ですが、noteの「見出し画像」はCanvaで作成してます。
![](https://assets.st-note.com/img/1724386243579-LWQRzcMaoB.png?width=1200)
で、今回キーボードの背景を探していたのですが、Gif画像で動くものがあったんですよね。
これ、「見出し画像」で動いていたらかっこいいなと思ったのですが、いざnoteでやってみると「見出し画像でGifファイルは出来ない」です😑
![](https://assets.st-note.com/production/uploads/images/151670263/picture_pc_122af908afa053012df8c828955ef9e5.gif?width=1200)
まあ、記事の中で使う分には、使えるみたいですね。
上記の画像、あなたのブラウザでは動きがあるでしょうか❓
著書
『 プログラマーにおくるFlutterアプリ開発の入門書』
2024年11月時点での最新技術をぎっしりと詰め込んであるので、アプリ開発に参画するエンジニアの人は、是非ともご覧になって頂ければと思います📱