3. StatefulWidget实现可切换主题的计数器
为什么计数器需要 StatefulWidget
例1中的默认计数器之所以能刷新界面,是因为它继承自 StatefulWidget。与 StatelessWidget 不同,StatefulWidget 本身不可变,真正保存可变数据的是它配套的 State 对象。当调用 setState() 时,Flutter 会把该 State 标记为“脏”,在下一帧重建其 build(),从而让界面反映新的状态值。
本例在计数器基础上增加一个主题开关:点击按钮切换亮色/暗色主题,同时保留计数功能。这样能同时观察两类状态变化——数值状态(计数)与外观状态(主题),并理解它们都通过 setState() 驱动重建。
状态生命周期与重建关系
下面这张图展示了 StatefulWidget 从创建到销毁的关键阶段,以及 setState() 如何触发 build() 重新执行。理解这条链路,就能明白为什么状态变量必须定义在 State 类中,而不是 StatefulWidget 里。
完整实现代码
下面是一个可直接运行的完整示例。它包含计数增减、主题切换两个状态,并用 ThemeData 的 brightness 控制亮暗外观。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _isDark = false;
void _toggleTheme() {
setState(() {
_isDark = !_isDark;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '主题计数器',
theme: _isDark
? ThemeData.dark(useMaterial3: true)
: ThemeData.light(useMaterial3: true),
home: CounterPage(
isDark: _isDark,
onToggleTheme: _toggleTheme,
),
);
}
}
class CounterPage extends StatefulWidget {
final bool isDark;
final VoidCallback onToggleTheme;
const CounterPage({
super.key,
required this.isDark,
required this.onToggleTheme,
});
@override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
void _decrement() {
setState(() {
if (_count > 0) _count--;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('StatefulWidget 计数器'),
actions: [
IconButton(
icon: Icon(widget.isDark ? Icons.light_mode : Icons.dark_mode),
onPressed: widget.onToggleTheme,
tooltip: '切换主题',
),
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'当前计数',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
Text(
'$_count',
style: Theme.of(context).textTheme.displayLarge,
),
const SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FilledButton.tonal(
onPressed: _decrement,
child: const Icon(Icons.remove),
),
const SizedBox(width: 16),
FilledButton(
onPressed: _increment,
child: const Icon(Icons.add),
),
],
),
],
),
),
);
}
}
关键点说明
_MyAppState管理主题状态_isDark,通过MaterialApp的theme属性切换亮暗主题。_CounterPageState管理计数状态_count,通过setState()更新并触发重建。- 主题切换按钮放在
AppBar的actions中,点击后调用父级传入的onToggleTheme回调。 - 计数减到 0 时不再继续减少,避免出现负数。
验证方式:运行后点击“+”或“−”按钮,数字应立即变化;点击右上角图标,整个界面应在亮色与暗色之间切换。若数字不变,检查是否在
setState() 内修改了 _count;若主题不变,检查 MaterialApp 的 theme 是否依赖了 _isDark。
常见错误:把
_count 定义在 StatefulWidget 子类中而不是 State 子类中。由于 StatefulWidget 本身不可变,每次重建都会创建新实例,定义在其中的变量无法保持状态。