【Flutter】TextFormField タップ時にピッカー（ドラムロール）を出して入力させる


![Flutter-1.20.2](https://img.shields.io/badge/Flutter-1.20.2-blue) ![Dart-2.9.1](https://img.shields.io/badge/Dart-2.9.1-blue)

フォームを作成するとき [TextFormField](https://api.flutter.dev/flutter/material/TextFormField-class.html) を用いて実装することが多いですが、キーボードによるテキスト入力ではなく、選択肢から選ばせたい場合があります。
独自のウィジェットを作成しても良いですが、 TextFormField として実装することで、他の TextFormField と同じように `onSave()` を処理したり、バリデーションを適応することができます。

「テキストフィールドをタップすると、ピッカー（ドラムロール）が表示され、選択したものがフォームに入力される」というものを実装します。

まず普通のテキストフィールドを作成します。


```main.dart
TextFormField buildTextFormField(BuildContext context) {
    return TextFormField(
      decoration: const InputDecoration(hintText: 'テキストを入力'),
    );
  }
```

`onTap()` でタップした時にピッカーを呼び出します。
ピッカー部分の実装は以前の投稿を参照。
[【Flutter】画面下から出てくるピッカー（ドラムロール）を実装する](https://qiita.com/tewi_r/items/703676a6eca7bd085180)

```main.dart
TextFormField buildTextFormField(BuildContext context) {
  return TextFormField(
    onTap: () {
      // キーボードが出ないようにする
      FocusScope.of(context).requestFocus(new FocusNode());
      showPicker();
    },
    decoration: const InputDecoration(hintText: 'テキストを入力'),
  );
}

void showPicker() {
  final list = ['選択肢1', '選択肢2', '選択肢3'];
  final _pickerItems = list.map((item) => Text(item)).toList();
  var selectedIndex = 0;

  showCupertinoModalPopup<void>(
    context: context,
    builder: (BuildContext context) {
      return Container(
        height: 216,
        child: GestureDetector(
          onTap: () {
            Navigator.pop(context);
          },
          child: CupertinoPicker(
            itemExtent: 32,
            children: _pickerItems,
            onSelectedItemChanged: (int index) {
              selectedIndex = index;
            },
          ),
        ),
      );
    },
  );
}

```

テキストフィールドをタップするとピッカーが起動するようになります。

そのままだとピッカーが出る前にキーボードが一瞬表示されてしまうので、
`FocusScope.of(context).requestFocus(new FocusNode())` を入れて抑制しています。

![Simulator Screen Shot - iPhone SE (2nd generation) - 2020-08-31 at 09.33.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105756/85adde97-3d3e-9865-51c3-be765608d67d.png)

ピッカーを閉じたとき、選択した文字列を TextFormField に反映します。

```main.dart
// コントローラーを追加
final TextEditingController _controller = TextEditingController();

TextFormField buildTextFormField(BuildContext context) {
    return TextFormField(
      onTap: () {
        // ...
      },
      // コントローラーを追加
      controller: _controller,
      decoration: const InputDecoration(hintText: 'テキストを入力'),
    );
  }
```

```main.dart
showCupertinoModalPopup<void>(
  // ...
).then((_) {
  if (selectedIndex != null) {
    _controller.value = TextEditingValue(text: list[selectedIndex]);
  }
});
```

![Simulator Screen Shot - iPhone SE (2nd generation) - 2020-08-31 at 09.33.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105756/99bfe325-d99b-7080-cc17-94964f09935d.png)

TextFormField として実装しているため、`validator()` や `onSave()` を実装することができます。


<details><summary>コード全体</summary><div>

```main.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'TextFormField Picker',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const MyHomePage(title: 'TextFormField Picker'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final TextEditingController _controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Center(
          child: buildTextFormField(context),
        ));
  }

  TextFormField buildTextFormField(BuildContext context) {
    return TextFormField(
      onTap: () {
        // キーボードが出ないようにする
        FocusScope.of(context).requestFocus(new FocusNode());
        showPicker();
      },
      controller: _controller,
      decoration: const InputDecoration(hintText: 'テキストを入力'),
    );
  }

  void showPicker() {
    final list = ['選択肢1', '選択肢2', '選択肢3'];
    final _pickerItems = list.map((item) => Text(item)).toList();
    var selectedIndex = 0;

    showCupertinoModalPopup<void>(
      context: context,
      builder: (BuildContext context) {
        return Container(
          height: 216,
          child: GestureDetector(
            onTap: () {
              Navigator.pop(context);
            },
            child: CupertinoPicker(
              itemExtent: 32,
              children: _pickerItems,
              onSelectedItemChanged: (int index) {
                selectedIndex = index;
              },
            ),
          ),
        );
      },
    ).then((_) {
      if (selectedIndex != null) {
        _controller.value = TextEditingValue(text: list[selectedIndex]);
      }
    });
  }
}
```

</div></details>