Chapter 08

入力フォームを作る

アプリを作っていると、新規登録画面とかログイン画面とかを作る際に入力フォームのUIをよく作ります。今回はそんな入力フォームをFlutterで作る方法です。

動画としては以下。

まずはこの状態からスタート

前回はTextの装飾をしたので、ColumnやTextがありましたが、それらを整理して以下のようにします。

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('KBOYのFlutter大学'),
      ),
      body: Container(
        width: double.infinity,
      ),
    );
  }

ここにTextFieldなどを追加して、入力フォームを作っていきます。

まずはdocumentを確認

Flutter Documentationの中のcookbookを選択します。

Formsの中のCreate and style a text fieldを参考に進めていきます。

TextFieldを配置してみる

ContainerのchildとしてTextFieldを入れてみましょう。

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('KBOYのFlutter大学'),
      ),
      body: Container(
        width: double.infinity,
        child: TextField(),
      ),
    );
  }

すると、以下のようになります。ラインが入った部分の上に入力できるようになりました。

TextFieldをタップしてみると、以下のようにキーボードが出ます。

InputDecoration

https://flutter.dev/docs/cookbook/forms/text-input

↑のcookbookに以下のような例文があります。

TextField(
  decoration: InputDecoration(
    border: InputBorder.none,
    hintText: 'Enter a search term'
  ),
);

これをそのまま、先ほど作ったTextFieldに適用しましょう。

そしたらこうなります↓

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('KBOYのFlutter大学'),
      ),
      body: Container(
        width: double.infinity,
        child: TextField(
          decoration: InputDecoration(
            border: InputBorder.none,
            hintText: 'Enter a search term',
          ),
        ),
      ),
    );
  }

↑を見たらわかるように、下のラインが消えて、Enter a search termという文字が薄く表示されるようになりました。

border: InputBorder.none,でラインを消して、hintText: 'Enter a search term',によって何も入力してない時のガイドの薄字を表示しています。後者のhintTextは、例えば名前の入力欄で「山田太郎」とかみたいに入力をイメージしやすくするために使われたりするものです。

TextFormFieldとは?

https://flutter.dev/docs/cookbook/forms/validation

↑を見たら書いてあるのですが、TextFormFieldにはvalidatorというプロパティがあり、条件に合わない入力に対して赤文字でエラーを出す機能を備えています。ただ、ちょっと実装が複雑なので今回は説明を省略します。

オートフォーカスの方法

次に、cookbookのFormsの中にある以下を見てみましょう。

https://flutter.dev/docs/cookbook/forms/focus
TextField(
  autofocus: true,
);

と言うサンプルがあります。これを先ほどの画面に反映してビルドし直すとどうなるのでしょうか?

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('KBOYのFlutter大学'),
      ),
      body: Container(
        width: double.infinity,
        child: TextField(
          decoration: InputDecoration(
            border: InputBorder.none,
            hintText: 'Enter a search term',
          ),
          autofocus: true,
        ),
      ),
    );
  }

↓このようにビルド直後にキーボードが出てる状態になりました。autofocusしとくと、画面を開いた瞬間にそこにフォーカスがあたり、キーボードが開きます。

使用ケースとしては、複数のTextFieldがあって、画面を開いた瞬間に一番上のTextFieldの入力モードになりキーボードが開くという実装するのに使えそうです。

FocusNodeを使ってフォーカス

引き続き↓のドキュメントを見ていきます。

https://flutter.dev/docs/cookbook/forms/focus

FocusNodeというのを使って、ボタンを押したらフォーカスが当たる、みたいな実装をしてみましょう。

↓こんな感じで、Columnの中にTextFieldが2つある、みたいな状態を作りましょう。

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('KBOYのFlutter大学'),
      ),
      body: Container(
        width: double.infinity,
        child: Column(
          children: [
            TextField(),
            TextField(),
          ],
        ),
      ),
    );
  }

では、ボタンを押したら、下のTextFieldの方にフォーカスが当たる、という実装をしてみましょう。

Columnの中に、追加で、RaisedButtonを入れます。

        child: Column(
          children: [
            TextField(),
            TextField(),
            RaisedButton(),
          ],
        ),

RaisedButtonにプロパティを追加していきます。

RaisedButton(
  child: Text('フォーカス'),
  onPressed: () {
    // TODO: ここにフォーカスするためのコードを書く
  },
)

まずは、childにText、そしてonPressedを追加します。このあと、onPressedの中身を書いていきましょう。

https://flutter.dev/docs/cookbook/forms/focus

↑をみながら進めます。

  final myFocusNode = FocusNode();

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('KBOYのFlutter大学'),
      ),
      body: Container(
        width: double.infinity,
        child: Column(
          children: [
            TextField(),
            TextField(),
            RaisedButton(
              child: Text('フォーカス'),
              onPressed: () {
                // TODO: ここにフォーカスするためのコードを書く
              },
            ),
          ],
        ),
      ),
    );
  }

