Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync two animations using the same AnimationController, but delay the second one by half of the controller duration #96

Closed
WieFel opened this issue Nov 21, 2022 · 4 comments

Comments

@WieFel
Copy link

WieFel commented Nov 21, 2022

Hi,

I have the following use case:
ezgif-3-1d51ec8ea8
A line with two moving arrows. The first arrow starts at t=0.0 and animates to the end of the line (t=1.0).
The second arrow starts when the first arrow is at t=0.5 and animates also from 0.0 to 1.0 until reaching the line end.

Currently, I implemented this using 2 AnimationControllers with the following two animations:

_animation1 = MovieTween()
    .scene(
        begin: const Duration(seconds: 0),
        end: arrow1Controller.duration)
    .tween(AnimatedArrows.arrow1Progress, Tween(begin: 0.0, end: 1.0),
        curve: Curves.easeInOut)
    .parent
    .animatedBy(arrow1Controller),
_animation2 = MovieTween()
    .scene(
        begin: const Duration(seconds: 0),
        end: arrow2Controller.duration)
    .tween(AnimatedArrows.arrow2Progress, Tween(begin: 0.0, end: 1.0),
        curve: Curves.easeInOut)
    .parent
    .animatedBy(arrow2Controller)

Both animations have essentially the same structure. They only differ in the AnimationController that animates them, and in the respective animation property to be animated (AnimatedArrows.arrow1Progress vs AnimatedArrows.arrow2Progress).

The animation controller arrow1Controller is started, and when it reaches a value >= 0.5, arrow2Controller is started as well. Like this, the arrows are animating more or less correctly. But they are not 100% synchronized correctly, as arrow1Controller never hits exactly 0.5 as a value, but always values like 0.56, 0.58 or similar.

I also tried to put everything in to 1 animation, using 2 scenes and start the second scene later.

MovieTween()
  ..scene(duration: const Duration(seconds: 5))
      .tween(
        AnimatedArrows.arrow1Progress,
        Tween(begin: 0.0, end: 1.0),
        curve: Curves.easeInOut,
      )
  ..scene(
    begin: const Duration(milliseconds: 2500),
    duration: arrow1Controller.duration,
  )
      .tween(
        AnimatedArrows.arrow2Progress,
        Tween(begin: 0.0, end: 1.0),
        curve: Curves.easeInOut,
      )
      .parent)
.animatedBy(arrow1Controller)

Like this, the problem is that once arrow1 has reached the end of the line, it waits for arrow2 to reach the end of line as well, instead of starting to animate again already.
ezgif-3-2c61d4ee97

The goal is to achieve a fluid animation that constantly animates, using 1 single AnimationController, which must be actually possible, but I don't know how.
Any hints on this?

@felixblaschke
Copy link
Owner

If you want to loop this, then:

  • Animate arrow1 from 0 to halfWidth with Curve.easeIn
  • Animate arrow2 from halfWidth to FullWidth with Curve.easeOut

The idea is to specify to half animation and it will look as if an arrow takes the full width, but internally those are 2 iterations.

@WieFel
Copy link
Author

WieFel commented Nov 22, 2022

@felixblaschke yes exactly, I want to loop it.

If you want to loop this, then:

* Animate arrow1 from 0 to halfWidth with Curve.easeIn

* Animate arrow2 from halfWidth to FullWidth with Curve.easeOut

The idea is to specify to half animation and it will look as if an arrow takes the full width, but internally those are 2 iterations.

Could you give a small example using my code from above?
I tried a few things. But the general problem is that the animation doesn't look "fluid". It always has a small break where nothing animates, once the controller reaches 1.0.

@felixblaschke
Copy link
Owner

Sometimes I am impressed what MovieTween is capable of. :)
Here we go:
ezgif com-gif-maker

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

void main() {
  runApp(const MaterialApp(home: Page()));
}

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

  @override
  Widget build(BuildContext context) {
    const startColor = Colors.green;
    const endColor = Colors.blue;
    final middleColor = Color.lerp(startColor, endColor, 0.5);

    final tween = MovieTween()
      ..scene(
        begin: Duration.zero,
        end: const Duration(seconds: 2),
      )
          .tween('align_x1', Tween(begin: -1.0, end: 0.0), curve: Curves.easeIn)
          .tween('align_x2', Tween(begin: 0.0, end: 1.0), curve: Curves.easeOut)
          .tween('color_1', ColorTween(begin: startColor, end: middleColor))
          .tween('color_2', ColorTween(begin: middleColor, end: endColor))
      ..scene(
        begin: Duration.zero,
        duration: const Duration(milliseconds: 500),
      ).tween('opacity_1', Tween(begin: 0.0, end: 1.0), curve: Curves.easeIn)
      ..scene(
        begin: const Duration(milliseconds: 1500),
        duration: const Duration(milliseconds: 500),
      ).tween('opacity_2', Tween(begin: 1.0, end: 0.0), curve: Curves.easeOut);

    return Scaffold(
        body: Center(
            child: Padding(
      padding: const EdgeInsets.all(32),
      child: SizedBox(
          height: 20,
          child: LoopAnimationBuilder(
            tween: tween,
            duration: tween.duration,
            builder: (context, value, _) {
              return Stack(
                children: [
                  Positioned.fill(
                      child: Container(color: Colors.grey.shade300)),
                  Positioned.fill(
                    child: Align(
                        alignment: Alignment(value.get<double>('align_x1'), 0),
                        child: Container(
                            color: value
                                .get<Color>('color_1')
                                .withOpacity(value.get<double>('opacity_1')),
                            width: 20)),
                  ),
                  Positioned.fill(
                    child: Align(
                        alignment: Alignment(value.get<double>('align_x2'), 0),
                        child: Container(
                            color: value
                                .get<Color>('color_2')
                                .withOpacity(value.get<double>('opacity_2')),
                            width: 20)),
                  ),
                ],
              );
            },
          )),
    )));
  }
}

@WieFel
Copy link
Author

WieFel commented Nov 28, 2022

@felixblaschke thank you, this helped.
What I first struggled to understand is that arrow1 is "switched to" arrow2 at the middle of the animation...

@WieFel WieFel closed this as completed Nov 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants