StatelessWidget
實例建立後,狀態就不會改變,例如,先前使用過的 Text
,或者〈自訂 StatelessWidget〉中自訂的 HelloWidget
,都是不可變動(immutable),也就是類別定義時,沒有提供可改變實例狀態的方法。
既然 StatelessWidget
不可變,父類當然 Widget
也是不可變,這就有了個問題,如果使用者操作後,畫面必須做出某種改變怎麼辦?最簡單的想法是,根據使用者的輸入操作資訊,重新建構 Widget
,然後畫面就改變了,不過畫面組成很複雜的話,這種想法自然是會呈現上的效能問題。
因此設計畫面時,對於畫面上不會變動的元件,使用 StatelessWidget
來組建,既然不會變動,畫面其他會變動的部份改變時,StatelessWidget
不用重新 build
。
若是畫面上會變動的元件,才繼承 StatefulWidget
,例如,來設計一個顯示時間的小程式:
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(Center(child: Time()));
class Time extends StatefulWidget {
@override
_TimeState createState() => _TimeState();
}
class _TimeState extends State<Time> {
DateTime _dateTime = DateTime.now();
@override
void initState() {
super.initState();
Timer.periodic(new Duration(seconds: 1), (timer) {
setState(() {
_dateTime = DateTime.now();
});
});
}
@override
Widget build(BuildContext context) {
return Text(
'${_dateTime}'.substring(0, 19),
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
);
}
}
Flutter 框架會在建立 State
實例後,呼叫 initState
進行狀態的初始,執行結果如下:
在範例程式中,Time
繼承了 StatefulWidget
,與 StatelessWidget
不同的是,必須實作的不是 build
方法,而是 createState
方法,傳回 State
實例,在定義 StatefulWidget
時,總是會定義 State
,如上例的 _TimeState
。
真正會改變狀態的是 State
實例,它會參考對應的 Widget
與 Element
,在範例中使用 Timer
,定時呼叫了 setState
方法,被指定的匿名函式會在該方法中執行(匿名函式執行過後不得傳回 Future
,否則會拋出錯誤)。
setState
指定的匿名函式中,通常會撰寫改變 State
的流程,像範例中,就改變了 _dateTime
,在 setState
方法執行完指定的匿名函式後,Widget
對應的元素會被標示為需要建構,框架會呼叫 build
,這時重新建構 Widget
,用來更新對應的 Widget
。
setState
簡單來說,就是通知 Flutter 狀態改變了,build
根據新狀態建立新的 Widget
,也就是說,每次狀態變化了,就產生新的畫面組態,UI = f(state) 的概念,一種宣告式的風格。
從範例中可以看到,StatefulWidget
本身還是不可變的,真正會改變狀態的是 State
實例,就這個簡單範例來說,Time
似乎沒什麼太大作用,在比較複雜的範例中,繼承 StatefulWidget
的類別,可以準備 State
中 build
時可以共用的資料,畢竟若每次 build
,全部的物件都要重新建構,是蠻耗費資源的動作。
例如,或許來改變一下文字的顏色:
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(
Center(
child: TimeText(
// 指定顏色
textColor: Colors.blue,
backgroundColor: Colors.yellow,
),
)
);
class TimeText extends StatefulWidget {
TextStyle style; // style 是可重用的
TimeText({Color textColor, Color backgroundColor}) {
style = TextStyle(
color: textColor,
backgroundColor: backgroundColor,
);
}
@override
_TimeTextState createState() => _TimeTextState();
}
class _TimeTextState extends State<TimeText> {
DateTime _dateTime = DateTime.now();
@override
void initState() {
super.initState();
Timer.periodic(new Duration(seconds: 1), (timer) {
setState(() {
_dateTime = DateTime.now();
});
});
}
@override
Widget build(BuildContext context) {
return Text(
'${_dateTime}'.substring(0, 19),
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
style: widget.style
);
}
}
在 State
中,可以透過 widget
,取得關聯的 Widget
實例,範例中 widget.style
就取得了 TimeText
中的 style
,執行結果如下: