【Flutter】画面レイアウトのコーディング手順について(後編)

モバイルアプリ開発エンジニアの木村です。

弊社では見積書アプリ検索ランキング1位の見積・請求書作成アプリ『ジムー』をはじめとするモバイルアプリ開発を行っており、私は主にアプリの開発を担当しております。

本記事は、私がFlutterで画面レイアウトをコーディングする際の手順を共有していく記事の後編になります。

まだ前編を見ていない方は「【Flutter】画面レイアウトのコーディング手順について(前編)」から見ていただけると幸いです。

前回の振り返り

前編では以下のオレンジ色の枠で作成したレイアウトの大枠が完成しました。

後編ではオレンジ色の枠にしていた箇所にそれぞれ文字や入力欄、ボタンを設定してログイン画面のデザインを完成させていきます。

レイアウトのコーディング

それでは文字や入力フォーム、ボタンについてWidgetの種類ごとに解説とコーディングを進めていきます。

テキストを表示(Text Widget)

「ログイン」などの文字を表示しているテキスト部分を設定していきます。

文字は「Text」Widgetを使うことで表示できますので、ひとつ目のオレンジの部分を以下のコードに置き換えてみましょう。

// 【修正前】
Container(
  margin: const EdgeInsets.all(5),
  height: 40,
  width: 200,
  color: Colors.orangeAccent,
),
// 【修正後】
Text("ログイン"),

置き換えると以下の画面が表示されます。

このままでは文字のサイズや太さが作成したいデザインと異なるので、次は「Text」Widgetにスタイルを設定していきます。

Text(
   "ログイン",
   style: TextStyle(fontSize: 35, fontWeight: FontWeight.w900),
),

「fontSize」は文字の大きさ、「fontWeight」は文字の太さを設定することができ、反映すると以下のようなレイアウトになります。

Flutter_作成レイアウト_3

同じ対応を「ログインID」「パスワード」「パスワードをお忘れですか?」にも追加していきます。(全体のソースコードは最後に記載します。)

文字の色の設定は「TextStyle」の「color」で、下線は「TextStyle」の「decoration」に「TextDecoration.underline」を追加することで設定できます。

Text(
   "パスワードをお忘れですか?",
   style: TextStyle(
      fontSize: 18,
      fontWeight: FontWeight.w400,
      color: Colors.blueAccent,
      decoration: TextDecoration.underline,
      decorationThickness: 2.0
   ),
),

入力フォームを表示する(TextField)

次にログインIDとパスワードを入力するフォームを設定していきます。

入力フォームは「TextField」Widgetを使うことで表示できますので「ログインID」の下にあるオレンジのエリアのWidgetを以下のコードに置き換えてみましょう。

TextField()

置き換えると以下のような表示になります。

線だけ表示されていますが、その部分をタップするとキーボードが表示されて文字を入力することができます。

しかし、このままではキーボードを表示した際に、以下のようなエラーが出力されて画面上では黄色と黒のエラーが表示される場合があります。

======== Exception caught by rendering library =====================================================
The following assertion was thrown during layout:
A RenderFlex overflowed by 41 pixels on the bottom.

これは元々のレイアウトにキーボードが追加され、画面の表示スペースに収まりきれなくなったためのエラーになります。

このエラーの対策としては、”画面をスクロールできるようにして、画面の表示スペースを大きくする”という方法があります。

画面のスクロールは「SingleChildScrollView」Widgetを用いて対応可能ですので、body配下を全てこのWidgetでラップしていきましょう。

