Home > Software design >  How to animate a border using custom paint/ canvas in flutter
How to animate a border using custom paint/ canvas in flutter

Time:01-02

I want to draw animated border around the square container with infinity loop (never stop) like this photo , I'm trying animated container but not help me

so can anyone tell me how to implement line animation

I'm using this code to draw the square but I can't make it build with animation

class RadialPainter extends CustomPainter {
  final double progressRemoval;
  final Color color;
  final StrokeCap strokeCap;
  final PaintingStyle paintingStyle;
  final double strokeWidth;
  final double progress;
  RadialPainter(
      {this.progressRemoval,
      this.color,
      this.strokeWidth,
      this.strokeCap,
      this.paintingStyle,
      this.progress});

  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint()
      ..strokeWidth = strokeWidth
      ..color = color
      ..style = paintingStyle
      ..strokeCap = strokeCap;

    var progressRemoval = 0.50;

    var path = Path();

    //LINEA SUPERIOR DEL CUADRADO
    path.moveTo((size.width * 0.30), 0);
    path.quadraticBezierTo((size.width * 0.30), 0, size.width, 0);

    //LATERAL DERECHO
    path.moveTo(size.width, 0);
    path.quadraticBezierTo(size.width, 0, size.width, size.height);

    //LINEA INFERIOR DEL CUADRADO
    path.moveTo(size.width, size.height);
    path.quadraticBezierTo(size.width, size.height, 0, size.height);

    //LINEA IZQUIERDA
    path.moveTo(0, size.height);
    path.quadraticBezierTo(0, (size.height * 0.75), 0, ((size.height * 0.75)));

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(RadialPainter oldDelegate) {
    return oldDelegate.progress != progress;
  }
}

https://stackoverflow.com/questions/66870939/flutter-animate-border-color-of-a-container?rq=1

CodePudding user response:

Here's a full example for your use case with a Container and a repeating animation that you can run in DartPad.

For a detailed explanation see this answer.

import 'dart:math' as math;
import 'dart:ui';

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Border Animation',
        home: Scaffold(body: ExampleOutlinePathWrapper()));
  }
}

// Example code including two animations
class ExampleOutlinePathWrapper extends StatefulWidget {
  @override
  State<ExampleOutlinePathWrapper> createState() =>
      _ExampleOutlinePathWrapperState();
}

class _ExampleOutlinePathWrapperState extends State<ExampleOutlinePathWrapper>
    with TickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    _controller = AnimationController(
      vsync: this,
      duration: Duration(
        milliseconds: 2000,
      ),
    );
    _animation = _controller
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          _controller.reset();
        } else if (status == AnimationStatus.dismissed) {
          _controller.forward();
        }
      });
    _controller.forward();
    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    return Center(
      child: CustomPaint(
        foregroundPainter: AnimatedBorderPainter(
          animation: _animation,
          strokeColor: Colors.deepOrange,
          pathType: PathType.rect,
          animationDirection: AnimationDirection.clockwise,
          startingIndex: 1,
          strokeWidth: 4.0,
        ),
        child: Container(height: 100, width: 100, color: Colors.grey),
      ),
    );
  }
}

class AnimatedBorderPainter extends CustomPainter {
  final Animation<double> _animation;
  final PathType _pathType;
  final double _strokeWidth;
  final Color _strokeColor;
  final double _radius;
  final int _startingIndex;
  final AnimationDirection _animationDirection;

  AnimatedBorderPainter({
    required animation,
    pathType = PathType.rect,
    strokeWidth = 2.0,
    strokeColor = Colors.blueGrey,
    radius = 4.0,
    startingIndex = 0,
    animationDirection = AnimationDirection.clockwise,
  })  : assert(strokeWidth > 0, 'strokeWidth must be greater than 0.'),
        assert(
            pathType == PathType.rect ? startingIndex <= 3 : startingIndex <= 7,
            'startingIndex may not exceed the number of points the path is constructed from'),
        _animation = animation,
        _pathType = pathType,
        _strokeWidth = strokeWidth,
        _strokeColor = strokeColor,
        _radius = radius,
        _startingIndex = startingIndex,
        _animationDirection = animationDirection,
        super(repaint: animation);

  late Path _originalPath;
  late Paint _paint;

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

    // Construct original path once when animation starts
    if (animationPercent == 0.0) {
      _originalPath = _createOriginalPath(size);
      _paint = Paint()
        ..strokeWidth = _strokeWidth
        ..style = PaintingStyle.stroke
        ..color = _strokeColor;
    }

    final currentPath = _createAnimatedPath(
      _originalPath,
      animationPercent,
    );

