StatefulWidget将不可变的配置(Widget)与可变的状态(State)分离。Widget每次重建都是新实例,但State对象会被保留。
// ✅ 验证通过:Flutter 3.22
// Widget是短期对象——每次重建都new一个
// State是长期对象——跨重建保持
class Counter extends StatefulWidget {
// Widget部分:不可变配置
final int initialValue;
final Color color;
const Counter({
this.initialValue = 0,
this.color = Colors.blue,
super.key,
});
// createState返回State实例
// 只在Element首次创建时调用
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
// State部分:可变数据
late int _count;
// 访问Widget的配置——通过widget属性
// widget.color → Widget的color
// widget.initialValue → Widget的initialValue
@override
void initState() {
super.initState();
_count = widget.initialValue; // 使用Widget配置初始化
}
// 当Widget配置变化时
@override
void didUpdateWidget(covariant Counter oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.initialValue != widget.initialValue) {
_count = widget.initialValue;
}
}
void increment() => setState(() => _count++);
void decrement() => setState(() => _count--);
void reset() => setState(() => _count = widget.initialValue);
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('$_count', style: TextStyle(fontSize: 48, color: widget.color)),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(onPressed: decrement, icon: const Icon(Icons.remove)),
ElevatedButton(onPressed: reset, child: const Text('Reset')),
IconButton(onPressed: increment, icon: const Icon(Icons.add)),
],
),
],
);
}
}
// ✅ 验证通过
/// setState做了什么?
/// 1. 执行传入的回调函数(修改状态)
/// 2. 标记当前Element为dirty
/// 3. 在下一帧,Framework调用build()重建UI
/// 4. 比较新旧Widget树,更新Render树
void setStateDeepDive() {
// ✅ 正确用法
setState(() {
_counter++; // 在回调内修改状态
});
// ❌ 错误用法1:忘记setState
_counter++; // 状态变了,UI不会更新!
// ❌ 错误用法2:在build中调用setState
@override
Widget build(BuildContext context) {
setState(() {}); // 无限循环!build → setState → build → ...
return Container();
}
// ❌ 错误用法3:异步回调中使用过期的context
void onPressed() async {
await Future.delayed(const Duration(seconds: 1));
// 如果Widget已经被dispose,setState会抛异常
if (mounted) { // ✅ 先检查mounted
setState(() {
_data = 'loaded';
});
}
}
// ❌ 错误用法4:setState粒度太粗
setState(() {
// 修改了10个字段,但只有2个影响UI
// 应该只修改影响UI的字段,或者拆分Widget
});
// ✅ 最佳实践:精细控制setState范围
// 方案1:拆分为多个小Widget,各自管理状态
// 方案2:使用ValueNotifier + ValueListenableBuilder
}
/// ValueNotifier——轻量级状态管理
class CounterWithNotifier extends StatefulWidget {
const CounterWithNotifier({super.key});
@override
State<CounterWithNotifier> createState() => _CounterWithNotifierState();
}
class _CounterWithNotifierState extends State<CounterWithNotifier> {
// 只追踪需要的数据
final _counter = ValueNotifier<int>(0);
@override
void dispose() {
_counter.dispose(); // 必须释放
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
// 只有这个部分会重建!
ValueListenableBuilder<int>(
valueListenable: _counter,
builder: (context, value, child) {
return Text('$value', style: const TextStyle(fontSize: 48));
},
),
// 这部分不会重建
const Text('计数器示例'),
Row(
children: [
IconButton(
onPressed: () => _counter.value--,
icon: const Icon(Icons.remove),
),
IconButton(
onPressed: () => _counter.value++,
icon: const Icon(Icons.add),
),
],
),
],
);
}
}
// ✅ 验证通过
/// TextEditingController — 文本输入控制器
class TextFormDemo extends StatefulWidget {
const TextFormDemo({super.key});
@override
State<TextFormDemo> createState() => _TextFormDemoState();
}
class _TextFormDemoState extends State<TextFormDemo> {
final _nameController = TextEditingController();
final _emailController = TextEditingController();
@override
void initState() {
super.initState();
// 监听文本变化
_nameController.addListener(() {
print('名字变化: ${_nameController.text}');
});
}
@override
void dispose() {
_nameController.dispose();
_emailController.dispose();
super.dispose();
}
void _submit() {
print('Name: ${_nameController.text}');
print('Email: ${_emailController.text}');
}
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
controller: _nameController,
decoration: const InputDecoration(
labelText: '姓名',
hintText: '请输入姓名',
prefixIcon: Icon(Icons.person),
),
),
TextField(
controller: _emailController,
decoration: const InputDecoration(
labelText: '邮箱',
hintText: '请输入邮箱',
prefixIcon: Icon(Icons.email),
),
keyboardType: TextInputType.emailAddress,
),
ElevatedButton(
onPressed: _submit,
child: const Text('提交'),
),
],
);
}
}
/// AnimationController — 动画控制器
class PulseWidget extends StatefulWidget {
final Widget child;
const PulseWidget({required this.child, super.key});
@override
State<PulseWidget> createState() => _PulseWidgetState();
}
class _PulseWidgetState extends State<PulseWidget> with SingleTickerProviderStateMixin {
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
)..repeat(reverse: true); // 来回重复
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ScaleTransition(
scale: Tween<double>(begin: 0.95, end: 1.05).animate(_controller),
child: widget.child,
);
}
}
/// ScrollController — 滚动控制器
class ScrollDemo extends StatefulWidget {
const ScrollDemo({super.key});
@override
State<ScrollDemo> createState() => _ScrollDemoState();
}
class _ScrollDemoState extends State<ScrollDemo> {
final _scrollController = ScrollController();
bool _showFab = false;
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
}
void _onScroll() {
var show = _scrollController.offset > 200;
if (show != _showFab) {
setState(() => _showFab = show);
}
}
void _scrollToTop() {
_scrollController.animateTo(
0,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
controller: _scrollController,
itemCount: 100,
itemBuilder: (context, index) => ListTile(title: Text('Item $index')),
),
floatingActionButton: _showFab
? FloatingActionButton(
onPressed: _scrollToTop,
child: const Icon(Icons.arrow_upward),
)
: null,
);
}
}
// ✅ 验证通过:Flutter 3.22 + Dart 3.4
// 文件:lib/interactive_todo.dart
import 'package:flutter/material.dart';
void main() => runApp(const TodoApp());
class TodoApp extends StatelessWidget {
const TodoApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '交互式待办',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const TodoPage(),
);
}
}
// 数据模型
class Todo {
final String id;
String title;
bool isDone;
DateTime createdAt;
Priority priority;
Todo({
required this.id,
required this.title,
this.isDone = false,
DateTime? createdAt,
this.priority = Priority.medium,
}) : createdAt = createdAt ?? DateTime.now();
void toggle() => isDone = !isDone;
}
enum Priority { low, medium, high }
extension PriorityX on Priority {
String get label => switch (this) {
Priority.low => '低',
Priority.medium => '中',
Priority.high => '高',
};
Color get color => switch (this) {
Priority.low => Colors.green,
Priority.medium => Colors.orange,
Priority.high => Colors.red,
};
}
// 主页面
class TodoPage extends StatefulWidget {
const TodoPage({super.key});
@override
State<TodoPage> createState() => _TodoPageState();
}
class _TodoPageState extends State<TodoPage> {
final List<Todo> _todos = [];
final _inputController = TextEditingController();
Priority _selectedPriority = Priority.medium;
TodoFilter _filter = TodoFilter.all;
SortMode _sort = SortMode.created;
int get _activeCount => _todos.where((t) => !t.isDone).length;
int get _doneCount => _todos.where((t) => t.isDone).length;
List<Todo> get _filteredTodos {
var filtered = switch (_filter) {
TodoFilter.all => _todos,
TodoFilter.active => _todos.where((t) => !t.isDone),
TodoFilter.done => _todos.where((t) => t.isDone),
};
var sorted = filtered.toList();
sorted.sort((a, b) => switch (_sort) {
SortMode.created => b.createdAt.compareTo(a.createdAt),
SortMode.priority => b.priority.index.compareTo(a.priority.index),
SortMode.alpha => a.title.compareTo(b.title),
});
return sorted;
}
void _addTodo() {
var title = _inputController.text.trim();
if (title.isEmpty) return;
setState(() {
_todos.insert(0, Todo(
id: DateTime.now().millisecondsSinceEpoch.toString(),
title: title,
priority: _selectedPriority,
));
});
_inputController.clear();
}
void _toggleTodo(String id) {
setState(() {
var todo = _todos.firstWhere((t) => t.id == id);
todo.toggle();
});
}
void _deleteTodo(String id) {
setState(() {
_todos.removeWhere((t) => t.id == id);
});
}
void _clearDone() {
setState(() {
_todos.removeWhere((t) => t.isDone);
});
}
@override
void dispose() {
_inputController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('待办事项'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
actions: [
PopupMenuButton<SortMode>(
icon: const Icon(Icons.sort),
onSelected: (mode) => setState(() => _sort = mode),
itemBuilder: (_) => [
const PopupMenuItem(value: SortMode.created, child: Text('按创建时间')),
const PopupMenuItem(value: SortMode.priority, child: Text('按优先级')),
const PopupMenuItem(value: SortMode.alpha, child: Text('按名称')),
],
),
],
),
body: Column(
children: [
// 输入区域
_buildInputArea(),
// 过滤栏
_buildFilterBar(),
// 统计
_buildStats(),
// 列表
Expanded(
child: _filteredTodos.isEmpty
? const Center(child: Text('暂无待办'))
: ListView.builder(
itemCount: _filteredTodos.length,
itemBuilder: (context, index) {
var todo = _filteredTodos[index];
return _TodoTile(
todo: todo,
onToggle: () => _toggleTodo(todo.id),
onDelete: () => _deleteTodo(todo.id),
);
},
),
),
],
),
);
}
Widget _buildInputArea() {
return Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
Expanded(
child: TextField(
controller: _inputController,
decoration: const InputDecoration(
hintText: '添加新待办...',
border: OutlineInputBorder(),
),
onSubmitted: (_) => _addTodo(),
),
),
const SizedBox(width: 8),
IconButton(
onPressed: () => setState(() {
_selectedPriority = Priority.values[(_selectedPriority.index + 1) % 3];
}),
icon: Icon(Icons.flag, color: _selectedPriority.color),
tooltip: '优先级: ${_selectedPriority.label}',
),
FilledButton(
onPressed: _addTodo,
child: const Icon(Icons.add),
),
],
),
);
}
Widget _buildFilterBar() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
children: TodoFilter.values.map((filter) {
var isSelected = _filter == filter;
return Padding(
padding: const EdgeInsets.only(right: 8),
child: FilterChip(
selected: isSelected,
label: Text(filter.label),
onSelected: (_) => setState(() => _filter = filter),
),
);
}).toList(),
),
);
}
Widget _buildStats() {
return Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
Text('总计: ${_todos.length}'),
const SizedBox(width: 16),
Text('待办: $_activeCount'),
const SizedBox(width: 16),
Text('完成: $_doneCount'),
const Spacer(),
if (_doneCount > 0)
TextButton(
onPressed: _clearDone,
child: const Text('清除已完成'),
),
],
),
);
}
}
enum TodoFilter { all, active, done }
extension on TodoFilter {
String get label => switch (this) {
TodoFilter.all => '全部',
TodoFilter.active => '待办',
TodoFilter.done => '已完成',
};
}
enum SortMode { created, priority, alpha }
// 待办项Widget
class _TodoTile extends StatelessWidget {
final Todo todo;
final VoidCallback onToggle;
final VoidCallback onDelete;
const _TodoTile({
required this.todo,
required this.onToggle,
required this.onDelete,
});
@override
Widget build(BuildContext context) {
return Dismissible(
key: ValueKey(todo.id),
direction: DismissDirection.endToStart,
onDismissed: (_) => onDelete(),
background: Container(
color: Colors.red,
alignment: Alignment.centerRight,
padding: const EdgeInsets.only(right: 16),
child: const Icon(Icons.delete, color: Colors.white),
),
child: ListTile(
leading: Checkbox(
value: todo.isDone,
onChanged: (_) => onToggle(),
),
title: Text(
todo.title,
style: TextStyle(
decoration: todo.isDone ? TextDecoration.lineThrough : null,
color: todo.isDone ? Colors.grey : null,
),
),
trailing: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: todo.priority.color.withOpacity(0.2),
borderRadius: BorderRadius.circular(12),
),
child: Text(
todo.priority.label,
style: TextStyle(color: todo.priority.color, fontSize: 12),
),
),
),
);
}
}
使用AnimationController或Timer实现一个秒表:
使用StatefulWidget实现ExpandableList:
使用ReorderableListView实现可拖拽排序的列表。
下一课:布局(Row/Column/Stack)——Flutter三大核心布局组件的深入讲解。