02_Widget系统
Flutter Widget 系统详解
Flutter 中一切皆 Widget。Widget 是描述 UI 的不可变配置对象,类似 React 的 Virtual DOM 节点。
Widget 是什么
Flutter 中一切皆 Widget:文字、按钮、布局、动画、主题、导航……都是 Widget。
与 Web 对比:
| Web | Flutter |
|---|---|
<div> |
Container |
<p> |
Text |
<img> |
Image |
<button> |
ElevatedButton / TextButton |
<input> |
TextField |
| Flex 容器 | Row / Column |
position: absolute |
Stack + Positioned |
| Vue/React 组件 | StatelessWidget / StatefulWidget |
StatelessWidget(无状态组件)
对应 Vue/React 的纯展示组件(只接收 props,没有内部状态)。
class UserCard extends StatelessWidget {
final String name;
final String avatarUrl;
final int followerCount;
// Flutter 组件的"props"通过构造函数传入
const UserCard({
super.key,
required this.name,
required this.avatarUrl,
required this.followerCount,
});
@override
Widget build(BuildContext context) {
// build() 相当于 Vue 的 template / React 的 return JSX
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
CircleAvatar(backgroundImage: NetworkImage(avatarUrl)),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(name, style: const TextStyle(fontWeight: FontWeight.bold)),
Text('$followerCount followers'),
],
),
],
),
),
);
}
}
// 使用(类似 <UserCard name="Alice" ... />)
UserCard(
name: 'Alice',
avatarUrl: 'https://example.com/avatar.jpg',
followerCount: 1024,
)
const构造函数:如果 Widget 的所有参数都是编译期常量,用const创建实例。Flutter 会缓存这些 Widget,跳过重建,是低成本的性能优化。
StatefulWidget(有状态组件)
对应 Vue/React 中带内部响应式数据的组件。
class CounterWidget extends StatefulWidget {
const CounterWidget({super.key});
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
// State 类持有可变数据(类似 Vue 的 data() / React 的 useState)
class _CounterWidgetState extends State<CounterWidget> {
int _count = 0;
final List<String> _history = [];
// ① 初始化(类似 Vue created / React useEffect(fn, []))
@override
void initState() {
super.initState();
print('组件初始化');
}
// ② 清理(类似 Vue beforeUnmount / React useEffect return cleanup)
@override
void dispose() {
print('组件销毁');
super.dispose();
}
// ③ 更新状态(类似 Vue 的 this.count++ / React 的 setState)
void _increment() {
setState(() { // setState 通知 Flutter 重建 UI
_count++;
_history.add('第 $_count 次');
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('计数: $_count', style: const TextStyle(fontSize: 24)),
ElevatedButton(onPressed: _increment, child: const Text('+1')),
],
);
}
}
Widget 生命周期
StatefulWidget 生命周期(类比 Vue / React):
createState() → 创建 State 对象
↓
initState() → 类似 Vue created / React useEffect(fn, [])
↓ 仅执行一次,适合初始化数据、订阅事件
didChangeDependencies() → 依赖的 context 数据变化时触发
↓
build() → 类似 Vue template / React return JSX
↓ 每次 setState 或父 Widget 更新都会调用
didUpdateWidget() → 类似 React useEffect(fn, [props])
↓ 父 Widget 重建且参数变化时触发
deactivate() → 从树中临时移除
↓
dispose() → 类似 Vue beforeUnmount / React cleanup
释放 Controller、Timer、StreamSubscription
setState 注意事项
// ❌ 错误:async 操作后 widget 可能已卸载
Future<void> loadData() async {
final data = await fetchApi();
setState(() { _data = data; }); // 可能报错:already disposed
}
// ✅ 正确:检查 mounted
Future<void> loadData() async {
final data = await fetchApi();
if (mounted) {
setState(() { _data = data; });
}
}
// ❌ 错误:在 build() 中调用 setState(死循环!)
Widget build(BuildContext context) {
setState(() { _count++; }); // 触发重建 → 再次 setState → 无限循环
return Text('$_count');
}
Widget 树
// Flutter 的 UI = 一棵 Widget 树
// 每次 setState,Flutter 只重建状态变化的子树(diff 优化)
MaterialApp
└── Scaffold
├── AppBar
│ └── Text('标题')
└── Column
├── Text('内容')
├── SizedBox(height: 16)
└── ElevatedButton
└── Text('按钮')
常用基础 Widget
Text(文本)
// 基础
Text('Hello Flutter')
// 带样式(类比 CSS font-size / font-weight / color)
Text(
'标题',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.blue,
letterSpacing: 1.2,
height: 1.5, // 行高倍数
decoration: TextDecoration.underline,
),
textAlign: TextAlign.center, // 类比 text-align
maxLines: 2,
overflow: TextOverflow.ellipsis, // 类比 text-overflow: ellipsis
)
// 富文本(类比 HTML 内联不同样式)
RichText(
text: TextSpan(
style: const TextStyle(color: Colors.black, fontSize: 16),
children: [
const TextSpan(text: '普通文字 '),
TextSpan(
text: '蓝色加粗',
style: const TextStyle(color: Colors.blue, fontWeight: FontWeight.bold),
),
],
),
)
Image(图片)
// 网络图片
Image.network(
'https://example.com/photo.jpg',
width: 200,
height: 150,
fit: BoxFit.cover, // 类比 object-fit: cover
loadingBuilder: (context, child, progress) {
if (progress == null) return child;
return const CircularProgressIndicator();
},
errorBuilder: (context, error, stack) => const Icon(Icons.broken_image),
)
// 本地图片(需在 pubspec.yaml 声明 assets)
Image.asset('assets/images/logo.png')
// 圆形头像
CircleAvatar(
radius: 40,
backgroundImage: NetworkImage('https://example.com/avatar.jpg'),
)
BoxFit 对应 CSS object-fit:
| Flutter | CSS |
|---|---|
BoxFit.cover |
object-fit: cover |
BoxFit.contain |
object-fit: contain |
BoxFit.fill |
object-fit: fill |
Icon(图标)
Icon(Icons.home)
Icon(Icons.search, size: 32, color: Colors.blue)
Icon(Icons.favorite, color: Colors.red)
// 常用图标
// Icons.arrow_back / Icons.close / Icons.more_vert
// Icons.share / Icons.delete / Icons.edit / Icons.add
Button(按钮)
// 主操作按钮(凸起)
ElevatedButton(
onPressed: () => print('点击'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: const Text('提交'),
)
// 文字按钮(次要操作)
TextButton(onPressed: () {}, child: const Text('取消'))
// 描边按钮
OutlinedButton(onPressed: () {}, child: const Text('次要操作'))
// 图标按钮(常用于 AppBar)
IconButton(icon: const Icon(Icons.search), onPressed: () {})
// 禁用状态:onPressed 设为 null
ElevatedButton(onPressed: null, child: const Text('不可用'))