Widget 系统:组合、const 优化与常用组件
mediumflutterwidgetstatelessstatefulconstcompositionlayout
Flutter Widget 设计哲学
"一切皆 Widget"是 Flutter 的核心理念。Flutter 不使用 OOP 继承来复用 UI,而是组合(Composition):
❌ 继承:class IconButton extends Icon { /* 混合逻辑 */ }
✅ 组合:
IconButton = GestureDetector(
onTap: ...,
child: Padding(
padding: ...,
child: Icon(Icons.add),
),
)
这使得每个 Widget 职责单一,易于测试和复用。
StatelessWidget vs StatefulWidget
// 无状态:输入(构造参数)不变则 UI 不变
class Greeting extends StatelessWidget {
final String name;
const Greeting({super.key, required this.name});
@override
Widget build(BuildContext context) {
return Text('Hello, $name!');
}
}
// 有状态:Widget 有内部可变状态
class ToggleButton extends StatefulWidget {
const ToggleButton({super.key});
@override
State<ToggleButton> createState() => _ToggleButtonState();
}
class _ToggleButtonState extends State<ToggleButton> {
bool _isOn = false;
@override
Widget build(BuildContext context) {
return Switch(value: _isOn, onChanged: (v) => setState(() => _isOn = v));
}
}
原则:能用 StatelessWidget 就用,不需要内部状态时不要用 Stateful。
const Widget 优化
const Widget 只创建一次,在整个 App 生命周期中复用同一实例,不参与 rebuild:
// ✅ 推荐:可以加 const 的地方都加
class MyPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Column(
children: [
Icon(Icons.star), // const Icon
Text('Hello Flutter'), // const Text
SizedBox(height: 16), // const SizedBox
],
),
),
);
}
}
什么时候不能用 const:
- 用到运行时变量(如接口返回数据)
- 用到
Theme.of(context)、MediaQuery.of(context)等 context 依赖
常用布局 Widget
// Row / Column:横向/纵向排列
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [Icon(Icons.home), Text('Home')],
)
// Stack:叠加(绝对定位)
Stack(
children: [
Image.network(url),
const Positioned(bottom: 8, right: 8, child: Badge()),
],
)
// Expanded / Flexible:弹性分配主轴空间
Row(
children: [
Expanded(flex: 2, child: SearchBar()), // 占 2/3
Expanded(flex: 1, child: FilterButton()), // 占 1/3
],
)
// SizedBox / Padding:间距
const SizedBox(height: 16) // 固定间距
const Padding(
padding: EdgeInsets.all(16),
child: Card(),
)
常用功能 Widget
// GestureDetector:手势识别
GestureDetector(
onTap: () => print('tapped'),
onLongPress: () => print('long press'),
child: Container(color: Colors.blue, width: 100, height: 100),
)
// InkWell:带水波纹效果
InkWell(
onTap: () {},
borderRadius: BorderRadius.circular(8),
child: const Padding(padding: EdgeInsets.all(8), child: Text('Click me')),
)
// AnimatedContainer:属性变化自动动画
AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: _expanded ? 200 : 100,
height: 100,
color: _expanded ? Colors.blue : Colors.red,
)
// FutureBuilder:异步数据
FutureBuilder<User>(
future: fetchUser(),
builder: (_, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
if (snapshot.hasError) return Text('Error: ${snapshot.error}');
return Text(snapshot.data!.name);
},
)
// ListView.builder:高效列表(仅创建可见项)
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ListTile(title: Text(items[index].name)),
)
Widget 拆分原则
拆分线索:
- 单个
build方法超过 100 行 → 考虑拆分 - 相同的 UI 片段出现 2+ 次 → 提取为独立 Widget
- 某个 Widget 频繁 rebuild 而子树不需要重建 → 拆分子树 +
const
// ✅ 拆小(提取为 Widget,可加 const 缓存)
class ProductCard extends StatelessWidget {
final Product product;
const ProductCard({super.key, required this.product});
@override
Widget build(BuildContext context) => /* ... */;
}
// ❌ 提取为 builder 方法(无法被缓存,每次 rebuild 都调用)
Widget _buildProductCard(Product product) => /* ... */;