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 应用界面。