🦍

【Flutter】BottomSheet を使ってテキスト入力する

2023/12/04に公開

BottomSheet とは

下からシュッと出てくるカードのような Widget です。
ダイアログなどでよくあるモーダルですね。(モーダル:背景が暗くなり対象がハイライトされる機能)
基本的には showModalBottomSheet()で呼べば良いと思います。
builder の中身は BottomSheet Widget もありますが、Container でカスタマイズしても良いです。
個人的には後者の方が扱いやすいです。
実行例

showModalBottomSheet
showModalBottomSheet(
  context: context,
  builder: (BuildContext context) {
    return Container(
      height: 200,
      color: Colors.blue[200],
      child: const Center(
        child: Text("BottomSheet"),
      ),
    );
  },
);

https://api.flutter.dev/flutter/material/showModalBottomSheet.html

showModalBottomSheet のパラメータ

パラメータ 説明
context BuildContext 現在のBuildContextを指定。ウィジェットツリー内の位置情報などが含まれる。
builder WidgetBuilder ボトムシートの中身を構築するための関数。BuildContextを引数に取り、表示するウィジェットを返す。
backgroundColor Color ボトムシートの背景色。
elevation double ボトムシートの影の深さ。影をつけることで立体感を出す。
shape ShapeBorder ボトムシートの形状。角の丸みなどをカスタマイズ可能。
isScrollControlled bool ボトムシートが全画面に広がるかどうかを制御。trueで全画面表示。
isDismissible bool ボトムシート外側をタップした時に閉じるかどうか。デフォルトはtrue
enableDrag bool ユーザーがボトムシートをドラッグして閉じることができるかどうか。デフォルトはtrue
barrierColor Color モーダルの背景色。ボトムシート以外の UI を覆う色。
clipBehavior Clip ボトムシートのコンテンツが枠を越えた場合のクリッピングの挙動を制御。

BottomSheet に TextField を配置

実行例

TextField配置
final TextEditingController _textEditingController = TextEditingController();
String? text1;

showModalBottomSheet(
    context: context,
    builder: (BuildContext context) {
        return Container(
        height: 250,
        color: Colors.blue[200],
        child: Center(
            child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 16.0),
            child: Column(
            children: [
                const SizedBox(height: 32.0),
                TextField(
                controller: _textEditingController,
                decoration: const InputDecoration(
                    border: OutlineInputBorder(),
                    labelText: 'テキスト',
                ),
                ),
                ElevatedButton(
                    onPressed: () {
                    setState(() {
                        text1 = _textEditingController.text;
                    });
                    _textEditingController.clear();
                    Navigator.pop(context);
                    },
                    child: const Text('OK')),
            ],
            ),
        )),
        );
    },
    );

キーボード出現時の注意点

実行例

テキスト入力をする際にキーボードが現れますが、テキスト入力部がキーボードで隠れてしまいます。
これを改善するために、MediaQuery.of(context).viewInsets.bottom を使ってキーボードの高さを取得します。

Container に高さにキーボード高さを足すことで BottomSheet の高さが可変になります。

viewInsets.bottom
Container(
  height: 250 + MediaQuery.of(context).viewInsets.bottom,
)

実行例
https://api.flutter.dev/flutter/widgets/MediaQueryData/viewInsets.html

実装サンプル

実行例

コード全文
import 'package:flutter/material.dart';

class BottomSheetSample extends StatefulWidget {
  const BottomSheetSample({super.key});

  
  State<BottomSheetSample> createState() => _BottomSheetSampleState();
}

class _BottomSheetSampleState extends State<BottomSheetSample> {
  final TextEditingController _textEditingController = TextEditingController();
  String? text1;

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(
                decoration: BoxDecoration(
                  color: Colors.blue[200],
                  border: Border.all(
                    color: Colors.black,
                    width: 1,
                  ),
                  borderRadius: const BorderRadius.all(
                    Radius.circular(10.0),
                  ),
                ),
                width: 200,
                padding: const EdgeInsets.all(16.0),
                margin: const EdgeInsets.only(bottom: 16.0),
                child: Text(text1 ?? ' ')),
            ElevatedButton(
                onPressed: () {
                  showModalBottomSheet(
                    context: context,
                    builder: (BuildContext context) {
                      return Container(
                        height: 250 + MediaQuery.of(context).viewInsets.bottom,
                        decoration: BoxDecoration(
                          color: Colors.blue[200],
                          borderRadius: const BorderRadius.only(
                            topLeft: Radius.circular(20.0),
                            topRight: Radius.circular(20.0),
                          ),
                        ),
                        child: Center(
                            child: Padding(
                          padding: const EdgeInsets.symmetric(horizontal: 16.0),
                          child: Column(
                            children: [
                              const SizedBox(height: 32.0),
                              TextField(
                                controller: _textEditingController,
                                decoration: const InputDecoration(
                                  border: OutlineInputBorder(),
                                  labelText: 'テキスト',
                                ),
                              ),
                              ElevatedButton(
                                onPressed: () {
                                  setState(() {
                                    text1 = _textEditingController.text;
                                  });
                                  _textEditingController.clear();
                                  Navigator.pop(context);
                                },
                                child: const Text('OK')),
                            ],
                          ),
                        )),
                      );
                    },
                  );
                },
                child: const Text('Show Bottom Sheet')),
          ],
        ),
      ),
    );
  }
}

Discussion