如何在抖动中为路径设置动画?

Sou*_*mes 5 flutter

我想要实现路径动画效果,如此处所示:

此动画(由于gif太大,我无法包含它)

我只想在地图动画上实现路径,我知道我需要使用堆积的地图,放置地图,然后使用Painter绘制这样的路径,但是如何对其进行动画处理?

and*_*ras 17

我知道这个问题有一个公认的答案,但我想展示这个问题的替代解决方案。

首先,从单个点创建自定义路径对于以下情况不是最佳选择:

  • 计算每个段的长度并非易事
  • 以小增量均匀地为步骤设置动画很困难且需要大量资源
  • 不适用于二次/贝塞尔曲线段

就像在旧的 Android 中有这种路径跟踪方法一样PathMetricsFlutter 中也存在非常相似的方法

基于这个问题的公认答案,这里有一种更通用的方法来为任何路径设置动画。


所以给定一个路径和一个动画百分比,我们需要从开始到那个百分比提取一条路径:

Path createAnimatedPath(
  Path originalPath,
  double animationPercent,
) {
  // ComputeMetrics can only be iterated once!
  final totalLength = originalPath
      .computeMetrics()
      .fold(0.0, (double prev, PathMetric metric) => prev + metric.length);

  final currentLength = totalLength * animationPercent;

  return extractPathUntilLength(originalPath, currentLength);
}
Run Code Online (Sandbox Code Playgroud)

所以现在我只需要提取一条路径直到给定长度(而不是百分比)。我们需要组合所有现有路径直到一定距离。然后将最后一个路径段的某些部分添加到此现有路径中。

这样做非常简单。

Path extractPathUntilLength(
  Path originalPath,
  double length,
) {
  var currentLength = 0.0;

  final path = new Path();

  var metricsIterator = originalPath.computeMetrics().iterator;

  while (metricsIterator.moveNext()) {
    var metric = metricsIterator.current;

    var nextLength = currentLength + metric.length;

    final isLastSegment = nextLength > length;
    if (isLastSegment) {
      final remainingLength = length - currentLength;
      final pathSegment = metric.extractPath(0.0, remainingLength);

      path.addPath(pathSegment, Offset.zero);
      break;
    } else {
      // There might be a more efficient way of extracting an entire path
      final pathSegment = metric.extractPath(0.0, metric.length);
      path.addPath(pathSegment, Offset.zero);
    }

    currentLength = nextLength;
  }

  return path;
}
Run Code Online (Sandbox Code Playgroud)

整个示例所需的其余代码:

void main() => runApp(
  new MaterialApp(
    home: new AnimatedPathDemo(),
  ),
);

class AnimatedPathPainter extends CustomPainter {
  final Animation<double> _animation;

  AnimatedPathPainter(this._animation) : super(repaint: _animation);

  Path _createAnyPath(Size size) {
    return Path()
      ..moveTo(size.height / 4, size.height / 4)
      ..lineTo(size.height, size.width / 2)
      ..lineTo(size.height / 2, size.width)
      ..quadraticBezierTo(size.height / 2, 100, size.width, size.height);
  }

  @override
  void paint(Canvas canvas, Size size) {
    final animationPercent = this._animation.value;

    print("Painting + ${animationPercent} - ${size}");

    final path = createAnimatedPath(_createAnyPath(size), animationPercent);

    final Paint paint = Paint();
    paint.color = Colors.amberAccent;
    paint.style = PaintingStyle.stroke;
    paint.strokeWidth = 10.0;

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => true;
}

class AnimatedPathDemo extends StatefulWidget {
  @override
  _AnimatedPathDemoState createState() => _AnimatedPathDemoState();
}

class _AnimatedPathDemoState extends State<AnimatedPathDemo>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;

  void _startAnimation() {
    _controller.stop();
    _controller.reset();
    _controller.repeat(
      period: Duration(seconds: 5),
    );
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(title: const Text('Animated Paint')),
      body: SizedBox(
        height: 300,
        width: 300,
        child: new CustomPaint(
          painter: new AnimatedPathPainter(_controller),
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _startAnimation,
        child: new Icon(Icons.play_arrow),
      ),
    );
  }

  @override
  void initState() {
    super.initState();
    _controller = new AnimationController(
      vsync: this,
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

Run Code Online (Sandbox Code Playgroud)

  • 有史以来最被低估的评论 (3认同)

bio*_*arl 11

我为此创建了一个库:drawing_animation

您只需要向小部件提供 Path 对象:

导致此图像输出:imgur

import 'package:drawing_animation/drawing_animation.dart';
//...
List<Paths> dottedPathArray = ...;
bool run = true;
//...

AnimatedDrawing.paths(
    this.dottedPathArray,
    run: this.run,
    animationOrder: PathOrders.original,
    duration: new Duration(seconds: 2),
    lineAnimation: LineAnimation.oneByOne,
    animationCurve: Curves.linear,
    onFinish: () => setState(() {
      this.run = false;
    }),
)),


Run Code Online (Sandbox Code Playgroud)


Ric*_*eap 8

你实际上并不需要一个堆栈;您可以在地图图像上使用foregroundPainter。动画CustomPainter传递AnimationController到其构造函数和super构造函数。在paint使用value动画来决定绘制多少路径。例如,如果value是 0.25,则只绘制路径的前 25%。

class AnimatedPainter extends CustomPainter {
  final Animation<double> _animation;

  AnimatedPainter(this._animation) : super(repaint: _animation);

  @override
  void paint(Canvas canvas, Size size) {
    // _animation.value has a value between 0.0 and 1.0
    // use this to draw the first X% of the path
  }

  @override
  bool shouldRepaint(AnimatedPainter oldDelegate) {
    return true;
  }
}

class PainterDemo extends StatefulWidget {
  @override
  PainterDemoState createState() => new PainterDemoState();
}

class PainterDemoState extends State<PainterDemo>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = new AnimationController(
      vsync: this,
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _startAnimation() {
    _controller.stop();
    _controller.reset();
    _controller.repeat(
      period: Duration(seconds: 5),
    );
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(title: const Text('Animated Paint')),
      body: new CustomPaint(
        foregroundPainter: new AnimatedPainter(_controller),
        child: new SizedBox(
          // doesn't have to be a SizedBox - could be the Map image
          width: 200.0,
          height: 200.0,
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _startAnimation,
        child: new Icon(Icons.play_arrow),
      ),
    );
  }
}

void main() {
  runApp(
    new MaterialApp(
      home: new PainterDemo(),
    ),
  );
}
Run Code Online (Sandbox Code Playgroud)

大概你会有一个定义路径的坐标列表。假设一些点列表,您将使用以下内容绘制完整路径:

if (points.isEmpty) return;
Path path = Path();
Offset origin = points[0];
path.moveTo(origin.dx, origin.dy);
for (Offset o in points) {
  path.lineTo(o.dx, o.dy);
}
canvas.drawPath(
  path,
  Paint()
    ..color = Colors.orange
    ..style = PaintingStyle.stroke
    ..strokeWidth = 4.0,
);
Run Code Online (Sandbox Code Playgroud)

value小于 1.0 时,您需要设计一种方法来绘制小于 100% 的路径。例如,当值为 0.25 时,您可能只将第一季度的点添加到路径中。如果您的路径由相对较少的点组成,则如果您计算路径的总长度并仅绘制路径的第一段,加起来等于总长度的四分之一,则您可能会获得最流畅的动画。