このように、buildの上のあたりに(_MyHomePageStateの中ならどこでもいいけど)、

final myFocusNode = FocusNode();

を定義します。

そして、TextFieldにfocusNodeを渡します。

        child: Column(
          children: [
            TextField(),
            TextField(
              focusNode: myFocusNode,
            ),
            RaisedButton(
              child: Text('フォーカス'),
              onPressed: () {
                // TODO: ここにフォーカスするためのコードを書く
              },
            ),
          ],
        ),

↑のように、フォーカスを当てたい2番目のTextFieldに渡します。

で、最後に、ボタンを押した時にこのmyFocusNodeが反応するようにするために、onPressedの中に書いていきます。

onPressed: () {
  // TODO: ここにフォーカスするためのコードを書く
  myFocusNode.requestFocus()
},

これで、ボタンを押すと、上から2番目のTextFieldにフォーカスが当たって、したからキーボードが出てくると思います。やってみましょう。

できましたね!!

TextFieldの値を取得する(onChanged)

次は実践的な例をやっていきましょう。ドキュメントは以下を参考に進めます。

https://flutter.dev/docs/cookbook/forms/text-field-changes

まず、先ほどの画面を以下のように変更してみましょう。名前と趣味を入力して新規登録するアプリです。

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('KBOYのFlutter大学'),
      ),
      body: Container(
        width: double.infinity,
        child: Column(
          children: [
            TextField(
              decoration: InputDecoration(
                hintText: '名前',
              ),
            ),
            TextField(
              decoration: InputDecoration(
                hintText: '趣味',
              ),
            ),
            RaisedButton(
              child: Text('新規登録'),
              onPressed: () {
                // TODO: 新規登録
              },
            ),
          ],
        ),
      ),
    );
  }

では、1つめのTextFieldのプロパティにonChangedを追加して、文字が入力されたときの値を取得してみましょう。

            TextField(
              decoration: InputDecoration(
                hintText: '名前',
              ),
              onChanged: (text) {
                // TODO: ここで取得したtextを使う
              },
            ),

先ほどのfocusNodeとかと同じ要領で、build関数のちょい上あたりに、String name;という変数を用意します。

全体としては↓みたいな感じ。

onChangedの中で、nameの変数にtextを突っ込んでいます。具体的なイメージとしては、ここでname変数に格納したものを、あとで新規登録ボタン押した時にサーバーに送信する、みたいな感じ。

  String name;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('KBOYのFlutter大学'),
      ),
      body: Container(
        width: double.infinity,
        child: Column(
          children: [
            TextField(
              decoration: InputDecoration(
                hintText: '名前',
              ),
              onChanged: (text) {
                // TODO: ここで取得したtextを使う
                name = text;
              },
            ),
            TextField(
              decoration: InputDecoration(
                hintText: '趣味',
              ),
            ),
            RaisedButton(
              child: Text('新規登録'),
              onPressed: () {
                // TODO: 新規登録
              },
            ),
          ],
        ),
      ),
    );
  }

TextFieldの値を取得する(TextEditingController)

引き続き、このドキュメントをみていきます。TextFieldに入力された値を取得する方法の二つ目をやっていきます。

https://flutter.dev/docs/cookbook/forms/text-field-changes

2. Use a TextEditingControllerの項目を真似していきます。

先ほどの要領で、final myController = TextEditingController(); を書きます。そして、今度は二つ目のTextFieldのcontorollerプロパティにmyControllerを渡してみましょう。

  final myController = TextEditingController();

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('KBOYのFlutter大学'),
      ),
      body: Container(
        width: double.infinity,
        child: Column(
          children: [
            TextField(
              decoration: InputDecoration(
                hintText: '名前',
              ),
              onChanged: (text) {
                // TODO: ここで取得したtextを使う
                name = text;
              },
            ),
            TextField(
              controller: myController,
              decoration: InputDecoration(
                hintText: '趣味',
              ),
            ),
            RaisedButton(
              child: Text('新規登録'),
              onPressed: () {
                // TODO: 新規登録
              },
            ),
          ],
        ),
      ),
    );
  }

みたいな感じ。で、RaisedButtonでボタンを押した時に、以下のようにTextFieldに入力された値を取得できます。

            RaisedButton(
              child: Text('新規登録'),
              onPressed: () {
                final hobbyText = myController.text;
              },
            ),

このように取得したやつを、実戦ではサーバーに送信して、新規登録すれば良いですね。

まとめ

今日の記事は以上です。動画では、プラスアルファで、TextFieldの文字列をダイアログに表示する方法も紹介しています。よければ見てみてください。

参考文献

https://flutter.dev/docs/cookbook/forms

https://www.youtube.com/watch?v=eXc-ej0CBBM