Flutter CustomPainter 详解
CustomPainter 是 Flutter 中用于自定义绘制的核心组件,允许你通过底层的绘制 API 实现任意形状、动画和视觉效果,比如自定义图表、特殊按钮、手绘风格的 UI 等。
一、核心概念与基础用法
1. 核心类说明
CustomPainter:抽象类,必须实现paint(Canvas canvas, Size size)和shouldRepaint(CustomPainter oldDelegate)两个方法。paint():绘制逻辑的核心,通过Canvas执行绘制操作。shouldRepaint():判断是否需要重绘(性能优化关键),返回true表示需要重绘。
Canvas:绘制画布,提供绘制点、线、矩形、圆形、路径等方法。Paint:画笔,定义颜色、宽度、样式(填充 / 描边)、抗锯齿等属性。
2. 基础示例:绘制一个带圆角的渐变矩形
dart
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('CustomPainter 示例')),
body: Center(
// 使用 CustomPaint 包裹自定义绘制逻辑
child: CustomPaint(
size: const Size(200, 100), // 绘制区域大小
painter: MyCustomPainter(), // 自定义的 Painter
),
),
),
);
}
}
// 自定义 CustomPainter
class MyCustomPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
// 1. 创建画笔
final Paint paint = Paint()
..shader = LinearGradient(
colors: [Colors.blueAccent, Colors.purpleAccent],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
).createShader(Rect.fromLTWH(0, 0, size.width, size.height))
..style = PaintingStyle.fill // 填充模式(stroke 为描边)
..isAntiAlias = true; // 抗锯齿
// 2. 创建路径(圆角矩形)
final path = Path()
..addRRect(RRect.fromRectAndRadius(
Rect.fromLTWH(0, 0, size.width, size.height),
const Radius.circular(20),
));
// 3. 画布绘制路径
canvas.drawPath(path, paint);
// 额外:绘制文字
final textPainter = TextPainter(
text: const TextSpan(
text: '自定义绘制',
style: TextStyle(color: Colors.white, fontSize: 20),
),
textDirection: TextDirection.ltr,
);
textPainter.layout();
// 文字居中
textPainter.paint(
canvas,
Offset(
(size.width - textPainter.width) / 2,
(size.height - textPainter.height) / 2,
),
);
}
@override
bool shouldRepaint(covariant MyCustomPainter oldDelegate) {
// 无动态数据时返回 false(避免不必要的重绘)
return false;
}
}
3. 代码关键解释
CustomPaint:承载自定义绘制的容器,size定义绘制区域的宽高。Paint:配置画笔属性,示例中使用了渐变着色器(shader),也可直接用color设置纯色。Path:路径对象,用于定义复杂形状(如圆角矩形、多边形、曲线),Canvas通过绘制路径实现自定义形状。TextPainter:用于在画布上绘制文字(Canvas本身无直接绘制文字的方法)。
二、进阶用法:动态绘制与动画
如果需要实现动态效果(比如绘制进度条),可以结合 Animation 和 ChangeNotifier:
dart
import 'package:flutter/material.dart';
// 带进度的自定义绘制器(支持动画)
class ProgressPainter extends CustomPainter with ChangeNotifier {
double progress; // 0~1 的进度值
ProgressPainter({this.progress = 0.0}) : super(repaint: null);
// 更新进度并通知重绘
void updateProgress(double value) {
progress = value.clamp(0.0, 1.0); // 限制在 0~1
notifyListeners();
}
@override
void paint(Canvas canvas, Size size) {
// 1. 绘制背景圆
final bgPaint = Paint()
..color = Colors.grey[200]!
..style = PaintingStyle.stroke
..strokeWidth = 10;
canvas.drawCircle(
Offset(size.width / 2, size.height / 2),
size.width / 2 - 5,
bgPaint,
);
// 2. 绘制进度圆弧
final progressPaint = Paint()
..color = Colors.blueAccent
..style = PaintingStyle.stroke
..strokeWidth = 10
..strokeCap = StrokeCap.round; // 圆角端点
canvas.drawArc(
Rect.fromCircle(
center: Offset(size.width / 2, size.height / 2),
radius: size.width / 2 - 5,
),
-90 * (3.14159 / 180), // 起始角度(-90度对应12点钟方向)
360 * progress * (3.14159 / 180), // 扫过的角度(弧度)
false, // 是否闭合路径
progressPaint,
);
// 3. 绘制进度文字
final textPainter = TextPainter(
text: TextSpan(
text: '${(progress * 100).toInt()}%',
style: const TextStyle(color: Colors.black, fontSize: 24),
),
textDirection: TextDirection.ltr,
);
textPainter.layout();
textPainter.paint(
canvas,
Offset(
(size.width - textPainter.width) / 2,
(size.height - textPainter.height) / 2,
),
);
}
@override
bool shouldRepaint(covariant ProgressPainter oldDelegate) {
// 进度变化时重绘
return oldDelegate.progress != progress;
}
}
// 使用示例
class ProgressPage extends StatefulWidget {
const ProgressPage({super.key});
@override
State<ProgressPage> createState() => _ProgressPageState();
}
class _ProgressPageState extends State<ProgressPage>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late ProgressPainter _painter;
@override
void initState() {
super.initState();
_painter = ProgressPainter();
// 创建动画控制器(3秒从0到1)
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 3),
)..addListener(() {
_painter.updateProgress(_controller.value);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('动态绘制进度条')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CustomPaint(
size: const Size(200, 200),
painter: _painter,
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
_controller.reset();
_controller.forward();
},
child: const Text('开始动画'),
),
],
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
三、关键优化点
- shouldRepaint 优化 :仅在绘制数据变化时返回
true,避免无意义的重绘。 - 抗锯齿 :设置
Paint.isAntiAlias = true,避免绘制边缘出现锯齿。 - 复用对象 :避免在
paint()方法内重复创建Paint、Path等对象(可提为成员变量),减少内存开销。 - 裁剪区域 :使用
canvas.clipRect()限制绘制区域,避免绘制超出可视范围的内容。
总结
CustomPainter是 Flutter 自定义绘制的核心,通过Canvas(画布)+Paint(画笔)实现任意视觉效果,需实现paint()和shouldRepaint()方法。- 基础用法可绘制静态形状(如矩形、圆形、路径),结合动画 / 状态管理可实现动态效果(如进度条、动效图标)。
- 性能优化关键:合理实现
shouldRepaint()、复用绘制对象、开启抗锯齿,避免不必要的重绘。