How to animate line draw in custom painter in Flutter?

3.1k views Asked by At

I want to animate the line drawing in custom painter canvas. So far what I can do is create two circles at two points and then create a line between those two points. But I don't know how to animate the line as if it is going from one point to the other. I have tried something but I can't make it work. Please check the code and suggest me if you have any idea.

import 'package:flutter/material.dart';
import 'dart:math' as math;

class ProgressMonitorAnimation extends StatefulWidget {

  @override
  State<StatefulWidget> createState() => _ProgressMonitorAnimationState();
}

class _ProgressMonitorAnimationState extends State<ProgressMonitorAnimation> with TickerProviderStateMixin {

  double _progress = 0.0;
  Animation<double> animation;

  @override
  void initState() {

    var controller = AnimationController(duration: Duration(milliseconds: 3000), vsync: this);

    animation = Tween(begin: 1.0, end: 0.0).animate(controller)..addListener(() {

        setState(() {
          _progress = animation.value;
        });
      });

    controller.forward();

    super.initState();
  }

  @override
  Widget build(BuildContext context) {

    return Transform(
      alignment: Alignment.center,
      transform: Matrix4.rotationX(math.pi),
      child: CustomPaint(
        foregroundPainter: ProgressPainter(_progress),
      ),
    );
  }
}


class ProgressPainter extends CustomPainter {

  double _progress;

  ProgressPainter(this._progress);

  @override
  void paint(Canvas canvas, Size size) {

    final Paint circlePainter = Paint()..color = Colors.green;
    final Paint linePainter = Paint()..color = Colors.black..strokeWidth = 4..strokeCap = StrokeCap.round;

    canvas.drawCircle(Offset(0.0, 30.0 * 3), 10.0, circlePainter);
    canvas.drawCircle(Offset(15.0 * 2, 80.0 * 3), 10.0, circlePainter);

    canvas.drawLine(Offset(0.0 / (_progress * 10), 30.0 * 3), Offset((30.0 * 3) + (15.0)  / (_progress * 15) * 2, (80.0 * 3) / (_progress * 15)), linePainter);
  }

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

There are 1 answers

0
gsm On

You can do as follows using flutter Custom Painter.

import 'dart:async';
import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

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.black;
    paint.style = PaintingStyle.stroke;
    paint.strokeWidth = 4.0;

    canvas.drawPath(path, paint);
  }

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

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);
}

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;
}

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

class _AnimatedPathDemoState extends State<AnimatedPathDemo>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;
  Completer<GoogleMapController> _controllerMap = Completer();

  static final CameraPosition _initialPosition = CameraPosition(
    // target: LatLng(12.947437, 77.685345),
    target: LatLng(7.8731, 80.7718),
    zoom: 8,
  );

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

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(title: const Text('Animated Paint')),
      body: Stack(
        children: <Widget>[
          GoogleMap(
            // rotateGesturesEnabled: false,

            mapType: MapType.normal,
            compassEnabled: false,
            initialCameraPosition: _initialPosition,
            // polylines: _polylines,
            // markers: _markers,
            onMapCreated: (GoogleMapController controller) {
              // controller.setMapStyle(Utils.mapStyles);
              _controllerMap.complete(controller);
            },
          ),
          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();
  }
}