vlambda博客
学习文章列表

flutter路径的用法(下)

本节目标:

[1]. 了解路径的 [封闭] [重置] [偏移] 操作。
[2]. 了解路径的 [矩形边距] 和 [检测点是否在路径中]。
[3]. 了解路径的 [路径变换] 和 [路径联合]。
[4]. 了解路径测量的用法和作用。

一、路径操作

路径的操作是路径使用的重要一环,很多路径的特效和复杂路径的拼合都会使用它们。


1.close、reset、shift

path#close :用于将路径尾点和起点,进行路径封闭
path#reset :用于将路径进行重置,清除路径内容
path#shift :指定点Offset将路径进行平移,且返回一条新的路径

flutter路径的用法(下)
image-20201031212102816
---->[p06_path/12_close_reset_shift/paper.dart]----
Path path = Path();
Paint paint = Paint()
  ..color = Colors.purpleAccent
  ..strokeWidth = 2
  ..style = PaintingStyle.stroke;
path
  ..lineTo(100100)
  ..relativeLineTo(0-50)
  ..close();
canvas.drawPath(path, paint);
canvas.drawPath(path.shift(Offset(1000)), paint);
canvas.drawPath(path.shift(Offset(2000)), paint);

2. containsgetBounds

Paint#contains可以判断点Offset在不在路径之内(如下图紫色区域),
这是个非常好用的方法,可以根据这个方法做一些触点判断简单的碰撞检测
Paint#getBounds可以获取当前路径所在的矩形区域,(如下橙色区域)

flutter路径的用法(下)
image-20201031214041569
---->[p06_path/13_getBounds_contains/paper.dart]----
Path path = Path();
Paint paint = Paint()
  ..color = Colors.purple
  ..style = PaintingStyle.fill;
  
path
  ..relativeMoveTo(00)
  ..relativeLineTo(-30120)
  ..relativeLineTo(30-30)
  ..relativeLineTo(3030)
  ..close();
canvas.drawPath(path, paint);

print(path.contains(Offset(2020)));
print(path.contains(Offset(020)));
Rect bounds = path.getBounds();
canvas.drawRect(
    bounds,
    Paint()
      ..color = Colors.orange
      ..style = PaintingStyle.stroke
      ..strokeWidth = 1);

3.Path#transform: 路径变换

对于对称性图案,当已经有一部分单体路径,可以根据一个4*4的矩阵对路径进行变换。
可以使用Matrix4对象进行辅助生成矩阵。能很方便进行旋转、平移、缩放、斜切等变换效果。

flutter路径的用法(下)
image-20201031214342969
---->[p06_path/14_getBounds_contains/paper.dart]----
Path path = Path();
Paint paint = Paint()
  ..color = Colors.purple
  ..style = PaintingStyle.fill;
path
  ..relativeMoveTo(00)
  ..relativeLineTo(-30120)
  ..relativeLineTo(30-30)
  ..relativeLineTo( 30,30)
  ..close();
for(int i = 0; i < 8; i++){
  canvas.drawPath(path.transform(Matrix4.rotationZ(i*pi/4).storage), paint);
}

4. combine: 路径联合

Patn#combine用于结合两个路径,并生成新路径,可用于生成复杂的路径。
一共有如下五种联合方式,效果如下图:

flutter路径的用法(下)
image-20201031215722410
---->[p06_path/15_combine/paper.dart]----
Path path = Path();
Paint paint = Paint();
paint
  ..color = Colors.purple
  ..style = PaintingStyle.fill;
path
  ..relativeMoveTo(00)
  ..relativeLineTo(-30120)
  ..relativeLineTo(30-30)
  ..relativeLineTo( 30,30)
  ..close();

var pathOval =Path()..addOval(Rect.fromCenter(center: Offset(00),width: 60,height: 60));
canvas.drawPath(Path.combine(PathOperation.difference, path, pathOval), paint);

canvas.translate(1200);
canvas.drawPath(Path.combine(PathOperation.intersect, path, pathOval), paint);

canvas.translate(1200);
canvas.drawPath(Path.combine(PathOperation.union, path, pathOval), paint);

canvas.translate(-120*3.00);
canvas.drawPath(Path.combine(PathOperation.reverseDifference, path, pathOval), paint);

canvas.translate(-1200);
canvas.drawPath(Path.combine(PathOperation.xor, path, pathOval), paint);

二、路径测量的使用

computeMetrics 是路径中一个非常实用的操作,可以根据这个方法获取很多有价值的信息,比如路径上某点在路径的位置角度,路径长度等。通过这些和动画结合,可以做出环路经运动路径绘制动画等效果。


1.认识Path#computeMetrics

