Flutter 自定义组件开发指南
Flutter 自定义组件指南
在 Flutter 中,自定义组件是构建独特用户界面的核心方式。以下是创建和使用自定义组件的全面指南:
1. 基本自定义组件
创建自定义组件最简单的方式是组合现有组件:
class CustomButton extends StatelessWidget {final String text;final VoidCallback onPressed;const CustomButton({required this.text,required this.onPressed,Key? key,}) : super(key: key);Widget build(BuildContext context) {return ElevatedButton(style: ElevatedButton.styleFrom(padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8),),),onPressed: onPressed,child: Text(text),);}
}
2. 有状态的自定义组件
当需要维护内部状态时,使用 StatefulWidget
:
class CounterButton extends StatefulWidget {final String label;const CounterButton({required this.label, Key? key}) : super(key: key); _CounterButtonState createState() => _CounterButtonState();
}class _CounterButtonState extends State<CounterButton> {int _count = 0;Widget build(BuildContext context) {return OutlinedButton(onPressed: () {setState(() {_count++;});},child: Text('${widget.label}: $_count'),);}
}
3. 自定义绘制 (CustomPaint)
对于完全自定义的绘制,使用 CustomPaint
:
class CircleProgress extends StatelessWidget {final double progress;const CircleProgress({required this.progress, Key? key}) : super(key: key);Widget build(BuildContext context) {return CustomPaint(size: Size(100, 100),painter: _CircleProgressPainter(progress),);}
}class _CircleProgressPainter extends CustomPainter {final double progress;_CircleProgressPainter(this.progress);void paint(Canvas canvas, Size size) {final paint = Paint()..color = Colors.blue..strokeWidth = 5..style = PaintingStyle.stroke;final center = Offset(size.width/2, size.height/2);final radius = size.width/2 - 5;// 绘制背景圆canvas.drawCircle(center, radius, paint..color = Colors.grey[300]!);// 绘制进度弧canvas.drawArc(Rect.fromCircle(center: center, radius: radius),-0.5 * pi,2 * pi * progress,false,paint..color = Colors.blue,);} bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
4. 组合复杂组件
class ProfileCard extends StatelessWidget {final String name;final String role;final String imageUrl;const ProfileCard({required this.name,required this.role,required this.imageUrl,Key? key,}) : super(key: key);Widget build(BuildContext context) {return Card(elevation: 4,child: Padding(padding: EdgeInsets.all(16),child: Column(mainAxisSize: MainAxisSize.min,children: [CircleAvatar(radius: 40,backgroundImage: NetworkImage(imageUrl),),SizedBox(height: 16),Text(name,style: Theme.of(context).textTheme.headline6,),SizedBox(height: 4),Text(role,style: Theme.of(context).textTheme.subtitle1?.copyWith(color: Colors.grey,),),SizedBox(height: 16),Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly,children: [IconButton(icon: Icon(Icons.message), onPressed: () {}),IconButton(icon: Icon(Icons.phone), onPressed: () {}),IconButton(icon: Icon(Icons.email), onPressed: () {}),],),],),),);}
}
5. 自定义布局组件
class WrapWithPadding extends StatelessWidget {final Widget child;final EdgeInsets padding;const WrapWithPadding({required this.child,this.padding = const EdgeInsets.all(16),Key? key,}) : super(key: key);Widget build(BuildContext context) {return Padding(padding: padding,child: child,);}
}
6. 动画自定义组件
class AnimatedToggle extends StatefulWidget {final bool value;final ValueChanged<bool> onChanged;const AnimatedToggle({required this.value,required this.onChanged,Key? key,}) : super(key: key); _AnimatedToggleState createState() => _AnimatedToggleState();
}class _AnimatedToggleState extends State<AnimatedToggle> with SingleTickerProviderStateMixin {late AnimationController _controller;void initState() {super.initState();_controller = AnimationController(duration: Duration(milliseconds: 200),vsync: this,)..value = widget.value ? 1.0 : 0.0;}void didUpdateWidget(AnimatedToggle oldWidget) {super.didUpdateWidget(oldWidget);if (widget.value != oldWidget.value) {widget.value ? _controller.forward() : _controller.reverse();}}Widget build(BuildContext context) {return GestureDetector(onTap: () {widget.onChanged(!widget.value);},child: Container(width: 60,height: 30,decoration: BoxDecoration(borderRadius: BorderRadius.circular(15),color: Colors.grey[300],),child: AnimatedBuilder(animation: _controller,builder: (context, child) {return Stack(children: [Positioned(left: _controller.value * 30,child: Container(width: 30,height: 30,decoration: BoxDecoration(shape: BoxShape.circle,color: Colors.blue,),),),],);},),),);}void dispose() {_controller.dispose();super.dispose();}
}
最佳实践
- 保持组件单一职责 - 每个组件应该只做一件事
- 合理使用参数 - 通过构造函数参数配置组件行为
- 考虑主题一致性 - 使用
Theme.of(context)
保持应用风格统一 - 文档注释 - 为公共组件添加文档注释
- 性能优化 - 对复杂组件使用
const
构造函数和shouldRepaint
通过组合和自定义组件,你可以创建出完全符合设计需求的 Flutter 应用界面。