PS:あなたの計画は完璧ですが、世界の変化はあまりにも早いです。
前の記事では、Flutter における画像の読み込みとソースコードの分析について学びました。モバイル開発を経験した友人は、コンポーネントのライフサイクルを知っているでしょう。Flutter でも同様で、Flutter のコンポーネントのライフサイクルを理解し、学ぶことは非常に重要です。関連する記事は以下の通りです:
本記事では、Flutter における Widget のライフサイクルについて主に紹介します。具体的には以下の通りです:
- StatelessWidget
- StatefulWidget
- State ライフサイクルの状態
- State ライフサイクルのメソッド
StatelessWidget#
StatelessWidget から派生したコンポーネントは無状態コンポーネントで、無状態コンポーネントは構築時に一度だけレンダリングされ、動的な変化をサポートしません。つまり、他のユーザー操作によってコンポーネントを再描画することはできず、渡されたパラメータを使用して構築することしかできません。以下のようになります:
/// StatelessWidget
/// 無状態Widgetを表します
class StatelessSamplePage extends StatelessWidget {
// 外部から渡されるデータ
final String data;
StatelessSamplePage(this.data);
@override
Widget build(BuildContext context) {
return Container(
color: Colors.lightBlue,
child: Text(data),
);
}
}
渡されたパラメータは final で修飾する必要があり、そうでない場合は以下の警告が表示されます:
This class (or a class which this class inherits from) is marked as '@immutable', but one or more of its instance fields are not final: StatelessSamplePage.data
Widget が @immutable 注釈で修飾されていることを示しています。以下のようになります:
@immutable
abstract class Widget extends DiagnosticableTree {
この時、変数を修飾するには final を使用する必要があります。Dart では final で修飾された変数は一度だけ初期化でき、これも StatelessWidget の無状態の特徴に合致します。
StatefulWidget#
StatefulWidget から派生したコンポーネントは有状態コンポーネントで、有状態コンポーネントはデータの変化に応じて Widget を何度も構築し、動的なインターフェースのレンダリングをサポートします。現在の時間をリアルタイムで表示するインターフェースを実現するには、明らかに StatelessWidget では不可能で、有状態の StatefulWidget を使用する必要があります。以下のようになります:
/// StatefulWidget
/// 有状態のWidgetを表します
class StatefulSamplePage extends StatefulWidget {
@override
_StatefulSamplePageState createState() => _StatefulSamplePageState();
}
class _StatefulSamplePageState extends State<StatefulSamplePage> {
DateFormat _dateFormat;
String _currentTime;
Timer _timer;
@override
void initState() {
super.initState();
_dateFormat = new DateFormat("yyyy-MM-dd HH:mm:ss");
// 現在の時間を初期化
_currentTime = _dateFormat.format(DateTime.now());
// 時間を更新
_startTime();
}
@override
Widget build(BuildContext context) {
return Center(
child: Scaffold(
appBar: AppBar(
title: Text("Stateful Widgetサンプル"),
centerTitle: true,
),
body: Align(
alignment: Alignment.topCenter,
child: Text("現在の時間:$_currentTime"),
),
),
);
}
@override
void dispose() {
super.dispose();
if(_timer!=null){
_timer.cancel();
}
}
_startTime() {
const Duration duration = Duration(seconds: 1);
_timer = Timer.periodic(
duration,
(Timer timer) => {
// インターフェースの状態を更新
setState(() {
_currentTime = _dateFormat.format(DateTime.now());
})
});
}
}
効果は以下の通りです:
実際には、静的なインターフェースの場合、StatelessWidget と StatefulWidget は完全に同じで、どちらも実現可能です。唯一の違いは、StatefulWidget は setState メソッドを通じて Widget の再構築をトリガーできることです。State クラスは Stateless から Stateful への橋渡しをします。
State ライフサイクルの状態#
Flutter のライフサイクルは実際には各コンポーネントのライフサイクルです:
- StatelessWidget:無状態コンポーネントのライフサイクルは build 構築のプロセスのみです;
- StatefulWidget:無状態コンポーネントのライフサイクルは State のライフサイクルを指します。
Flutter のライフサイクルは実際には無状態コンポーネントのライフサイクル、すなわち State のライフサイクルです。以下の図のようになります:
上記の各 State のライフサイクル状態は主に 3 つです:
- created:State の作成状態を指し、createState メソッドが呼び出された後に created state に入ります;
- dirty:setState などのメソッドが呼び出され、データが変化したが、Widget がまだ再構築されていない状態を指します;
- clean:Widget 構築後の状態を指します;
- defunct:State.dispose が呼び出された後の状態を指し、この時点で対応する Widget は破棄され、再構築できなくなります。
State ライフサイクルのメソッド#
有状態コンポーネントのライフサイクルは State のライフサイクルであり、その具体的な呼び出しプロセスと build トリガーのタイミングは以下の図のようになります:
そのライフサイクルメソッドの具体的な意味は以下の通りです:
- createState:StatefulWidget 内で State を作成するために使用されます;
- initState:State の初期化操作、変数の初期化など;
- didChangeDependencies:initState が呼び出された後、または InheritedWidgets コンポーネントを使用した場合に呼び出されます。InheritedWidgets は Flutter の状態管理に使用できます;
- build:Widget の構築に使用されます;
- deactivate:この State オブジェクトを含む Widget が削除された後に呼び出されます。この Widget が削除された後、他の Widget ツリー構造に追加されていない場合、dispose メソッドが引き続き呼び出されます;
- dispose:このメソッドが呼び出された後、Widget が占有していたリソースを解放します;
- reassemble:開発段階で、ホットリロードの際に呼び出され、その後再構築されます;
- didUpdateWidget:親 Widget が構築されると、子 Widget の didUpdateWidget メソッドが呼び出されます。
対応する Widget のライフサイクルメソッドにログを追加し、上記のライフサイクルメソッドの実行を検証します。親 Widget のソースコードは以下の通りです:
const String TAG = "Flutter";
/// Widgetライフサイクル
class WidgetLifecycleSamplePage extends StatefulWidget {
@override
_WidgetLifecycleSamplePageState createState() {
Log.info(TAG, "親 createState");
return _WidgetLifecycleSamplePageState();
}
}
class _WidgetLifecycleSamplePageState extends State<WidgetLifecycleSamplePage> {
num _count = 0;
@override
void initState() {
super.initState();
Log.info(TAG, "親 initState");
}
@override
Widget build(BuildContext context) {
Log.info(TAG, "親 build");
return Scaffold(
appBar: AppBar(
title: Text("Widgetライフサイクルサンプル"),
centerTitle: true,
),
body: Column(
children: <Widget>[
Center(
child: RaisedButton(
textColor:Colors.white,
color: Colors.lightBlue,
child: Text("親->setState:$_count",style: TextStyle(color: Colors.white),),
onPressed: (){
setState(() {
Log.info(TAG, "親 setState");
_count++;
});
}),
),
SubWidgetLifecycleSamplePage(),
],
)
);
}
@override
void didUpdateWidget(WidgetLifecycleSamplePage oldWidget) {
super.didUpdateWidget(oldWidget);
Log.info(TAG, "親 didUpdateWidget");
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
Log.info(TAG, "親 didChangeDependencies");
}
@override
void deactivate() {
super.deactivate();
Log.info(TAG, "親 deactivate");
}
@override
void reassemble() {
super.reassemble();
Log.info(TAG, "親 reassemble");
}
@override
void dispose() {
super.dispose();
Log.info(TAG, "親 dispose");
}
}
/// 子Widget
class SubWidgetLifecycleSamplePage extends StatefulWidget {
@override
_SubWidgetLifecycleSamplePageState createState() {
Log.info(TAG, "子 createState");
return _SubWidgetLifecycleSamplePageState();
}
}
子 Widget の実装は親 Widget の実装と似ているため、ここでは省略します。完成したソースコードは、公式アカウントのバックエンドでキーワード【Lifecycle】を返信することで入手できます。上記のコードの表示効果は以下の通りです:
その対応するライフサイクルメソッドの呼び出しは以下の通りです:
分析は以下の通りです:
- Widget の初期化プロセスは、この Widget が存在するページのライフサイクルプロセスを指し、まず親 Widget が呼び出され、その後子 Widget が呼び出され、次に createState、initState、didChangeDepandencies、build メソッドが順に呼び出されます;
- 親 Widget が setState でデータを更新すると、親 Widget の構築がトリガーされ、したがって子 Widget は didUpdateWidget メソッドが呼び出されます;
- ホットリロードや Widget の破棄などの他のプロセスは、上記の State ライフサイクルメソッドのフローチャートと同じであるため、ここでは繰り返しません。