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

2

9

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,
      ),
    );
Circumambulate answered 7/10, 2020 at 3:37 Comment(12)
when using status listener call forward() inside Future.delayed() method callbackImprobable
@Improbable you mean this right? Future.delayed(Duration(microseconds: 5000),(){ _controller.forward(); }); But it's didn't workCircumambulate
well microseconds is really really short period, also you would need from: 0.0Improbable
But 5000 milliseconds = 5 seconds neh!Circumambulate
but you used microseconds: 5000Improbable
oh goh.. i miss that. Okay all that happens now is to wait 5 seconds for the animation to start and repeat without delay.Circumambulate
even this not help - Future.delayed(Duration(milliseconds: 5000),(){ _controller.repeat(); });Circumambulate
so read my todays's first comment againImprobable
Once it is called _controller.repeat () the delay does not add up between the animationCircumambulate
this is because you call repeat - instead you should "repeat" your controller by yourself (with calling forward inside Future.delayed)Improbable
Thank you very muchhhhhhhhhhhh. I got your pointCircumambulate
It works as I really expected. Thank you very much @Improbable for your valuable time. Could you please add your comment as an answer? Then I can set it as the correct answer.Circumambulate
C
13

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);
     }
   });
  }
}
Circumambulate answered 7/10, 2020 at 7:1 Comment(2)
Don't forget to use if(mounted). Because you have a chance that forwards the disposed of animation controller.Scalawag
Man it should be if(mounted).Eolande
S
1

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.

Siphon answered 10/1 at 12:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.