見出し画像

【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.ワークスペースとなるディレクトリを選択(この中にプロジェクトが作成されます)

プロジェクト名は「note_flutter_keyevent」とします。

下準備

import 'package:flutter/services.dart';

今回は特にパッケージの導入は不要ですが、標準ライブラリの「services.dart」のインポートが必要となります。

土台を作成する

まずは土台を作成します。
キー操作関連は、何かのウィジェットに対し「フォーカス状態」にする必要があります。
今回は学習用サンプルなので、ダミーとして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()直下にウィジェットを書きまくるという暴挙に出てますが、検証用なのでご容赦を🐣

キーイベント - 基本

●基本
対象ウィジェットを「フォーカス状態」にする必要がある。
これをする為、Focusウィジェットの中に対象ウィジェット(今回はText)入れて使います。

Enterキー押下時の例です

autofocus:自動フォーカスさせるため trueにします
onKeyEvent:キーイベント発生時のコールバック
if (event is KeyDownEvent) {}:キーイベントは「キーが下に下がった」「キーが戻し上がった」の2種類があるので分岐する(これをしないと同じ処理が2回されてしまう)
child:対象ウィジェットを指定

【注意点】
例えばTextField(テキスト入力欄)にすぐに入力出来るようにフォーカスする場合は

TextFieldの方に autofocus: true 」を付けます。( ※ Focusの方には付けてはいけない )

【返り値について】

Focusウィジェットのパラメーター

なお、Focusウィジェットですが「KeyEventResult」を返す必要があります

・KeyEventResult.handled
ハンドルした場合に返す(今回はEnterキー押下時)
・KeyEventResult.ignored
特に何もしなかったキーの場合に返す

KeyEventResultをサジェストして見ると

ちなみに「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);
}

・動作確認

Enterキーを押下

Enterキーを押下(2回目)

キーイベント - 非同期処理

●非同期処理の場合
例えば、こんな感じで非同期処理をしたいとする

await Future.delayed(Duration(seconds: 10));
print('10秒後に実行');

この為、onKeyEventに「async」指定しようとするも

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」に解決策が掲載されていた。
( ※ 概要 - 参考になったサイト にリンクを貼っておきました )

それを参考に改修してみる

非同期的な関数分割をして

こんな感じで呼び出せばオッケー🙆

非同期関数の呼び出しと言うことを分かりやすくする為 .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」押下時を例とします。

・インテントクラスを定義

・インテント時の処理関数を定義

・ショートカットキーの定義

LogicalKeySetオブジェクトを作成(「Control + N」の例)

【FocusableActionDetector】

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);
}

●動作確認

特に問題なくデバッグ文が出力される。

キーボードショートカット - 非同期処処理

非同期処理ですが、すんなりいきました。

↑これを
↓こう

これで、DB処理とかも問題無さげですね💡

【余談】note「見出し画像」でgifファイルは使えない

余談ですが、noteの「見出し画像」はCanvaで作成してます。

https://www.canva.com

で、今回キーボードの背景を探していたのですが、Gif画像で動くものがあったんですよね。
これ、「見出し画像」で動いていたらかっこいいなと思ったのですが、いざnoteでやってみると「見出し画像でGifファイルは出来ない」です😑

これが見出し画像で出来れば、かっこよかったのだが ……

まあ、記事の中で使う分には、使えるみたいですね。
上記の画像、あなたのブラウザでは動きがあるでしょうか❓

著書

【辛島信芳の著書】
IT技術などに興味のある方は、是非ご覧になってください。


この記事が気に入ったらサポートをしてみませんか?