見出し画像

プログラミング初心者が始めるFlutter学習③-1

こんにちは。PHR事業開発部の大岡です。
私の回は、Flutterのソースコードを解説していくシリーズになっています。
これまでの記事はこちらです。

今回は、前回までに作った入力フォームの他に、ピッカーやラジオボタンを使って、いくつかのデータを入力できるようにします。
入力したデータをアプリ内で保存するところまで行きたかったんですが、入力画面だけで今回は終わりそうです。。
ソースコードはこちらです。
https://github.com/psp-engineer/data_input

■データ入力画面を作る

氏名(入力フォーム)、性別(ラジオボタン)、出身地(ドラムロールピッカー)の入力欄と「次へ」ボタンを作成する

画面はこのようになります。

データ入力画面

分量が増えてきたので、見やすくするために、入力するパーツごとにメソッドにしてみました。

 // 画面描画を書いているところ
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 一番上のタイトルが表示されているところ
      appBar: AppBar(
        title: Text(widget.title),
      ),

      // 画面のメインの部分
      body: Align(
        // 画面の真ん中に位置させる
        alignment: Alignment.center,
        // 画面に収まりきらない時にスクロールできるようにする
        child: SingleChildScrollView(
          // Widgetを縦に並べる
          child: Column(
            children: <Widget>[
              // 氏名
              nameInputForm(),
              // 性別
              genderInputForm(),
              // 出身地
              regionInputForm(),
              // 次へボタン
              nextButton(),
            ],
          ),
        ),
      ),
    );
  }

このコードの下でnameInputForm()、genderInputForm()などの具体的な中身を書いていきます。
また、入力されたデータはそれぞれ_name(氏名)、_gender(性別)、_region(出身地)に格納されるようにします。
nameInputForm()とnextButton()は前回までに作った入力フォームをそのまま使っているので省略します。

genderInputForm()
ラジオボタンで性別を選択する部分です。

  Widget genderInputForm() {
    return Column(
      children: [
        const Align(
            alignment: Alignment.centerLeft,
            child: Text(
              '性別',
              style: TextStyle(fontSize: 18.0),
            )),
        // 子Widgetの周りに余白を作る
        Padding(
          // 余白の幅やどこに付けるかを指定
          padding: const EdgeInsets.only(bottom: 25.0),
          // 子Widgetを横に並べる
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              // ラジオボタン
              Radio(
                // このラジをボタンを押した時にonChangedでvalueに入ってくる値
                value: Gender.male,
                // ここで設定した値(ここでは_gender)がvalueに設定した値(ここではGender.maile)と一致する時、このラジオボタンがアクティブになる
                groupValue: _gender,
                // ボタンのON、OFFが変わるたびにこの関数を実行
                onChanged: (Gender? value) {
                  if (value != null) {
                    _setGender(value);
                  }
                },
              ),
              const Text('男'),
              Radio(
                value: Gender.female,
                groupValue: _gender,
                onChanged: (Gender? value) {
                  if (value != null) {
                    _setGender(value);
                  }
                },
              ),
              const Text('女'),
              Radio(
                value: Gender.other,
                groupValue: _gender,
                onChanged: (Gender? value) {
                  if (value != null) {
                    _setGender(value);
                  }
                },
              ),
              const Text('その他'),
            ],
          ),
        ),
      ],
    );
  }

Padding():子Widgetの周りに余白を作る。上下左右の余白を個別に設定できたり、逆に全て同じ幅で一括して設定できたりする。
Row():children:にウィジェットのリストを設定すると、リストの最初のウィジェットから順に横に並べてくれる。
Radio():ラジオボタンを作る。onChanged:で、押した時にさせたい動作を関数で設定できる。ラジオボタン自体を作るだけのウィジェットなので、項目名などは他のウィジェットと組み合わせて表示させる必要がある。

ここで、Radio()ウィジェットのvalueに指定しているGender.maleは、enumというクラスです。
列挙型とも呼ばれ、関連する定数を一括りにするためのクラスです。今回は性別の選択肢をまとめてGenderというenumを作りました。
これだけだと必要性があまり感じられないかもしれませんが、定数として定義することで、スペルミスしたときに教えてくれたり、それぞれの定数に引数として属性のようなものを設定できたりします。

