原文地址 : Playing with Paths in Flutterhtml
在 Flutter 中,万物皆是 Widget ,同时 Flutter 中也提供了许多了不得的 Widget 供咱们使用,可是这里面最能使人喜欢的仍是 CustomPaint。git
CustomPaint 这个组件为咱们提供了一个画布,在 Flutter 的绘图(paint)阶段,咱们能够把咱们想要绘画内容绘制上去。github
想要在 canvas 上绘图,有多种不一样的方式,其中最高效和经常使用的就是使用 Path,在本篇文章中,将会展现 Path 的绘制以及在 Path 上应用动画。若是你对 Path 不熟悉的话,能够参考一下这篇文章。编程
在 Flutter 中,经过 Path 画线是很是容易的一件事。 首先,将绘制的启动经过 moveTo 方法移动到指定位置,而后经过 lineTo 方法进行绘制。canvas
class LinePainter extends CustomPainter {
final double progress;
LinePainter({this.progress});
Paint _paint = Paint()
..color = Colors.black
..strokeWidth = 4.0
..style = PaintingStyle.stroke
..strokeJoin = StrokeJoin.round;
@override
void paint(Canvas canvas, Size size) {
var path = Path();
path.moveTo(0, size.height / 2);
path.lineTo(size.width * progress, size.height / 2);
canvas.drawPath(path, _paint);
}
@override
bool shouldRepaint(LinePainter oldDelegate) {
return oldDelegate.progress != progress;
}
}
复制代码
效果:api
画虚线相对画直线来讲就复杂一点了,Flutter 中没有直接提供画虚线的方法,可是咱们能够借助 PathMetric 来实现。bash
pathMetric 是一个对 Path 进行测量而且可以提取子 Path 的工具。微信
首先,咱们要画一条直线,和上面画直线同样,而后咱们经过 path.computeMetrics() 获取到 PathMetrics 对象。经过对 PathMetric 遍历,咱们能够提取到子 Path,这个子 Path 的起点有当前 distance 指定,而长度是咱们本身定义的 dashWidth 。ide
dashPath.addPath(
pathMetric.extractPath(distance, distance + dashWidth),
Offset.zero,
);
复制代码
完整代码:函数
class DashLinePainter extends CustomPainter {
final double progress;
DashLinePainter({this.progress});
Paint _paint = Paint()
..color = Colors.black
..strokeWidth = 4.0
..style = PaintingStyle.stroke
..strokeJoin = StrokeJoin.round;
@override
void paint(Canvas canvas, Size size) {
var path = Path()
..moveTo(0, size.height / 2)
..lineTo(size.width * progress, size.height / 2);
Path dashPath = Path();
double dashWidth = 10.0;
double dashSpace = 5.0;
double distance = 0.0;
for (PathMetric pathMetric in path.computeMetrics()) {
while (distance < pathMetric.length) {
dashPath.addPath(
pathMetric.extractPath(distance, distance + dashWidth),
Offset.zero,
);
distance += dashWidth;
distance += dashSpace;
}
}
canvas.drawPath(dashPath, _paint);
}
@override
bool shouldRepaint(DashLinePainter oldDelegate) {
return oldDelegate.progress != progress;
}
}
复制代码
效果:
圆形其实本质是一个特殊的椭圆,咱们能够经过 addOval 方法绘制一个椭圆,这个方法须要一个 Rect 类型的参数,若是咱们想绘制圆形,能够经过 Rect.fromCircle 来实现。
@override
void paint(Canvas canvas, Size size) {
var path = Path();
path.addOval(Rect.fromCircle(
center: Offset(0, 0),
radius: 80.0,
));
canvas.drawPath(path, myPaint);
}
复制代码
上面的代码的效果以下:
画一个圆形仍是很容易的,接下来尝试画一个复杂的,以下:
对上面的绘制图形简单分析一下,因此的圆形大小相同,相切于同一个点 (0,0),而后全部圆形的交点能够组成一个圆形,而且相邻的两个点之间的弧度相同。
咱们先尝试分析一下这些交点的关系。
首先假设一共有 n 个圆形,那么将有 n 给交点,而后假设其中的一个点(也是一个圆的圆心)的坐标是 (x,y) 。
因为一个圆的弧度是 2π,那么圆的弧度和个数置级的关系以下:
接下来就是咱们高中学到的三角函数了。经过上面的分析,咱们能够获得以下的值:
进而计算得出 x 和 y 的值 :
所以到这里,咱们有了每一个圆形圆心的 x 与 y 的计算方法,而后圆形的半径 r 是咱们本身指定的,这样咱们就知道了绘制圆所须要的所有信息,用代码表示以下:
@override
void paint(Canvas canvas, Size size) {
var path = createPath();
canvas.drawPath(path, myPaint);
}
Path createPath() {
var path = Path();
int n = circles.toInt();
var range = List<int>.generate(n, (i) => i + 1);
double angle = 2 * math.pi / n;
for (int i in range) {
double x = radius * math.cos(i * angle);
double y = radius * math.sin(i * angle);
path.addOval(Rect.fromCircle(center: Offset(x, y), radius: radius));
}
return path;
}
复制代码
因为圆形的个数、半径、圆心所在位置咱们都是知道的,那么进一步咱们还能够进行动态的圆形绘制。
动画须要使用 AnimationController
class _CirclesState extends State<Circles> with SingleTickerProviderStateMixin {
AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 3),
);
_controller.value = 1.0;
}
复制代码
而圆形的动态绘制须要用到 pathMetrics,这个类是一个辅助类能够用来测量和提取子路径的。
@override
void paint(Canvas canvas, Size size) {
var path = createPath();
PathMetrics pathMetrics = path.computeMetrics();
for (PathMetric pathMetric in pathMetrics) {
Path extractPath = pathMetric.extractPath(
0.0,
pathMetric.length * progress,
);
canvas.drawPath(extractPath, myPaint);
}
}
复制代码
详细的代码参考以下:
效果:
path 绘制里面另外一个比较重要的部分就是多边形的绘制,多边形的每一条边都是一条直线。
多边形里面每一个顶点的坐标的计算方式相似与上面说到的圆形圆心的计算。
知道了顶点的坐标,绘制每条边就很容易了。
class PolygonPainter extends CustomPainter {
PolygonPainter({
this.sides,
this.progress,
this.showPath,
this.showDots,
});
final double sides;
final double progress;
bool showDots, showPath;
final Paint _paint = Paint()
..color = Colors.purple
..strokeWidth = 4.0
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round;
@override
void paint(Canvas canvas, Size size) {
var path = createPath(sides.toInt(), 100);
PathMetric pathMetric = path.computeMetrics().first;
Path extractPath =
pathMetric.extractPath(0.0, pathMetric.length * progress);
if (showPath) {
canvas.drawPath(extractPath, _paint);
}
if (showDots) {
try {
var metric = extractPath.computeMetrics().first;
final offset = metric.getTangentForOffset(metric.length).position;
canvas.drawCircle(offset, 8.0, Paint());
} catch (e) {}
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
Path createPath(int sides, double radius) {
var path = Path();
var angle = (math.pi * 2) / sides;
path.moveTo(radius * math.cos(0.0), radius * math.sin(0.0));
for (int i = 1; i <= sides; i++) {
double x = radius * math.cos(angle * i);
double y = radius * math.sin(angle * i);
path.lineTo(x, y);
}
path.close();
return path;
}
}
复制代码
效果:
曲线能够理解为点的移动,画一个螺旋曲线其实仍是有点难度的。
为了达到曲线的效果,咱们能够先把中心的移动到 (x,y) 坐标,而后,对于下一个点,咱们让半径增长 0.75,而弧度增长 2π/50. 对于每一个新增的点,因为半径和角度增长的都很小,所以咱们在视觉上看到的就是一条曲线了,而不是直线。
Path createSpiralPath(Size size) {
double radius = 0, angle = 0;
Path path = Path();
for (int n = 0; n < 200; n++) {
radius += 0.75;
angle += (math.pi * 2) / 50;
var x = size.width / 2 + radius * math.cos(angle);
var y = size.height / 2 + radius * math.sin(angle);
path.lineTo(x, y);
}
return path;
}
复制代码
一样的动画效果须要使用 pathMetric。 完整代码能够在这里找到 :
效果:
最后展现一个行星旋转的动画效果,完整代码地址:
效果:
欢迎关注「Flutter 编程开发」微信公众号 。