第5章
ボタンを押して画面を変える
ボタンを押すと数字が増えるアプリを作り、onPressedとsetStateで画面を更新する方法を学びます。
この章で学ぶこと
- ボタンを押したときに処理を実行する方法
- 画面に表示する値を「状態」として持つ方法
- 状態を変えて画面を更新する方法
事前準備
先に「Flutterアプリの見た目を編集する」の章まで進め、作成した hello_app プロジェクトを開いてください。この章では、追加のパッケージや画像は使いません。
前の章では、ボタンを押すと debugPrint でコンソールに文字を表示しました。今回は、ボタンを押すたびに画面の数字が 0 → 1 → 2 と増えるアプリを作ります。
動くアプリを作る
lib/main.dart の内容を、次のコードにすべて置き換えて保存します。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: CounterPage(),
);
}
}
class CounterPage extends StatefulWidget {
const CounterPage({super.key});
@override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('ボタンで画面を変える'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('ボタンを押した回数'),
Text(
'$count 回',
style: const TextStyle(fontSize: 40),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () {
setState(() {
count = count + 1;
});
},
child: const Text('1増やす'),
),
],
),
),
);
}
}
hello_app フォルダのターミナルで、次のコマンドを実行します。
flutter run -d chrome
すでにアプリを実行している場合は、今回はWidgetの構成を変えたので、ターミナルで大文字の R を入力してホットリスタートしてください。終了してから再実行しても構いません。
次の動作を確認しましょう。
- 最初は「0 回」と表示される
- 「1増やす」を押すと「1 回」になる
- もう一度押すと「2 回」になる
onPressedで押したときの処理を書く
ElevatedButton の onPressed には、ボタンを押したときに呼び出してほしい関数を渡します。このように、あとで呼び出される関数を コールバック と呼びます。
onPressed: () {
setState(() {
count = count + 1;
});
},
() { ... } は、名前のない関数の書き方です。ここではボタンを押すたびに、中の処理が実行されます。count = count + 1; は、現在の count に1を足し、その結果を count に入れ直すという意味です。
なお、onPressed: null にするとボタンは無効になり、押せなくなります。
変化する値をStateに持たせる
画面に表示する回数のように、操作によって変わる値を 状態(state) と呼びます。今回の状態は、次の変数です。
int count = 0;
int は整数を表す型で、0 が初期値です。この変数は build の中ではなく、_CounterPageState の中に置いています。build の中で毎回 0 を代入すると、画面を作り直すたびに値が戻ってしまいます。
今回のコードでは、役割を次のように分けています。
MyApp:自分自身では変化する値を持たないので、StatelessWidgetを使うCounterPage:変化する値を扱う画面なので、StatefulWidgetを使う_CounterPageState:countを保持し、その値に応じた画面をbuildで作る
createState() は、CounterPage に対応する _CounterPageState を作るためのメソッドです。クラス名の先頭の _ は、Dartでは同じライブラリの中だけで使う名前であることを表します。
setStateで画面の更新を知らせる
変数に新しい値を代入するだけでは、その変更を理由に画面が更新されるとは限りません。setState の中で値を変えると、Flutterに状態の変化を知らせ、もう一度 build を実行するように依頼できます。
今回の流れは次のとおりです。
- ボタンを押すと
onPressedの処理が呼ばれる setStateの中でcountが増える- Flutterが
buildを再実行し、新しい値を使って表示を更新する
表示側の '$count 回' は、文字列に count の値を埋め込む書き方です。count が 3 なら「3 回」と表示されます。
Text(
'$count 回',
style: const TextStyle(fontSize: 40),
)
この Text は実行中に内容が変わるので、const Text(...) にはできません。一方、文字サイズなど固定の設定には const を付けられます。
ここで扱う状態はアプリの実行中に保持する値です。ホットリロードでは通常その値が残りますが、ホットリスタートやアプリの再起動では 0 に戻ります。終了後も値を残すには、別途保存する仕組みが必要です。
やってみよう:リセットボタンを追加する
「1増やす」ボタンの下に、数字を 0 に戻すボタンを追加しましょう。先ほどの Column の children 内で、ElevatedButton(...) の直後に次のコードを追加します。
const SizedBox(height: 12),
ElevatedButton(
onPressed: () {
setState(() {
count = 0;
});
},
child: const Text('リセット'),
),
保存したら、実行中のターミナルで小文字の r を入力してホットリロードします。「1増やす」を3回押してから「リセット」を押し、「0 回」に戻ることを確認してください。その後「1増やす」を押すと、再び「1 回」になります。
余裕があれば、次の変更にも挑戦してみましょう。
- 1回押すごとに2増えるようにする。ボタンの文字も「2増やす」に変える
- 回数の下に、5回以上なら「たくさん押しました!」、それ未満なら「押してみよう」と表示する
2つ目のヒントは、条件によって値を選ぶ 条件 ? 条件を満たすときの値 : 満たさないときの値 という書き方です。children に次の Text を追加できます。
Text(count >= 5 ? 'たくさん押しました!' : '押してみよう'),
うまく動かないとき
- ボタンが押せない:
onPressedがnullになっていないか確認する - 数字が変わらない:
setStateの中でcountを変更しているか確認する - 数字が元に戻る:
int count = 0;をbuildの中に置いていないか確認する constに関するエラーが出る:countを使うTextや、その親のColumnなどにconstを付けていないか確認する- Widgetの種類を変更したあとにエラーが出る:ホットリスタートするか、アプリを終了して再実行する
まとめ
ボタンを押したときの処理は onPressed に書きます。画面に使う変化する値は State に持たせ、setState の中で更新します。同じ仕組みで、数字だけでなくメッセージや色なども操作に応じて変えられます。