Flutter で汎用的な入力ページを考える

今年は、Flutterを使用し、いくつかのアプリをリリースすることができました。

その中で、会員登録などの入力ページでチームのスタンダードになってきているなぁという部分がありましたので紹介したいと思います。

### 入力ページのイメージ

会員登録ページを例にあげると、現在担当しているアプリは下記のような、一度に情報を登録せず、ページ遷移させる事が多いです。

![ページの遷移.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81348/7b4a1ba4-43e2-0c38-049f-3e6475348682.png)

<img width="181" alt="first.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81348/1473f349-047c-bbfb-89b8-5dc3394f3932.png">

```dart:main.dart
body: Column(
        children: <Widget>[
          Padding(
            padding: EdgeInsets.symmetric(vertical: 30),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Text('XXXXXを入力してください。'),
                Text('XXXXXはあとからマイページにて変更することも可能です。'),
              ],
            ),
          ),
          TextField(),
          Expanded(child: Container()),
          Padding(
            padding: EdgeInsets.only(bottom: 50),
            child: RaisedButton(child: Text('次へ進む')),
          ),
      ])
```

### 縦に長い場合エラー
 
しかし文言が増えたときなど、縦幅が長くなった場合、bottom overflowedエラーが出てしまいます。

<img width="181" alt="スクリーンショット 2019-12-25 12.35.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81348/d6ff105c-7c8b-8e68-f2d7-a58ad481c7d8.png">

対策として、画面をタップするとキーボードが閉じる処理を入れます。

```dart:main.dart
body: GestureDetector(
          onTap: () => FocusScope.of(context).unfocus(),
          child: Column(children: <Widget>[
        Padding(
          padding: EdgeInsets.symmetric(vertical: 30),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Text('XXXXXを入力してください。'),
              Container(
                height: 500,
                color: Colors.green,
              ),
            ],
          ),
        ),
        TextField(),
        Expanded(child: Container()),
        Padding(
          padding: EdgeInsets.only(bottom: 50),
          child: RaisedButton(child: Text('次へ進む')),
        )
      ])),
      resizeToAvoidBottomInset: false, //追加部分
```

全体を `GestureDetector` で囲み、 `onTap` でフォーカスを外す処理を入れ、キーボードを隠すようにします。
また、 `resizeToAvoidBottomInset` でキーホードの表示/非表示でボタンの位置が変わらないようにします。

### もっと長い場合は

ページが長い場合はスクロール処理を入れますが、スクロールを行なった際にもキーボードを閉じる処理を入れたいです。

```dart:main.dart

body: NotificationListener(
      child: SingleChildScrollView(
          dragStartBehavior: DragStartBehavior.down,
          child: GestureDetector(
              onTap: () => FocusScope.of(context).unfocus(),
              child: Column(children: <Widget>[
                Padding(
                  padding: EdgeInsets.symmetric(vertical: 30),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: <Widget>[
                      Text('XXXXXを入力してください。'),
                      Text('XXXXXはあとからマイページにて変更することも可能です。'),
                      Container(
                        height: 500,
                        color: Colors.green,
                      ),
                    ],
                  ),
                ),
                TextField(),
                Padding(
                  padding: EdgeInsets.only(top: 50, bottom: 50),
                  child: RaisedButton(child: Text('次へ進む')),
                )
              ]))),
      onNotification: (ScrollNotification scrollInfo) {
        FocusScope.of(context).unfocus();
      }),
  resizeToAvoidBottomInset: false,
```
`SingleChildScrollView` で全体を囲み、 さらに `NotificationListener` で囲みます。
`Expanded` はスクロールの中に入れられないので外します。
`onNotification` でスクロールが発生した際にフォーカスを外す処理を入れて出来上がりです。

[サンプルのプログラムをgithubにあげました](https://github.com/naoaki-kaito/flutter_input_example)