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,執行結果如下:

