Flutterアプリケーション開発概論

【チーム作成UI】FloatingActionButtonからLINE風ボトムシートを表示する

24LINE風チームタスク管理アプリを作りながら、ログイン・データベース・権限管理を学ぶ
FlutteriOSAndroidMacOSWindows基礎から学ぶ開発アプリ開発

このページでやること

このページでは、右下の + ボタンを押したときに、チーム作成用のボトムシートを表示します。

ボトムシートとは、画面の下から出てくる入力画面です。

今回作る流れはこれです。

トーク一覧画面
↓
右下の+ボタンを押す
↓
下からチーム作成画面が出る
↓
チーム名を入力する
↓
作成する

LINE風に、白背景・角丸・シンプルな入力欄で作ります。


今日のゴール

右下の FloatingActionButton を押すと、次のような画面が下から出るようにします。

新しいチームを作成

チーム名

[作成する]

FloatingActionButton とは、画面右下に表示する丸いボタンです。

今回の画面では、+ ボタンとして使います。


このページで出てくる単語

単語一言説明
FloatingActionButton画面右下に出す丸いボタン
showModalBottomSheet下から画面を出す命令
ボトムシート画面下から出てくる小さな画面
TextEditingController入力欄の文字を管理する道具
StatefulBuilderボトムシート内だけ画面更新するための部品
MediaQuery画面サイズやキーボード状態を知る道具
viewInsets.bottomキーボードの高さを考慮する設定
isScrollControlledボトムシートをキーボードに合わせて広げる設定

npmや環境変数はこのページで必要?

このページでは、npmは使いません。

環境変数も設定しません。

必要なのは、すでにあるFlutterのコードだけです。

main.dartを開く
↓
FloatingActionButtonを置く
↓
showCreateTeamSheet()を作る
↓
保存する
↓
flutter runで確認する

Step 1:main.dartを開く

次のファイルを開きます。

lib/main.dart

ターミナルから開く場合は、次を実行します。

code lib/main.dart

code が使えない場合は、VS Codeの左側から lib/main.dart を開いてください。


Step 2:TeamListPageを探す

main.dart の中で、次を検索します。

class TeamListPage

VS Codeなら、次で検索できます。

command + F

この TeamListPage が、トーク一覧画面です。

ここに右下の + ボタンと、ボトムシートを追加します。


Step 3:チーム名入力用Controllerを確認する

_TeamListPageState の中に、次があるか確認します。

final teamNameController = TextEditingController();

これは、チーム名入力欄の文字を管理する道具です。

もしまだなければ追加します。

class _TeamListPageState extends State<TeamListPage> {
  final teamNameController = TextEditingController();

  bool isCreating = false;
  String? errorText;

  @override
  void dispose() {
    teamNameController.dispose();
    super.dispose();
  }

  // この下に処理を書いていきます
}

dispose() は、使い終わったControllerを片付ける処理です。


Step 4:ボトムシートを開く関数を作る

_TeamListPageState の中に、次の関数を追加します。

Future<void> showCreateTeamSheet() async {
  teamNameController.clear();
  errorText = null;

  await showModalBottomSheet<void>(
    context: context,
    isScrollControlled: true,
    backgroundColor: Colors.white,
    shape: const RoundedRectangleBorder(
      borderRadius: BorderRadius.vertical(
        top: Radius.circular(20),
      ),
    ),
    builder: (context) {
      return StatefulBuilder(
        builder: (context, setSheetState) {
          return Padding(
            padding: EdgeInsets.only(
              left: 20,
              right: 20,
              top: 20,
              bottom: MediaQuery.of(context).viewInsets.bottom + 20,
            ),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                const Text(
                  '新しいチームを作成',
                  style: TextStyle(
                    fontSize: 20,
                    fontWeight: FontWeight.w900,
                    color: AppColors.text,
                  ),
                ),
                const SizedBox(height: 12),
                AppTextField(
                  controller: teamNameController,
                  label: 'チーム名',
                ),
                if (errorText != null) ...[
                  const SizedBox(height: 12),
                  ErrorBox(message: errorText!),
                ],
                const SizedBox(height: 16),
                FilledButton(
                  onPressed: isCreating
                      ? null
                      : () async {
                          await createTeam(setSheetState);
                        },
                  child: Text(isCreating ? '作成中...' : '作成する'),
                ),
              ],
            ),
          );
        },
      );
    },
  );
}

これで、下からチーム作成画面を表示できます。


Step 5:showModalBottomSheetの意味

一番大事なのは、この部分です。

