LoginSignup
6
3

More than 1 year has passed since last update.

Flutter AutocompleteでTextFieldに入力候補を表示する

Last updated at Posted at 2022-07-22

autocomplete_sample_03.png

この記事について

Flutterで入力候補付きのテキストフィールドを表示するAutocompleteウィジェットの実装サンプルを紹介、解説しています。ここでは下記の3つタイプの実装サンプルを紹介しています。

  1. Flutter公式サンプル ← 一番簡単だが見た目×
  2. Medium(海外ブログサービス)で見つけたサンプル ← Flutter Webと相性×
  3. 僕がたどり着いた正解のサンプル ← 上記の欠点を克服!

答えだけ欲しい方は3つ目のサンプルコードだけコピペで使っていただければOKです◎

※ FlutterやDartの基礎的な部分についての解説は省いています
※ 可読性や簡単のため参考記事から一部コードを編集しています

Flutter AutoCompleteについて

Flutterの標準的なWidgetTextFieldの変化系で、テキスト入力中に入力候補を表示させることができます。
その他多数の標準的なWidgetと共にflutter/material.dartに含まれています。
公式ページ:Autocomplete class

material.dartのインポート

sample.dart
import 'package:flutter/material.dart';

1. Flutter公式サンプル

特長
最も簡単な実装サンプル
デメリット
装飾の変更なし
autocomplete_sample_01.png

Widget実装部コードサンプル

sample.dart
class MyAutocomplete extends StatelessWidget {
  const MyAutocomplete({Key? key}) : super(key: key);

  // 入力候補に出す値の配列
  static const _kOptions = ['aardvark', 'bobcat', 'chameleon']; // 1.

  @override
  Widget build(BuildContext context) {
    // AutoComplete Widget本体
    return Autocomplete<String>( // 2.
      // 入力候補リストの要素ビルダー
      optionsBuilder: (TextEditingValue textEditingValue) { // 3.
        if (textEditingValue.text == '') {
          return const Iterable<String>.empty();
        }
        return _kOptions.where((String option) { // 4.
          return option.contains(textEditingValue.text.toLowerCase());
        });
      },
      // 入力候補選択時の処理
      onSelected: (String selection) { // 5.
        debugPrint('You just selected $selection');
      },
    );
  }
}

解説

  1. 入力候補に表示する要素のデータ元になる配列
  2. AutoComplete<String>のように、リストに出す候補のクラスを宣言
    (Stringが返せるクラスであればなんでも可、今回はそのままString)
  3. optionsBuilderで、候補に出す値の配列を返します
    (textEditingValue.textでフィールドに入力中の値を取得してwhere内で利用)
  4. 2.で宣言したクラスの配列をreturn
  5. 候補選択時の処理、次フィールドへフォーカスの移動なども実装可

参考ページ

2. Mediumで見つけたサンプル

特長
装飾を施したパターンの簡単なサンプル
デメリット
Webでこのまま実装した場合、キーボードの矢印による候補の選択ができなくなる
(厳密にはハイライトされなくなる、その他いろいろと難あり)
autocomplete_sample_02.png

Widget実装部コードサンプル

sample.dart
class MyAutocomplete extends StatelessWidget {
  const MyAutocomplete({Key? key}) : super(key: key);

  static const _kOptions = ['aardvark', 'bobcat', 'chameleon'];

  @override
  Widget build(BuildContext context) {
    return Autocomplete<String>(
      optionsBuilder: (TextEditingValue textEditingValue) {
        if (textEditingValue.text == '') {
          return const Iterable<String>.empty();
        }
        return _kOptions.where((String option) {
          return option.contains(textEditingValue.text.toLowerCase());
        });
      },
      onSelected: (String selection) {
        debugPrint('You just selected $selection');
      },
      // ---------- ここから追加 ----------
      optionsViewBuilder: (
        BuildContext context,
        AutocompleteOnSelected<String> onSelected,
        Iterable<String> options,
      ) {
        // 入力候補リストの表示枠のWidgetを定義
        return Align(
          alignment: Alignment.topLeft,
          child: Material( 
            elevation: 4, // 1.
            child: Container(
              width: MediaQuery.of(context).size.width - 24, // 2.
              color: Colors.cyan,
              // のリストWidget本体
              child: ListView.builder( // 3.
                shrinkWrap: true, // 4.
                itemCount: options.length,
                itemBuilder: (context, index) {
                  final option = options.elementAt(index);
                  return GestureDetector(
                    child: ListTile(
                      title: Text(option, style: const TextStyle(color: Colors.white)),
                    ),
                    onTap: () => onSelected(option), // 5.
                  );
                },
              ),
            ),
          ),
        );
      },
      // ---------- ここまで ----------
    );
  }
}

