【Flutter】FocusableActionDetectorを使ってショートカットキーを設定する

ネコニウム研究所

PCを利用したモノづくりに関連する情報や超個人的なナレッジを掲載するブログ

【Flutter】FocusableActionDetectorを使ってショートカットキーを設定する

2024-6-5 | ,

FlutterのFocusableActionDetectorを使ってショートカットキーを設定したい!

概要

今回の記事では、FlutterのFocusableActionDetectorを使ってショートカットキーを設定する手順を掲載する。

仕様書

環境

  • Android Studio Giraffe | 2023.2.1 Patch 2
  • Flutter 3.19.6
  • file_picker 8.0.3

手順書

インストール編とコード編の2部構成です。

インストール編

ファイルの入出力にfile_pickerを使ってるのでインストールする。

ターミナルでコマンドを実行するか

flutter pub add file_picker

pubspec.yamldependencies:に下記のような感じで追加して

dependencies:
  file_picker: ^8.0.3

ターミナルでflutter pub getする。

flutter pub get

コード編

下記のテキストファイルの入出力の例にショートカットキーを設定する。

Crtl+Oでファイルを開き、Ctrl+Sでファイルを保存する例。

import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        body: const FilePickerSample1()
      )
    )
  );
}

class SaveTextIntent extends Intent {}
class OpenTextIntent extends Intent {}

class FilePickerSample1 extends StatelessWidget {
  FilePickerSample1({super.key});

  TextEditingController textEditingController = TextEditingController();

  void saveText() async {
    String? outputFile = await FilePicker.platform.saveFile(
      dialogTitle: 'Please select an text file:',
      fileName: 'output.txt',
    );

    if (outputFile == null) {
      return;
    }

    final file = File(outputFile);
    await file.create();
    await file.writeAsString(textEditingController.text);
  }

  void loadText() async {
    FilePickerResult? result = await FilePicker.platform.pickFiles(
      type: FileType.custom,
      allowedExtensions: ['txt'],
    );

    if (result != null) {
      File file = File(result.files.single.path!);
      if (await file.exists()) {
        final data = await file.readAsString();
        textEditingController.text = data;
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return FocusableActionDetector(
      autofocus: true,
      shortcuts: <ShortcutActivator, Intent>{
        LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyS):
            SaveTextIntent(),
        LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyO):
            OpenTextIntent(),
      },
      actions: {
        SaveTextIntent: CallbackAction<SaveTextIntent>(
          onInvoke: (intent) => saveText(),
        ),
        OpenTextIntent: CallbackAction<OpenTextIntent>(
          onInvoke: (intent) => loadText(),
        ),
      },
      child: SafeArea(
        child: Center(
          child: Column(children: [
            Row(children: [
              IconButton(
                icon: const Icon(Icons.upload),
                onPressed: () {
                  loadText();
                },
              ),
              IconButton(
                icon: const Icon(Icons.download),
                onPressed: () {
                  saveText();
                },
              ),
            ]),
            const SizedBox(height: 8),
            Expanded(
              //width: double.infinity,
              child: TextField(
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  labelText: 'テキスト',
                  floatingLabelBehavior: FloatingLabelBehavior.always,
                ),
                controller: textEditingController,
                maxLines: null,
                expands: true,
                textAlignVertical: TextAlignVertical.top,
              ),
            ),
          ]),
        ),
      ),
    );
  }
}

FocusableActionDetectorにショートカットキーと紐づける動作を設定する。

複数のショートカットキーを設定するには例のようにshortcutsにオブジェクトとして

      shortcuts: <ShortcutActivator, Intent>{
        LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyS):
            SaveTextIntent(),
        LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyO):
            OpenTextIntent(),

動作を設定するactionsも同様。

      actions: {
        SaveTextIntent: CallbackAction<SaveTextIntent>(
          onInvoke: (intent) => saveText(),
        ),
        OpenTextIntent: CallbackAction<OpenTextIntent>(
          onInvoke: (intent) => loadText(),
        ),

トップレベルに動作に対応するIntentを作っておく。

class SaveTextIntent extends Intent {}
class OpenTextIntent extends Intent {}

FocusableActionDetectorにフォーカスがあたってないとショートカットキーが反応しないのでautofocus: trueとして自動的にフォーカスされるようにしておく。この設定で子のTextFieldにフォーカスがあたらないということはない。

まとめ(感想文)

ショートカットキーがあることでアプリでの作業効率が上がるかもね!