Flutter 自定义绘制完全指南
·
Flutter 自定义绘制完全指南
引言
Flutter 的自定义绘制功能允许开发者创建各种复杂的图形和动画效果。本文将深入探讨 CustomPainter 的各种用法和高级技巧。
基础概念回顾
核心组件
- CustomPaint: 用于绘制自定义图形的 Widget
- CustomPainter: 定义绘制逻辑的类
- Canvas: 绘制画布
- Paint: 画笔配置
基本语法
class MyPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
// 绘制逻辑
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
// 使用
CustomPaint(
painter: MyPainter(),
child: const Text('Content'),
)
高级技巧一:基础绘制
绘制线条
class LinePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.blue
..strokeWidth = 2
..style = PaintingStyle.stroke;
canvas.drawLine(
const Offset(0, 0),
Offset(size.width, size.height),
paint,
);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
绘制圆形
class CirclePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final radius = min(size.width, size.height) / 2;
final paint = Paint()
..color = Colors.red
..style = PaintingStyle.fill;
canvas.drawCircle(center, radius, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
绘制矩形
class RectanglePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final rect = Rect.fromLTWH(10, 10, size.width - 20, size.height - 20);
final paint = Paint()
..color = Colors.green
..style = PaintingStyle.stroke
..strokeWidth = 2;
canvas.drawRect(rect, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
绘制路径
class PathPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final path = Path()
..moveTo(0, size.height / 2)
..quadraticBezierTo(
size.width / 4,
0,
size.width / 2,
size.height / 2,
)
..quadraticBezierTo(
size.width * 3 / 4,
size.height,
size.width,
size.height / 2,
);
final paint = Paint()
..color = Colors.purple
..strokeWidth = 3
..style = PaintingStyle.stroke;
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
高级技巧二:复杂绘制
绘制多边形
class PolygonPainter extends CustomPainter {
final int sides;
PolygonPainter({this.sides = 6});
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final radius = min(size.width, size.height) / 2 - 10;
final path = Path();
for (int i = 0; i < sides; i++) {
final angle = (i * 2 * pi) / sides - pi / 2;
final x = center.dx + radius * cos(angle);
final y = center.dy + radius * sin(angle);
if (i == 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
}
path.close();
final paint = Paint()
..color = Colors.orange
..style = PaintingStyle.fill;
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
绘制渐变
class GradientPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final rect = Offset.zero & size;
final gradient = LinearGradient(
colors: [Colors.blue, Colors.purple],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
final paint = Paint()..shader = gradient.createShader(rect);
canvas.drawRect(rect, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
绘制文字
class TextPainterWidget extends StatelessWidget {
const TextPainterWidget({super.key});
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: TextDrawingPainter(),
size: const Size(200, 100),
);
}
}
class TextDrawingPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final textPainter = TextPainter(
text: const TextSpan(
text: 'Hello Flutter',
style: TextStyle(
color: Colors.black,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
textDirection: TextDirection.ltr,
);
textPainter.layout(minWidth: 0, maxWidth: size.width);
textPainter.paint(
canvas,
Offset(
(size.width - textPainter.width) / 2,
(size.height - textPainter.height) / 2,
),
);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
高级技巧三:动画绘制
动画圆形
class AnimatedCircle extends StatefulWidget {
const AnimatedCircle({super.key});
@override
State<AnimatedCircle> createState() => _AnimatedCircleState();
}
class _AnimatedCircleState extends State<AnimatedCircle>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _radiusAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat(reverse: true);
_radiusAnimation = Tween<double>(begin: 20, end: 100).animate(_controller);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return CustomPaint(
painter: AnimatedCirclePainter(_radiusAnimation.value),
size: const Size(200, 200),
);
},
);
}
}
class AnimatedCirclePainter extends CustomPainter {
final double radius;
AnimatedCirclePainter(this.radius);
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final paint = Paint()
..color = Colors.blue
..style = PaintingStyle.fill;
canvas.drawCircle(center, radius, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
实战案例:饼图
class PieChartPainter extends CustomPainter {
final List<double> data;
final List<Color> colors;
PieChartPainter({required this.data, required this.colors});
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final radius = min(size.width, size.height) / 2 - 10;
double startAngle = -pi / 2;
final total = data.reduce((a, b) => a + b);
for (int i = 0; i < data.length; i++) {
final sliceAngle = (data[i] / total) * 2 * pi;
final path = Path()
..moveTo(center.dx, center.dy)
..arcTo(
Rect.fromCircle(center: center, radius: radius),
startAngle,
sliceAngle,
false,
)
..close();
final paint = Paint()..color = colors[i];
canvas.drawPath(path, paint);
startAngle += sliceAngle;
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
// 使用
CustomPaint(
painter: PieChartPainter(
data: [30, 20, 50],
colors: [Colors.red, Colors.green, Colors.blue],
),
size: const Size(200, 200),
)
实战案例:进度条
class ProgressPainter extends CustomPainter {
final double progress;
ProgressPainter({required this.progress});
@override
void paint(Canvas canvas, Size size) {
final backgroundPaint = Paint()
..color = Colors.grey[200]!
..style = PaintingStyle.fill;
final foregroundPaint = Paint()
..color = Colors.blue
..style = PaintingStyle.fill;
final borderRadius = BorderRadius.circular(size.height / 2);
final backgroundRect = Rect.fromLTWH(0, 0, size.width, size.height);
canvas.drawRRect(
RRect.fromRectAndRadius(backgroundRect, borderRadius),
backgroundPaint,
);
final foregroundRect = Rect.fromLTWH(0, 0, size.width * progress, size.height);
canvas.drawRRect(
RRect.fromRectAndRadius(foregroundRect, borderRadius),
foregroundPaint,
);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
实战案例:波形图
class WaveformPainter extends CustomPainter {
final List<double> data;
WaveformPainter({required this.data});
@override
void paint(Canvas canvas, Size size) {
if (data.isEmpty) return;
final paint = Paint()
..color = Colors.blue
..strokeWidth = 2
..style = PaintingStyle.stroke;
final path = Path();
final stepX = size.width / (data.length - 1);
for (int i = 0; i < data.length; i++) {
final x = i * stepX;
final y = size.height - (data[i] * size.height);
if (i == 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
}
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
常见问题与解决方案
Q1:绘制不显示?
A:确保设置了大小:
CustomPaint(
painter: MyPainter(),
size: const Size(200, 200), // 设置大小
)
Q2:动画卡顿?
A:使用 repaintBoundary:
RepaintBoundary(
child: CustomPaint(painter: AnimatedPainter()),
)
Q3:文字不显示?
A:设置 textDirection:
TextPainter(
text: const TextSpan(text: 'Hello'),
textDirection: TextDirection.ltr, // 必须设置
)
最佳实践
1. 优化绘制性能
class MyPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
// 只在必要时重绘
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false; // 不需要重绘
}
}
2. 复用 Paint 对象
class MyPainter extends CustomPainter {
final Paint _paint = Paint()..color = Colors.blue;
@override
void paint(Canvas canvas, Size size) {
canvas.drawCircle(Offset(50, 50), 20, _paint);
}
}
3. 使用 save/restore
@override
void paint(Canvas canvas, Size size) {
canvas.save();
canvas.translate(50, 50);
canvas.rotate(pi / 4);
canvas.drawRect(const Rect.fromLTWH(0, 0, 100, 100), _paint);
canvas.restore();
}
总结
Flutter 的自定义绘制功能非常强大。通过本文的学习,你应该能够:
- 绘制基本图形(线条、圆形、矩形、路径)
- 创建复杂图形(多边形、渐变、文字)
- 实现动画绘制
- 创建各种图表(饼图、进度条、波形图)
- 优化绘制性能
掌握这些技巧,能够帮助你创建更加复杂和吸引人的视觉效果。
更多推荐
所有评论(0)