enum Gender {
  male,
  female,
  other,
  none,
}

今回の場合は、例えばラジオボタンの「男」を選択すると下記の_setGender(value);が実行されてGender.maleが_genderに入ります。

void _setGender(Gender selectedGender) {
  // 選択された性別のenumを_genderに代入
  setState(() {
    _gender = selectedGender;
  });
}

regionInputForm()
ドラムロールピッカーなどと呼ばれる、選択肢を上下にくるくる動かして選択するピッカーで、出身地を選択する部分です。

Widget regionInputForm() {
  return Column(
    children: [
      const Align(
          alignment: Alignment.centerLeft,
          child: Text(
            '出身地',
            style: TextStyle(fontSize: 18.0),
          )),
      // 子Widgetの周りに余白を作る
      Padding(
        // 余白の幅やどこに付けるかを指定
        padding: const EdgeInsets.only(bottom: 25.0),
        child: TextFormField(
          // ピッカーで選択した地域をTextFormFieldに表示させるためのcontrollerを設定
          controller: _controller,
          decoration: const InputDecoration(
            // 何も入力されていない時に表示するヒントテキスト
            hintText: '関東',
          ),
          onTap: () {
            // タップした時にキーボードが出ないようにする
            FocusScope.of(context).requestFocus(FocusNode());
            // ピッカー(_showDialogはさらに下で定義)
            _showDialog(
              CupertinoPicker(
                itemExtent: 32.0,
                // ピッカーの値が変化するたびに_setRegionを実行
                onSelectedItemChanged: (int selectedItem) {
                  _setRegion(selectedItem);
                },
                // ピッカーの項目を設定
                children: List<Widget>.generate(_regionNames.length, (int index) {
                  return Center(
                    child: Text(
                      _regionNames[index],
                    ),
                  );
                }),
              ),
            );
          },
        ),
      ),
    ],
  );
}

CupertinoPicker():iOSスタイルのピッカーを作る(cupertinoはiOSスタイルという意味)。_showDialog()で使っているshowCupertinoModalPopupと一緒に使うことで画面下部にピッカーを表示させることができる。cupertino系のウィジェットは、Androidアプリでも動きますが、本来iOSで動作するアプリ向けのようです。
List<Widget>.generate:下記の_regionNamesに入っている地方のListの項目を、上から順に並べて画面上でリストにする。このリストがピッカーの選択項目になる。

// ピッカーの選択肢として表示する項目のList
final List<String> _regionNames = <String>[
  '北海道',
  '東北',
  '関東',
  '中部',
  '近畿',
  '中国・四国',
  '九州',
];

TextFormField()をタップしたらピッカーが出てくるようにしたいので、TextFormField()のonTap: に_showDialog()としてピッカーを設定しています。

void _showDialog(Widget child) {
  showCupertinoModalPopup<void>(
      context: context,
      builder: (BuildContext context) => Container(
            height: 216,
            // 余白の幅やどこに付けるかを指定
            padding: const EdgeInsets.only(top: 6.0),
            // Widgetの内側に余白を作る
            margin: EdgeInsets.only(
              bottom: MediaQuery.of(context).viewInsets.bottom,
            ),
            // ピッカーの背景色を指定
            color: Colors.white70,
            // 子Widgetがスマホ画面からはみ出さないようにする
            child: SafeArea(
              // 上部だけSafeAreaを無効にする
              top: false,
              child: child,
            ),
          ));
}

showCupertinoModalPopup():画面下部から上にスライドしてポップアップが表示される。このポップアップの中にピッカーを表示している。
SafeArea():端末のノッチ(内カメラの部分)や、OSのステータスバーなどによって意図せず画面が隠れることを防ぐために、子Widgetを、それらを避けたエリアに配置させる。

これで入力画面ができました。文字や選択項目を入力できるようになったはずです。

入力画面のウィジェットなどについて説明するだけで結構な長さになってしまいました、、続きはまた次回です。
どんどん進みが遅くなっていますが、なんとか続けていきたいと思います。
誰かの理解の助けになれば嬉しいです。

次回は、入力したデータを次のページで表示させるのと、入力データの保存までいけたらいいなと思っています。