How to add some delay between AnimationController.repeat() in Flutter

11.2k Views Asked by At

I need to add some delay between each iteration of animation gets call to repeat. Something like the following image. enter image description here

I tried to do it by passing value to the period parameter of the repeat method but it was not what I expected.

_controller = AnimationController(vsync: this, duration: widget.period)
      ..addStatusListener((AnimationStatus status) {
        if (status != AnimationStatus.completed) {
          return;
        }
        _count++;
        if (widget.loop <= 0) {
          //_controller.repeat(period: Duration(microseconds: 5000));
          _controller.repeat();
        } else if (_count < widget.loop) {
          _controller.forward(from: 0.0);
        }
      });

I've also tried to add Tween with the animation. That didn't help either. Can you help me clarify where I went wrong?

AnimatedBuilder(
      animation: Tween<double>(begin: 0.0, end: 1.0).animate(
        CurvedAnimation(
          parent: _controller,
          curve: Interval(0.5, 1.0)
        ),
      ),
      child: widget.child,
      builder: (BuildContext context, Widget child) => _Shiner(
        child: child,
        direction: widget.direction,
        gradient: widget.gradient,
        percent: _controller.value,
        enabled: widget.enabled,
      ),
    );
2

There are 2 best solutions below

2
SUDESH KUMARA On BEST ANSWER

Thanks to @pskink Now it's working as I expected. All you have to do is repeat the controller yourself instead of trying to add delay to controller.repeat()

_controller.addStatusListener((status) { 
  if(status == AnimationStatus.completed){
   Future.delayed(Duration(milliseconds: 5000),(){
     if(mounted){
       _controller.forward(from: 0.0);
     }
   });
  }
}
0
Robbendebiene On

You can do this using a CurvedAnimation and supplying an appropriate Interval.

  1. add the desired delay to the AnimationController duration
  2. calculate an Interval based on the ratio of the delay and total animation duration.
  3. create a CurvedAnimation and supply the created Interval

Below you can find the calculations wrapped into a class for ease of use.

const delay = Duration(seconds: 5);
_controller = AnimationController(
  duration: const Duration(seconds: 1) + delay,
  vsync: this,
)..repeat();
_animation = DelayedCurvedAnimation(
  controller: _controller,
  delayEnd: delay,
);

// Interval calculation and Animation creation

class DelayedCurvedAnimation extends CurvedAnimation {
  DelayedCurvedAnimation({
    required AnimationController controller,
    Duration delayStart = Duration.zero,
    Duration delayEnd = Duration.zero,
    Curve curve = Curves.linear,
  }) : super(
    parent: controller,
    curve: _calcInterval(controller, delayStart, delayEnd, curve),
  );

  static Interval _calcInterval(AnimationController controller, Duration delayStart, Duration delayEnd, Curve curve) {
    final animationStartDelayRatio = delayStart.inMicroseconds / controller.duration!.inMicroseconds;
    final animationEndDelayRatio = delayEnd.inMicroseconds / controller.duration!.inMicroseconds;
    return Interval(animationStartDelayRatio, 1 - animationEndDelayRatio, curve: curve);
  }
}

Example (try it on https://dartpad.dev):

import 'package:flutter/material.dart';

void main() => runApp(const FadeTransitionExampleApp());

class FadeTransitionExampleApp extends StatelessWidget {
  const FadeTransitionExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: FadeTransitionExample(),
    );
  }
}

class FadeTransitionExample extends StatefulWidget {
  const FadeTransitionExample({super.key});

  @override
  State<FadeTransitionExample> createState() => _FadeTransitionExampleState();
}

class _FadeTransitionExampleState extends State<FadeTransitionExample> with TickerProviderStateMixin {
  late final AnimationController _controller;
  late final Animation<double> _animation;
  
  @override
  initState() {
    super.initState();
    const delay = Duration(seconds: 5);
    _controller = AnimationController(
      duration: const Duration(seconds: 1) + delay,
      vsync: this,
    )..repeat();
    _animation = DelayedCurvedAnimation(
      controller: _controller,
      delayEnd: delay,
    );
  }

  @override
  Widget build(BuildContext context) {
    return FadeTransition(
      opacity: _animation,
      child: const Padding(
        padding: EdgeInsets.all(8),
        child: FlutterLogo(),
      ),
    );
  }
  
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}


class DelayedCurvedAnimation extends CurvedAnimation {
  DelayedCurvedAnimation({
    required AnimationController controller,
    Duration delayStart = Duration.zero,
    Duration delayEnd = Duration.zero,
    Curve curve = Curves.linear,
  }) : super(
    parent: controller,
    curve: _calcInterval(controller, delayStart, delayEnd, curve),
  );

  static Interval _calcInterval(AnimationController controller, Duration delayStart, Duration delayEnd, Curve curve) {
    final animationStartDelayRatio = delayStart.inMicroseconds / controller.duration!.inMicroseconds;
    final animationEndDelayRatio = delayEnd.inMicroseconds / controller.duration!.inMicroseconds;
    return Interval(animationStartDelayRatio, 1 - animationEndDelayRatio, curve: curve);
  }
}

Note: For animations running in reverse you may have to do a little bit of extra work.