当内置的Flutter小部件不够用时。 CustomPainter来拯救我们!
所以,让我们看看如何用它来绘制这个任务完成环的动画的UI。
任务完成环的动画
这个动画序列是由多个阶段组成的。
- 显示未完成的任务的用户界面
- 当我们点击并按住任务时,逐渐填充环形图
- 显示一个用于确认的复选标记图标
- 显示已完成任务的用户界面
特别是,这种 "部分完成 "的环形UI并不是我们可以用Flutter SDK中的内置小部件来创建的。
关键点
任务完成环可以表示为一个StatelessWidget 子类,有一个AspectRatio 和一个CustomPaint 子类。
class TaskCompletionRing extends StatelessWidget {
@override
Widget build(BuildContext context) {
return AspectRatio(
// Use 1.0 to ensure that the custom painter
// will draw inside a container with width == height
aspectRatio: 1.0,
child: CustomPaint(
painter: RingPainter(),
),
);
}
}
然后我们可以添加一个CustomPainter 子类,并重写paint() 和shouldRepaint() 方法。
class RingPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
paint() 方法有两个参数。
- 一个
canvas对象,我们可以用它来画东西 - 一个
size对象,告诉我们绘图区域有多大。
shouldRepaint() 方法应该在有变化时返回true (我们稍后会回到这个问题)。
实现RingPainter
这就像这样实现了。
class RingPainter extends CustomPainter {
// 1. add a constructor and properties that can be set from the parent widget
RingPainter({
required this.progress,
required this.taskNotCompletedColor,
required this.taskCompletedColor,
});
// a value between 0 and 1
final double progress;
// background color to use when the task is not completed
final Color taskNotCompletedColor;
// foreground color to use when the task is completed
final Color taskCompletedColor;
@override
void paint(Canvas canvas, Size size) {
// 2. configure the paint and drawing properties
final strokeWidth = size.width / 15.0;
final center = Offset(size.width / 2, size.height / 2);
final radius = (size.width - strokeWidth) / 2;
// 3. create and configure the background paint
final backgroundPaint = Paint()
..isAntiAlias = true
..strokeWidth = strokeWidth
..color = taskNotCompletedColor
..style = PaintingStyle.stroke;
// 4. draw a circle
canvas.drawCircle(center, radius, backgroundPaint);
// 5. create and configure the foreground paint
final foregroundPaint = Paint()
..isAntiAlias = true
..strokeWidth = strokeWidth
..color = taskCompletedColor
..style = PaintingStyle.stroke;
// 6. draw an arc that starts from the top (-pi / 2)
// and sweeps and angle of (2 * pi * progress)
canvas.drawArc(
Rect.fromCircle(center: center, radius: radius),
-pi / 2,
2 * pi * progress,
false,
foregroundPaint,
);
}
// 7. only return true if the old progress value
// is different from the new one
@override
bool shouldRepaint(covariant RingPainter oldDelegate) =>
oldDelegate.progress != progress;
}
注意在重写 时使用的
covariant在覆盖shouldRepaint()方法时使用了关键字。这是为了让oldDelegate,即使它在基类中被声明为CustomPainter,也可以用类型RingPainter。
关键点
- 当任何现有的Flutter小部件不足以满足我们的目的时,我们可以实现一个
CustomPainter。 - 在扩展
CustomPainter类时,我们必须实现paint()和shouldRepaint()方法。 paint()方法给了我们一个canvas,我们可以用它来绘制形状。所有可用的绘制方法都需要一个Paint对象,我们可以用它来定制形状的外观。- 如果我们想绘制相对于父部件大小的形状,我们可以创建取决于
size参数的变量。 - 我们可以通过传递一些值作为参数来使我们的绘图器变得可定制,就像我们在创建自定义widget时那样。
- 我们可以以这样的方式实现
shouldRepaint()方法,即它只在有变化时返回true,这有助于提高性能。
奖励:这里有一个关于如何使用CustomPainter 的完整教程。
在下一课,我们将看到如何使用AnimationController 实现任务动画。
编码愉快!