await showModalBottomSheet<void>(
  context: context,
  isScrollControlled: true,
  backgroundColor: Colors.white,
  shape: const RoundedRectangleBorder(
    borderRadius: BorderRadius.vertical(
      top: Radius.circular(20),
    ),
  ),
  builder: (context) {
    return ...
  },
);

短く言うと、こうです。

下から出る画面を表示する
↓
背景を白にする
↓
上だけ角丸にする
↓
中身をbuilderで作る

shape は、ボトムシートの形を決める設定です。

今回は、上の角だけ丸くしています。


Step 6:キーボードに隠れないようにする

この部分を見ます。

bottom: MediaQuery.of(context).viewInsets.bottom + 20,

これは、スマホでキーボードが出たときに、入力欄が隠れないようにするための設定です。

キーボードが出る
↓
ボトムシートの下余白を増やす
↓
入力欄が見える

初心者の方は、ここはそのまま使えばOKです。


Step 7:StatefulBuilderを使う理由

この部分です。

return StatefulBuilder(
  builder: (context, setSheetState) {
    return ...
  },
);

StatefulBuilder は、ボトムシートの中だけを更新するために使います。

たとえば、チーム名が空のまま「作成する」を押したとき、エラーを出したいです。

チーム名が空
↓
作成する
↓
エラーを表示する

このようなボトムシート内の表示変更には、setSheetState を使います。


Step 8:作成ボタンとcreateTeamをつなげる

ボトムシートの中のボタンは、この形です。

FilledButton(
  onPressed: isCreating
      ? null
      : () async {
          await createTeam(setSheetState);
        },
  child: Text(isCreating ? '作成中...' : '作成する'),
),

意味はこうです。

作成中ではない
↓
ボタンを押せる
↓
createTeam() が動く

作成中
↓
ボタンを押せない
↓
文字が「作成中...」になる

createTeam(setSheetState) は、チームをFirestoreに保存する処理です。


Step 9:まだcreateTeamがない場合

まだ createTeam() を作っていない場合は、仮で次を入れてください。

Future<void> createTeam(StateSetter setSheetState) async {
  final teamName = teamNameController.text.trim();

  if (teamName.isEmpty) {
    setSheetState(() {
      errorText = 'チーム名を入力してください。';
    });
    return;
  }

  setSheetState(() {
    errorText = '次の処理でFirestoreにチームを保存します。';
  });
}

この仮コードでは、Firestoreには保存しません。

まずは、ボトムシートが表示され、入力チェックが動くことだけ確認します。

すでにチーム作成処理がある場合は、この仮コードは不要です。


Step 10:FloatingActionButtonを追加する

Scaffold の中に、次を追加します。

floatingActionButton: FloatingActionButton(
  onPressed: showCreateTeamSheet,
  child: const Icon(Icons.add),
),

完成イメージはこのようになります。

return Scaffold(
  backgroundColor: AppColors.bg,
  appBar: AppBar(
    title: const Text('トーク'),
  ),
  body: ...,
  floatingActionButton: FloatingActionButton(
    onPressed: showCreateTeamSheet,
    child: const Icon(Icons.add),
  ),
);

onPressed: showCreateTeamSheet にすることで、右下の + を押したときにボトムシートが開きます。


Step 11:保存する

main.dart を保存します。

Macの場合:

command + S

Windowsの場合:

Ctrl + S

Step 12:実行する

ターミナルで実行します。

flutter run

すでに起動している場合は、ターミナルで r を押します。

r

うまく反映されない場合は、R でホットリスタートします。

R

Step 13:画面を確認する

ログイン後、トーク一覧画面を開きます。

右下に + ボタンが出ているか確認します。

右下に+ボタン

次に、+ を押します。

下から、次のような画面が出ればOKです。

新しいチームを作成

チーム名

[作成する]

Step 14:入力チェックを確認する

何も入力せずに「作成する」を押してみます。

次のエラーが表示されればOKです。

チーム名を入力してください。

次に、チーム名を入力します。

開発チーム

「作成する」を押して、ボトムシートが閉じる、または仮メッセージが表示されればOKです。


1か所だけ変更してみる

ボトムシートのタイトルを変えてみます。

この部分を探します。

'新しいチームを作成',

次のように変えてみます。

'チームを追加',

保存して、ホットリロードします。

r

+ ボタンを押して、タイトルが変わっていれば成功です。


よくあるエラーと直し方

エラー原因直し方
showCreateTeamSheet isn't defined関数がない_TeamListPageState の中に作る
teamNameController isn't definedControllerがないfinal teamNameController = TextEditingController(); を追加
StateSetter isn't definedMaterialのimportがないimport 'package:flutter/material.dart'; を確認
createTeam isn't defined作成処理がない仮の createTeam() を作る
ボトムシートが開かないFABのonPressedが空onPressed: showCreateTeamSheet にする
エラーが表示されないsetSheetState を使っていないボトムシート内は setSheetState を使う
キーボードで隠れるbottom余白がないviewInsets.bottom + 20 を入れる