通过path.computeMetrics(),可以获取一个可迭代PathMetrics类对象 它迭代出的是PathMetric对象,也就是每个路径的测量信息。也就是说通过path.computeMetrics()你获得是一组路径的测量信息。注意:如下path.addOval之后,PathMetrics类对象中元素变为两个。

---->[p06_path/16_computeMetrics/paper.dart]----
Path path = Path();
path
  ..relativeMoveTo(00)
  ..relativeLineTo(-30120)
  ..relativeLineTo(30-30)
  ..relativeLineTo(3030)
  ..close();
path.addOval(Rect.fromCenter(center: Offset.zero,width: 50,height: 50));

通过PathMetrics对象可以获得路径长度 length路径索引 contourIndexisClosed路径是否闭合isClosed

PathMetrics pms = path.computeMetrics();
Tangent t;
pms.forEach((pm) {
  print("---length:-${pm.length}----contourIndex:-${pm.contourIndex}----contourIndex:-${pm.isClosed}----");
});

---->[打印日志]----
---length:-332.2391357421875----contourIndex:-0----contourIndex:-true---------
---length:-156.0674285888672----contourIndex:-1----contourIndex:-true---------

2 .路径测量获取路径某位置信息

比如我想要在路径一半的地方绘制一个小球,如果通过自己计算的话,非常困难。
幸运的是通过路径测量,实现起来就非常方便。甚至还能得到改点的角度速度信息。
下面通过pm.length * 0.5表示在路径长度50%的点的信息。

image-20201031222900097
 Paint paint = Paint()
   ..color = Colors.purple
   ..strokeWidth = 1
   ..style = PaintingStyle.stroke;
 Path path = Path();
 path
   ..relativeMoveTo(00)
   ..relativeLineTo(-30120)
   ..relativeLineTo(30-30)
   ..relativeLineTo(3030)
   ..close();

 path.addOval(Rect.fromCenter(center: Offset.zero, width: 50, height: 50));
 PathMetrics pms = path.computeMetrics();
 pms.forEach((pm) {
   Tangent tangent = pm.getTangentForOffset(pm.length * 0.5);
   print(
       "---position:-${tangent.position}----angle:-${tangent.angle}----vector:-${tangent.vector}----");
   canvas.drawCircle(
       tangent.position, 5, Paint()..color = Colors.deepOrange);
 });
 canvas.drawPath(path, paint);

---->[打印日志]----
---position:-Offset(0.090.0)----angle:-0.7853981633974483----vector:-Offset(0.7-0.7)----
---position:-Offset(-25.00.0)----angle:-1.5707963267948966----vector:-Offset(0.0-1.0)----

3. 路径测量和动画结合

虽然动画在后面章节才讲述,这样可以先看一下。路径测量和动画搭档起来,才能更看出它的价值,下面将实现小球沿路径动画,使用动画控制器让数字在 3 秒内从 0 运动到 1,达到动画效果。

随路径变换
---->[p06_path/17_computeMetrics_anim/paper.dart]----
class Paper extends StatefulWidget {
  @override
  _PaperState createState() => _PaperState();
}

class _PaperState extends State<Paperwith SingleTickerProviderStateMixin {
  AnimationController _ctrl;

  @override
  void initState() {
    super.initState();
    _ctrl = AnimationController(duration: Duration(seconds: 3), vsync: this)
      ..forward();
  }
  
  @override
  void dispose() {
    _ctrl.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      child: CustomPaint(
        painter: PaperPainter(progress: _ctrl),
      ),
    );
  }
}

class PaperPainter extends CustomPainter {

  final Animation<double> progress;

  PaperPainter({this.progress}) : super(repaint: progress);

  @override
  void paint(Canvas canvas, Size size) {
    canvas.translate(size.width / 2, size.height / 2);

    Paint paint = Paint()
      ..color = Colors.purple
      ..strokeWidth = 1
      ..style = PaintingStyle.stroke;

    Path path = Path();
    path
      ..relativeMoveTo(00)
      ..relativeLineTo(-30120)
      ..relativeLineTo(30-30)
      ..relativeLineTo(3030)
      ..close();

    path.addOval(Rect.fromCenter(center: Offset.zero, width: 50, height: 50));

    PathMetrics pms = path.computeMetrics();
    pms.forEach((pm) {
      Tangent tangent = pm.getTangentForOffset(pm.length * progress.value);

      canvas.drawCircle(
          tangent.position, 5, Paint()..color = Colors.deepOrange);
    });

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(PaperPainter oldDelegate) => oldDelegate.progress!=progress;
}

到此为止讲述完了Path相关的所有Api, 不过知道怎么用是一切的开始,绘制的能力和灵感并非那么轻易可得,需要你不断的使用和练习。在后面的章节中将向你展示光怪陆离的绘制世界,为什么世界如此多彩,那当然是因为它有颜色啦 ~,下一章将带你重新认识,这不起眼却美化万物的颜色