    canvas.drawPath(currentPath, _paint);
  }

  @override
  bool shouldRepaint(AnimatedBorderPainter oldDelegate) => true;

  Path _createOriginalPath(Size size) {
    switch (_pathType) {
      case PathType.rect:
        return _createOriginalPathRect(size);
      case PathType.rRect:
        final maxAllowedRadius =
            [size.height / 2, size.width / 2, _radius].reduce(math.min);
        if (_animationDirection == AnimationDirection.clockwise) {
          return _createOriginalPathRRectClockwise(size, maxAllowedRadius);
        } else {
          return _createOriginalPathRRectCounterclockwise(
              size, maxAllowedRadius);
        }
    }
  }

  Path _createOriginalPathRect(Size size) {
    final tlPoint = Point(0, 0);
    final trPoint = Point(size.width, 0);
    final brPoint = Point(size.width, size.height);
    final blPoint = Point(0, size.height);

    final points = _animationDirection == AnimationDirection.clockwise
        ? [tlPoint, trPoint, brPoint, blPoint]
        : [tlPoint, blPoint, brPoint, trPoint];

    int startingIndex = _startingIndex;
    final path = Path();

    // Move to starting point
    final startingPoint = points[startingIndex];
    path.moveTo(startingPoint.x, startingPoint.y);

    // Contruct the complete path by moving from point to point
    for (var i = 0; i < points.length; i  ) {
      final index = startingIndex == points.length - 1 ? 0 : startingIndex   1;
      final pointLineTo = points[index];
      path.lineTo(pointLineTo.x, pointLineTo.y);
      if (startingIndex == points.length - 1) {
        startingIndex = 0;
        continue;
      }
      startingIndex  ;
    }
    return path;
  }

  Path _createOriginalPathRRectClockwise(Size size, double radius) {
    final tlPoint = Point(0, 0   radius);
    final tlBezierPoint = BezierPoint(0, 0, 0   radius, 0);
    final trPoint = Point(size.width - radius, 0);
    final trBezierPoint = BezierPoint(size.width, 0, size.width, 0   radius);
    final brPoint = Point(size.width, size.height - radius);
    final brBezierPoint =
        BezierPoint(size.width, size.height, size.width - radius, size.height);
    final blPoint = Point(0   radius, size.height);
    final blBezierPoint = BezierPoint(0, size.height, 0, size.height - radius);

    final points = [
      tlPoint,
      tlBezierPoint,
      trPoint,
      trBezierPoint,
      brPoint,
      brBezierPoint,
      blPoint,
      blBezierPoint
    ];

    return _createOriginalPathRRect(points);
  }

  Path _createOriginalPathRRectCounterclockwise(Size size, double radius) {
    final tlPoint = Point(0   radius, 0);
    final tlBezierPoint = BezierPoint(0, 0, 0, 0   radius);
    final trPoint = Point(size.width, 0   radius);
    final trBezierPoint = BezierPoint(size.width, 0, size.width - radius, 0);
    final brPoint = Point(size.width - radius, size.height);
    final brBezierPoint =
        BezierPoint(size.width, size.height, size.width, size.height - radius);
    final blPoint = Point(0, size.height - radius);
    final blBezierPoint = BezierPoint(0, size.height, 0   radius, size.height);

    final points = [
      tlPoint,
      tlBezierPoint,
      blPoint,
      blBezierPoint,
      brPoint,
      brBezierPoint,
      trPoint,
      trBezierPoint,
    ];

    return _createOriginalPathRRect(points);
  }

  Path _createOriginalPathRRect(List<Object> points) {
    int startingIndex = _startingIndex;
    final path = Path();

    // Move to starting point
    final startingPoint = points[startingIndex];
    if (startingPoint is Point) {
      path.moveTo(startingPoint.x, startingPoint.y);
    } else {
      path.moveTo((startingPoint as BezierPoint).x2, startingPoint.y2);
    }

    // Contruct the complete path by moving from point to point
    for (var i = 0; i < points.length; i  ) {
      final index = startingIndex == points.length - 1 ? 0 : startingIndex   1;
      final nextPoint = points[index];
      if (nextPoint is Point) {
        path.lineTo(nextPoint.x, nextPoint.y);
      } else {
        path.quadraticBezierTo((nextPoint as BezierPoint).x1, nextPoint.y1,
            nextPoint.x2, nextPoint.y2);
      }
      if (startingIndex == points.length - 1) {
        startingIndex = 0;
        continue;
      }
      startingIndex  ;
    }
    return path;
  }

  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 = 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 Point {
  final double x;
  final double y;

  Point(this.x, this.y);

  @override
  String toString() {
    return 'Point($x, $y)';
  }
}

class BezierPoint {
  final double x1;
  final double y1;
  final double x2;
  final double y2;

  BezierPoint(this.x1, this.y1, this.x2, this.y2);

  @override
  String toString() {
    return 'BezierPoint($x1, $y1, $x2, $y2)';
  }
}

enum PathType {
  rect,
  rRect,
}

enum AnimationDirection {
  clockwise,
  counterclockwise,
}

  • Related