ボトムシートが開かないとき

まず、FloatingActionButton を確認します。

これはNGです。

floatingActionButton: FloatingActionButton(
  onPressed: () {},
  child: const Icon(Icons.add),
),

これだと、押しても何も起きません。

正しくはこれです。

floatingActionButton: FloatingActionButton(
  onPressed: showCreateTeamSheet,
  child: const Icon(Icons.add),
),

エラー表示が変わらないとき

ボトムシートの中では、通常の setState ではなく、setSheetState を使います。

setSheetState(() {
  errorText = 'チーム名を入力してください。';
});

これで、ボトムシート内のエラー表示が更新されます。


最短作業まとめ

読むのが大変な人は、ここだけ見てください。

1. Controllerを用意

final teamNameController = TextEditingController();

2. ボトムシートを開く関数を作る

Future<void> showCreateTeamSheet() async {
  teamNameController.clear();
  errorText = null;

  await showModalBottomSheet<void>(
    context: context,
    isScrollControlled: true,
    backgroundColor: Colors.white,
    shape: const RoundedRectangleBorder(
      borderRadius: BorderRadius.vertical(
        top: Radius.circular(20),
      ),
    ),
    builder: (context) {
      return StatefulBuilder(
        builder: (context, setSheetState) {
          return Padding(
            padding: EdgeInsets.only(
              left: 20,
              right: 20,
              top: 20,
              bottom: MediaQuery.of(context).viewInsets.bottom + 20,
            ),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                const Text(
                  '新しいチームを作成',
                  style: TextStyle(
                    fontSize: 20,
                    fontWeight: FontWeight.w900,
                    color: AppColors.text,
                  ),
                ),
                const SizedBox(height: 12),
                AppTextField(
                  controller: teamNameController,
                  label: 'チーム名',
                ),
                if (errorText != null) ...[
                  const SizedBox(height: 12),
                  ErrorBox(message: errorText!),
                ],
                const SizedBox(height: 16),
                FilledButton(
                  onPressed: isCreating
                      ? null
                      : () async {
                          await createTeam(setSheetState);
                        },
                  child: Text(isCreating ? '作成中...' : '作成する'),
                ),
              ],
            ),
          );
        },
      );
    },
  );
}

3. 右下の+ボタンにつなげる

floatingActionButton: FloatingActionButton(
  onPressed: showCreateTeamSheet,
  child: const Icon(Icons.add),
),

4. 保存して実行

flutter run

チェックリスト

□ main.dartを開いた
□ TeamListPageを探した
□ teamNameControllerを用意した
□ disposeでControllerを片付けた
□ showCreateTeamSheet()を作った
□ showModalBottomSheetを書いた
□ 上だけ角丸にした
□ AppTextFieldでチーム名入力欄を作った
□ ErrorBoxを表示できるようにした
□ FilledButtonを置いた
□ FloatingActionButtonを追加した
□ onPressedをshowCreateTeamSheetにした
□ 保存した
□ flutter runで確認した
□ +ボタンからボトムシートが出た

ミニ確認問題

Q1. FloatingActionButton は何をするWidgetですか?

回答

画面右下に丸いボタンを表示するWidgetです。

今回なら、チーム作成用の + ボタンとして使います。


Q2. showModalBottomSheet は何をする命令ですか?

回答

画面の下から、入力用の小さな画面を表示する命令です。


Q3. ボトムシート内の表示を更新するときは何を使いますか?

回答

setSheetState を使います。

エラー表示や「作成中…」の表示を更新するときに使います。


Q4. このページでnpmや環境変数は必要ですか?

回答

必要ありません。

このページでは、右下の + ボタンからボトムシートを表示するUIを作るだけです。


このページのまとめ

  • FloatingActionButton は、右下の丸い + ボタン。
  • showModalBottomSheet で、画面下から入力画面を出せる。
  • ボトムシートは、チーム作成UIに向いている。
  • isScrollControlled: true を入れると、キーボード表示に対応しやすい。
  • viewInsets.bottom を使うと、入力欄がキーボードに隠れにくい。
  • ボトムシート内の更新には setSheetState を使う。
  • このページではnpmや環境変数は不要。
  • まずは + を押して、下から入力画面が出ることを確認する。

次のページでやること

次のページでは、チームをタップしてタスク一覧画面へ移動します。

teamIdteamName を渡して、そのチーム専用のタスク一覧画面を開きます。

教材トップへ戻る