@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SingleChildScrollView( // ここに追加
        child: Container(
          margin: const EdgeInsets.only(left: 30, top: 100, right: 30, bottom: 50),
   ・・・

これでキーボードを表示しても先ほどのエラーが発生しなくなりました!

では本題に戻りまして、「TextField」Widgetのデザイン調整を進めていきます。

デザインの調整は「decoration」で設定でき、こちらに枠線の設定を追加します。

TextField(
   decoration: InputDecoration(
   border: OutlineInputBorder(),
   ),
),

追加すると、画面レイアウトは以下のようになります。

これで入力フォームは完成になります!

ボタンを表示する(Button)

最後にログインボタンと新規登録ボタンの設定を進めていきます。

ボタンは「ElevatedButton」Widgetを使うことで表示できますので、残っているオレンジのエリアのWidgetを以下のコードに置き換えてみましょう。

Container(
   margin: const EdgeInsets.all(5),
   height: 50,
   width: double.infinity,
   child: ElevatedButton(
      child: const Text('ログインする'),
      onPressed: () {/*タップされた際の処理*/},
   ),
),

今回もデフォルトでは文字のサイズが小さいのと、新規登録ボタンの色が異なるので調整していきます。

文字のサイズは前章の「Text Widget」と同様に「TextStyle」に設定していき、ボタンの色は「ElevatedButton」のstyleで設定していきます。

ボタンの枠線は「ElevatedButton.styleFrom」の「side」パラメータに「BorderSide」Widgetを設定することで変更できます。

ボタン自体の色は「foregroundColor」と「backgroundColor」に変更したい色を設定することで変更できます。

それぞれのレイアウトを設定したコードは以下になります。

Container(
   margin: const EdgeInsets.all(5),
   height: 50,
   width: double.infinity,
   child: ElevatedButton(
      style: ElevatedButton.styleFrom(
         side: const BorderSide(color: Colors.blueAccent),
         foregroundColor: Colors.blueAccent,
         backgroundColor: Colors.white
      ),
      child: const Text(
          '新規登録',
         style: TextStyle(fontSize: 22, fontWeight: FontWeight.w500),
      ),
      onPressed: () {/*タップされた際の処理*/},
   ),
),

まとめ

2つの記事を通して画面のレイアウトを作成してきましたが、いかがでしたでしょうか?

個人的には、Flutterのレイアウト作成は直感的で実装しやすいと感じています。
本記事では数種類のWidgetしか紹介できておりませんが、他にも数多くのWidgetが用意されています。

作りたいと思う画面レイアウトの大半は用意されているWidgetを組み合わせることで作成できますのでぜひ、様々なWidgetを調べたり見つけてみてください!

最後に完成した画面とコードを記載いたします。

@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SingleChildScrollView(
        child: Container(
          margin: const EdgeInsets.only(
            left: 30, 
            top: 100, 
            right: 30, 
            bottom: 50
          ),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text(
                    "ログイン",
                    style: TextStyle(fontSize: 35, fontWeight: FontWeight.w900),
                  ),
                ],
              ),
              const SizedBox(
                height: 40,
              ),
              const Text(
                "ログインID",
                style: TextStyle(fontSize: 25, fontWeight: FontWeight.w500),
              ),
              const SizedBox(
                height: 5,
              ),
              const TextField(
                decoration: InputDecoration(
                  border: OutlineInputBorder(),
                ),
              ),
              const SizedBox(
                height: 20,
              ),
              const Text(
                "パスワード",
                style: TextStyle(fontSize: 24, fontWeight: FontWeight.w400),
              ),
              const SizedBox(
                height: 5,
              ),
              const TextField(
                decoration: InputDecoration(
                  border: OutlineInputBorder(),
                ),
              ),
              const Row(
                mainAxisAlignment: MainAxisAlignment.end,
                children: [
                  Text(
                    "パスワードをお忘れですか?",
                    style: TextStyle(
                        fontSize: 18,
                        fontWeight:
                        FontWeight.w400,
                        color: Colors.blueAccent,
                        decoration: TextDecoration.underline,
                        decorationThickness: 2.0
                    ),
                  ),
                ],
              ),
              const SizedBox(
                height: 40,
              ),
              Container(
                margin: const EdgeInsets.all(5),
                height: 50,
                width: double.infinity,
                child: ElevatedButton(
                  style: ElevatedButton.styleFrom(
                      foregroundColor: Colors.white,
                      backgroundColor: Colors.blueAccent
                  ),
                  child: const Text(
                    'ログインする',
                    style: TextStyle(fontSize: 22, fontWeight: FontWeight.w500),
                  ),
                  onPressed: () {/*タップされた際の処理*/},
                ),
              ),
              Container(
                margin: const EdgeInsets.all(5),
                height: 50,
                width: double.infinity,
                child: ElevatedButton(
                  style: ElevatedButton.styleFrom(
                      side: const BorderSide(color: Colors.blueAccent),
                      foregroundColor: Colors.blueAccent,
                      backgroundColor: Colors.white
                  ),
                  child: const Text(
                    '新規登録',
                    style: TextStyle(fontSize: 22, fontWeight: FontWeight.w500),
                  ),
                  onPressed: () {/*タップされた際の処理*/},
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }