BottomSheetは、画面下から伸びてきて、何らかの入力を求めるWidgetです。私が実際にBottomSheetを使用してみて、疑問に思って検索して分かったことを汎用性の高い形式のソースを作成してまとめています(デザイン的には、アレですが)。
この記事で分かることは、以下の通りです。
基本的な表示方法とボトムシートのサイズ(長さ?)の指定、結果の使い方
void _onTap() async {
final result = await showModalBottomSheet<int>(
context: context,
builder: (context) => SizedBox(
height: 200,
child: Container(),
)
);
if (result != null) {
setState(() => _counter += result);
}
}
ボトムシートは、showModelBottomSheetで表示させます。この関数をボタンを押したときなどのイベント内に書きます。結果として返したい型をジェネリックで指定します(ここでは、intを指定)。
ボトムシートの中身は、builderの中身に書きます。Widgetを指定するので、自由には設定できます。
ボトムシートを指定しないと大きくなりがちなので、SizeBoxを使うことで、長さを指定しています。
ボトムシートから結果を受け取ることができ、非同期でボトムシートが閉じられるのを待ちます。ボトムシートが閉じられると結果が返ってくるので受け取ります(この場合、result)。結果の値を見て、処理を実施します。
ボトムシートの閉じ方と結果の返し方
Navigator.of(context).pop() // 結果はnull
Navigator.of(context).pop(2) // 結果は2
ボトムシート以外の箇所を押すと、ボトムシートは消えます。そのときの結果はnullになります。
プログラム側でボトムシートを消したい場合は、Navigator.of(context).pop()を使います。popの引数に結果を設定し、引数無しならnull、設定すればその値が結果として戻されます。
ボトムシートの上部を角円にする方法
showModalBottomSheet<int>(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(25.0)),
),
デフォルトのままでは、ボトムシートの上部が四角になります。デザイン的に角円の方が良いかと思いますので、上記を設定します。
ボトムシート内での状態変化の反映方法
StatefulBuilder(
builder: (BuildContext context, Function setSheetState) {
return Column(
children: [
Checkbox(
value: _isChecked,
onChanged: (value) =>
setSheetState(() => _isChecked = value ?? false),
),
],
);
},
),
setStateを使っても、ボトムシートを開いたページのsetStateが呼ばれているので、ボトムシート内の状態変化には対応できませんでした(この例ですと、チェックボックスをチェックしても、チェックされた状態にならない)。StatefulBuilderというWidgetを使うことで、解決できました。
StatefulBuilderを使用すると、引数に該当のWidgetのsetState的な関数を取得できるようです。ただ、setStateという名前にすると元のページのsetStateが隠されて使えないので、名前をsetSheetStateに変えてみました。
こうすることで、チェックボックスにチェックを入れたり、ラジオボタンで初期値以外のボタンを選べるようになりました。
全ソース
ということで、上記の全てを入れたテストコードを記載しておきます。ボトムシート内でチェックボックスのチェックを押すと、チェックがされて、ボタンの表示が変わります。そして、結果がチェックなしだと1,、ありだと2が戻され、_counterに足されます
また一番上のボタンは、結果NULLを返すようになってます。
また、ボトムシートの長さが指定されていて、角円になっています。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
bool _isChecked = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _onTap,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
void _onTap() async {
final result = await showModalBottomSheet<int>(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(25.0)),
),
builder: (context) => SizedBox(
height: 200,
child: StatefulBuilder(
builder: (BuildContext context, Function setSheetState) {
return Column(
children: [
OutlinedButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Nullを返す')),
Checkbox(
value: _isChecked,
onChanged: (value) =>
setSheetState(() => _isChecked = value ?? false),
),
OutlinedButton(
onPressed: () =>
Navigator.of(context).pop(_isChecked ? 2 : 1),
child: Text('${_isChecked ? 2 : 1}を返す')),
],
);
},
),
),
);
print('result: $result');
if (result != null) {
setState(() => _counter += result);
}
}
}
まとめ
Flutterでのボトムシートの基本的な表示方法から、状態変化、角円にする方法などを紹介しました。
「Flutter bottomSheet」で検索しても、表示方法しか記載されていないページが多かったので、ボトムシートを舐めてかかってました。しかし、実際に使ってみると、状態変化の反映など、なかなか手強かったです。通常はボトムシートで色々と入力させないんですかね。