解説

  1. 影をつけるためのelevation
    Materialごと削除すれば影のないフラットなデザインに
  2. テキストフィールドの幅に入力候補の幅もあわせる
    デフォルトでは画面の右端まで突き抜けてしまう
  3. 入力候補を動的に生成するリストビュー
  4. 候補が少ない時にリストが下の伸びっぱなしにならないようfalse
  5. 候補選択時にフィールドに値が入力させる処理onSelected

参考ページ

3. 僕がたどり着いた正解のサンプル

autocomplete_sample_03.png

特長
入力候補部分のWidgetの装飾を変更しつつ、キーボード矢印による候補の選択にも対応
執筆時点では他サイトに類似のサンプルなどありませんでした!
(Autocompleteのソースコードを見てアレンジしました)
デメリット
特にありません、実際にアプリ制作する場合はこちらをお使いください

Widget実装部コードサンプル

sample.dart
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; // 1.
sample.dart
class MyAutocomplete extends StatelessWidget {
  const MyAutocomplete({Key? key}) : super(key: key);

  static const _kOptions = ['aardvark', 'bobcat', 'chameleon', 'dragonfly', 'eagle', 'fossa'];

  @override
  Widget build(BuildContext context) {
    return Autocomplete<String>(
      optionsBuilder: (TextEditingValue textEditingValue) {
        if (textEditingValue.text == '') {
          return const Iterable<String>.empty();
        }
        return _kOptions.where((String option) {
          return option.contains(textEditingValue.text.toLowerCase());
        });
      },
      onSelected: (String selection) {
        debugPrint('You just selected $selection');
      },
      optionsViewBuilder: (
        BuildContext context,
        AutocompleteOnSelected<String> onSelected,
        Iterable<String> options,
      ) {
        // 入力候補リストの表示枠のWidgetを定義
        return Align(
          alignment: Alignment.topLeft,
          child: Material(
            elevation: 4,
            child: SizedBox( // 2.
              width: MediaQuery.of(context).size.width - 24,
              child: ConstrainedBox( // 3.
                constraints: const BoxConstraints(maxHeight: 175),
                child: ListView.builder(
                  shrinkWrap: true,
                  itemCount: options.length,
                  itemBuilder: (context, index) {
                    final option = options.elementAt(index);
                    return Builder(
                      builder: (BuildContext context) {
                        // 4.
                        final bool highlight = AutocompleteHighlightedOption.of(context) == index;
                        // 5.
                        if (highlight) {
                          SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) {
                            Scrollable.ensureVisible(context, alignment: 0.5);
                          });
                        }
                        return GestureDetector(
                          child: ListTile(
                            tileColor: highlight ? Colors.cyan.withAlpha(172) : Colors.cyan, // 6.
                            title: Text(option, style: const TextStyle(color: Colors.white)),
                          ),
                          onTap: () => onSelected(option),
                        );
                      },
                    );
                  },
                ),
              ),
            ),
          ),
        );
      },
    );
  }
}

解説

  1. 5.で使用するコールバック関数のライブラリをインポート
  2. 色付きのContainerから色なしのSizedBoxに変更
  3. 長いリストが画面外に出ないようにmaxHeightを設定
  4. 選択中の候補かどうかをboolで取得
  5. 選択中の候補がリスト表示枠の中心に来るようにcallback
  6. 選択中の候補の色をハイライト表示

あとがき

参考になりましたらLGTMポチッとお願いします!
まだまだFlutter修行中ですが、ご質問などいただければ分かる範囲でお答え(もしかすると記事化)するので、ぜひコメントも残してください:)

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3