← Frontend / Flutter

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('不可用'))