はじめに
「わかんない、わかんないよ!Flutterってなんなの!Dartなんて初めて聞いたし、オブジェクト指向って言われても難しくてわかんないよ!」
どうも、堕罪オサムです。
最近FlutterとDartを学習していて、その一環でFlutterのチュートリアル(?)アプリをいじって遊んだりしてたのですが、これ、ありえないほどむずくねえですか?
なんかこう、知らない単語イッパイだァみたいな……。
そういうわけで理解を深めるべく、そのお遊びでできたコードを一から解説することにしました。
本当になにもわからない私が読んでもわかるように(今後Flutterを使用してアプリを作るときに見返せるように)細かく調べてみたので、よかったら目を通していただけると嬉しいです!
書いたコードはコチラをクリック!
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.amber),
useMaterial3: true,
),
home: const MyHomePage(title: 'testApp'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class ResetButton extends ElevatedButton {
const ResetButton({Key? key, required VoidCallback? callback})
: super(
key: key,
onPressed: callback,
child: const Icon(
Icons.delete,
));
}
class ReduceButton extends ElevatedButton {
const ReduceButton({Key? key, required VoidCallback? callback})
: super(
key: key,
onPressed: callback,
child: const Text(
' \u2212 ', // Unicode 文字列 U+2212 はマイナス記号を表す
style: TextStyle(fontSize: 25),
));
}
class MemoDataGetButton extends ElevatedButton {
const MemoDataGetButton({Key? key, required VoidCallback? callback})
: super(
key: key,
onPressed: callback,
child: const Icon(Icons.done),
);
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
String _memoText = ''; // テキストを保持するための変数
final List<String> _memoList = []; // メモのリスト
final TextEditingController _textEditingController = TextEditingController();
void _incrementCounter() {
setState(() {
_counter++;
});
}
void _resetCounter() {
setState(() {
_counter = 0;
});
}
void _reduceCounter() {
setState(() {
_counter--;
});
}
void _updateText(String newText) {
setState(() {
_memoText = newText;
});
}
void _handleButtonPress() {
// ボタンが押されたときに新しいメモデータを取得し、_updateTextを呼び出す
_memoList.add(_memoText); // メモをリストに追加
_updateText(''); // メモデータを初期化
// TextFormFieldのテキストを初期化
_textEditingController.clear();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'count',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
ResetButton(callback: _resetCounter),
ReduceButton(callback: _reduceCounter),
TextFormField(
controller: _textEditingController, // TextEditingControllerを指定
decoration: const InputDecoration(
labelText: 'memo',
floatingLabelBehavior: FloatingLabelBehavior.never,
),
keyboardType: TextInputType.multiline,
maxLines: null,
onChanged: (newMemo) {
_updateText(newMemo);
// メモデータを入力するたびに更新
},
),
MemoDataGetButton(callback: _handleButtonPress),
//メモデータを取得するボタン
SizedBox(
height: 200,
width: double.infinity, // メモデータ左寄せ
child: ListView(
children: _memoList
.map((memo) => ListTile(
leading: const Icon(Icons.fiber_manual_record),
title: Text('Item $memo'),
))
.toList()),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
解説
Flutterアプリケーションの初期化とルートウィジェットの設定
この部分のコードは、Flutterアプリケーションのエントリーポイントである main 関数から始まり、アプリケーションの初期設定を行う MyApp クラスについて説明しています。
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.amber),
useMaterial3: true,
),
home: const MyHomePage(title: 'testApp'),
);
}
}
-
void main() ~ runApp(const MyApp());⇒ 一番最初に実行されるコード(多分必須)。 -
class MyApp extends StatelessWidget ~ const MyApp({super.key})⇒MyAppという子クラスがStatelessWidgetクラスという親クラスを継承(extends)している。 ※StatelessWidgetを継承しているWidgetは定数コンストラクタを含むことができる。 -
@override ~ Widget build(BuildContext context)…⇒buildメソッドでMyAppがウィジェットを構築するため@overrideで親クラス(StatelessWidget)は書き換えずに子クラスのMyAppを上書きしている。※MaterialAppはFlutterフレームワークで作成されたアプリケーションのルートとなるウィジェット。アプリのテーマやナビゲーション、ローカライズなどのアプリ全体の管理を行う役割のウィジェット。 -
return MaterialApp()…⇒子クラスのMyAppのbuildメソッド内にMaterialAppを定義している。homeは最初に起動させたいページ(MyHomePage以降)を設定している。
MyHomePage ウィジェットの設定
この部分のコードは、MyHomePage ウィジェットが状態を持ち、その状態を管理するための State オブジェクトを生成するためのものです。
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
-
class MyHomePage extends StatefulWidget ~ const MyHomePage…⇒StatefulWidgetという親クラスがMyHomePageを継承(extends)している。また、MyHomePageクラスのコンストラクタは、super.keyとrequired this.titleの2つのパラメーターを受け取っている。required this.titleは、コンストラクタ呼び出し時にtitleパラメーターが必要であることを示していいる。 -
final String title;⇒finalに文字列(String)のtitleを代入する。↑で指定されているrequired this.titleがコンストラクタ呼び出し時にtitleパラメーターが必要なのでfinalで関連付ける。 -
State<MyHomePage> createState() => _MyHomePageState();⇒createStateメソッドの実装。State<MyHomePage>は、Stateクラスのジェネリクス型引数としてMyHomePageクラスを指定している。この指定により*S*tateオブジェクトがどのウィジェットの状態を管理するかを示し、ここではMyHomePageウィジェットの状態を管理するためのStateオブジェクトを作成している。またcreateStateで作成したstateが変化したときに再描画される。
ボタンクラスの設定
このセクションでは、3つのボタンクラス(ResetButton、ReduceButton、MemoDataGetButton)の設定について説明します。
class ResetButton extends ElevatedButton {
const ResetButton({Key? key, required VoidCallback? callback})
: super(
key: key,
onPressed: callback,
child: const Icon(
Icons.exposure_zero,
));
}
このコードではボタンクラスが3つ(ResetButton・ReduceButton・MemoDataGetButton)あり、どれも同様に作られている。よってこの3つの命名以外はテンプレートのようになっています。
-
class ResetButton extends ElevatedButton⇒ElevatedButtonを継承したボタンのクラスを作る。※ ElevatedButton:立体的な効果を持ったボタンを作るためのウィジェット -
const ResetButton({Key? key, required VoidCallback? callback})⇒ResetButtonのコンストラクタ。VoidCallbackはonPressedなどのボタンのイベント処理のときに使うもの。また引数を受け取らず何も返さない関数の型。?はnullを許容することを示している。{}の中に定義されているパラメーター(引数)は、コンストラクタ呼び出し時に渡すことができる。requiredがあるためcallbackの引数を指定しなければならない。 - super(…) ⇒親クラスの
ElevatedButtonのコンストラクタを呼び出している。key・onPressed・childはコンストラクタ引数。onPressedが発火するタイミング(ResetButton押下時)のアクションをcallbackとして受け取っている(ちなみにそれぞれのボタンのcallback時の引数はのちに定義していく)。childではボタンアイコンの指定をしている。
ウィジェット内部の状態
このセクションでは、_MyHomePageStateクラス内でウィジェット内部の状態を管理する方法について説明します。
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
String _memoText = ''; // テキストを保持するための変数
final List<String> _memoList = []; // メモのリスト
final TextEditingController _textEditingController = TextEditingController();
void _incrementCounter() {
setState(() {
_counter++;
});
}
void _incrementCounter() のほかに_resetCounter() ・_reduceCounter() ・_updateText(String newText) が同じようなコードになります。
-
class _MyHomePageState extends State<MyHomePage>⇒State<MyHomePage>を継承した_MyHomePageStateクラスを作る(StateクラスはMyHomePageウィジェットの内部状態を管理する)。 -
int _counter = 0;⇒_counterウィジェット内で使用される整数値変数。初期値は0。 -
String _memoText = '';⇒テキストフィールドに入力されたメモのテキストを保持するための文字列変数。_updateTextメソッドで新しいテキストが更新されると、この変数も更新される。 -
final List<String> _memoList = [];⇒メモのリストを保持するための文字列リストを作る。_handleButtonPressメソッド内で新しいメモが_memoListに追加される。 -
final TextEditingController _textEditingController = TextEditingController();⇒_textEditingControllerはテキストフィールドの入力を制御するためのTextEditingControllerオブジェクトです。テキストフィールドにテキストを入力する際に使用されます。 -
void _incrementCounter() {}⇒カウンターをインクリメントするための_incrementCounter()メソッドを作る。setState内で変数の値を変更することでウィジェットの再描画をハンドルすることが可能。_counter++;は_counterウィジェット内で使用される整数値変数を増加させるという処理。
_resetCounter() ・_reduceCounter() ・_updateText(String newText) についての説明
void _resetCounter() {
setState(() {
_counter = 0;
});
}
void _reduceCounter() {
setState(() {
_counter--;
});
}
void _updateText(String newText) {
setState(() {
_memoText = newText;
});
}
-
void _resetCounter(){}⇒setState内で変数の値を変更することでウィジェットの再描画をハンドルすることが可能。_counter = 0;は_counter変数で使用される整数値変数をリセットするという処理。 -
void _reduceCounter() {}⇒setState内で変数の値を変更することでウィジェットの再描画をハンドルすることが可能。_counter--;は_counter変数で使用される整数値変数を減少させるという処理。 -
_updateText(String newText) {}⇒void _updateText(String newText)は、void _updateTextが新しいテキスト情報を受け取るための引数newTextを持っていることを示す。setState内で変数の値を変更することでウィジェットの再描画をハンドルすることが可能。_memoText = newText;では新しいテキストnewTextが_memoTextに代入され、メモのテキストが新しい値に更新される。
ボタンが押されたときの処理
このセクションでは、ボタンが押されたときに実行される _handleButtonPress メソッドについて説明します。
void _handleButtonPress() {
// ボタンが押されたときに新しいメモデータを取得し、_updateTextを呼び出す
_memoList.add(_memoText); // メモをリストに追加
_updateText(''); // メモデータを初期化
// TextFormFieldのテキストを初期化
_textEditingController.clear();
}
-
void _handleButtonPress(){}⇒MemoDataGetButtonを押下した際のコールバック処理が書かれている。(のちにMemoDataGetButton(callback: _handleButtonPress)が出てくる) -
_memoList.add(_memoText);⇒新しいメモデータ_memoTextを_memoListと呼ばれるリストに追加する。 -
_updateText('');⇒_updateTextメソッドを呼び出し、メモのテキストデータ_memoTextを初期化(空文字列'') になるようにする。 -
_textEditingController.clear();⇒_textEditingControllerを使用して、テキストフィールドのテキストをクリアまたは初期化する。テキストを入力後、ボタンを押下するとテキストボックスが初期化される。
ウィジェットの描画と Scaffold
このセクションでは、build メソッドに焦点を当てて説明します。
@override
Widget build(BuildContext context) {
return Scaffold(...)
-
@override Widget build(BuildContext context)⇒buildメソッドはstatefulWidgetから継承されたMyHomePageウィジェットを描画する。BuildContext contextはbuildメソッドの引数でこれを使用してウィジェットがどのように配置され、スタイルが適用されるかを決定する。 -
return Scaffold()⇒Scaffoldはほかのウィジェットを描画するためのウィジェット。それを返している。Scaffoldのプロパティは多く実装されている。(body・appBar・floatingActionButton…など)
参考サイト・記事・単語まとめ
クラス – プログラミング用語解説|Unity高校&ゲームスクールのG学院
インスタンス – プログラミング用語解説|Unity高校&ゲームスクールのG学院
継承 – プログラミング用語解説|Unity高校&ゲームスクールのG学院
Dart のコンストラクタを理解する - エキサイト TechBlog.
【Flutter】build() でやってはいけない 3 つのこと - Qiita
【Flutter初心者向け】MaterialAppとScaffoldとは??サンプルアプリをもとに分かりやすく解説 - 文系プログラマー「いお」が語る
【5分でわかる】Widgetとは? | Flutter入門|KIMURA LOG
FlutterのBuildContextとは何か - Qiita
【Flutter】UI構築のスタート地点Scaffoldを徹底解説【サンプルコードあり】
※コンストラクタ:Classのインスタンスを作成したとき(newしたとき)に呼び出される初期化処理。
※パラメーター:ソフトウエアを実行したりプログラム内で関数を呼び出したりする時に、一連の処理を指定するために与える情報(「処理結果に影響を与える外部から投入される変動要素」のこと プログラムの世界では「引数(仮引数)」と呼ばれる場合もあります。 引用:https://wa3.i-3-i.info/word1443.html)
※superクラス:親クラスの別名
※ジェネリクス型引数:主にクラスや関数を汎用的に設計するための仕組み ⇒ジェネリクスを使うことで、クラスや関数が様々な型に対して動作する柔軟性を持つことができます。例えば、StateクラスはState<MyHomePage>と指定することで、MyHomePageクラスだけでなく、他のウィジェットの状態も管理することができる。