Flare: Looping the end of an animation

A lot of people are asking how to loop just the end X seconds of a flare animation. It's an easy problem to solve once you know where to look, but it's not totally straight forward. In this article we're going to discuss two ways of handling this situation. First, just looping the last X seconds of a single animation. Second, looping a second animation after the first animation has finished. If you are unfamiliar with Flare, it is a product created by Rive to help bring real-time animations to your apps. It's a high quality tool and it's 100% free.

Download

Visit the GitHub Repository to clone the source and assets for this application.

If you just want the flare resources, they can be found and forked in the following locations: Single Animation Flag and Double Animation Flag

This code was tested with Flutter 1.3.8, Dart 2.2.1, and Flare Plugin 1.3.4

Setup

You can jump straight to The Code if you just want to see the examples.

Step 1: Flare assets

Our application needs to be able to load the flare files as assets inside our app. See the Download section for the links to the flare resources where you can download them. In this example, we placed the files in "assets/flare" where they are easy to reference from our pubspec.yaml file.

Speaking of pubspec.yaml, let's actually add the files as assets now:

pubspec.yaml
flutter:
  # ... snip ...
  assets:
    - assets/flare/

We're just going to import the whole directory, which will make all the assets in the directory available to Flutter, instead of importing them one by one.

Step 2: Flare Dependencies

While we're in the pubspec.yaml, we need to add our flare dependency: flareflutter. (Note: flareflutter depends on flaredart, so we don't need to explicitly add flaredart to our dependencies list).

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  # ... snip ...
  flare_flutter: ^1.3.4

Be sure to run flutter packages get now that we've modified our dependencies.

Our application now has everything we need to build the app.

The Code


Looping a Single Animation

Step 1: EndLoopController class

We're going to build a simple flare controller that will loop the last section of a specified animation. We are going to allow the animation, loop amount, and blending to be configured. Let's create the bare bones of the controller:

class EndLoopController implements FlareController {
  final String _animation;
  final double _loopAmount;
  final double _mix;

  EndLoopController(this._animation, this._loopAmount, [this._mix = 0.5]);

  
  void initialize(FlutterActorArtboard artboard) {
    _actor = artboard.getAnimation(_animation);
  }

  
  bool advance(FlutterActorArtboard artboard, double elapsed) {}

  
  void setViewTransform(Mat2D viewTransform) {}
}

First, the _animation is a string that represents a named animation in a flare asset. In the case of our single animation golf flag, we named the animation 'upandwave'. Next, the _loopAmount is the fractional number of seconds that we are going to loop at the end of the animation. Lastly, _mix is the blending that will be used with this animation.

In the initialize method, we get a reference to our animation's actor from the flare asset's artboard. We will be doing all of our animations on _actor.

For this simple example, we will not be implementing setViewTransform, but feel free to play with it on your own.

Let's do our magic animation now, and implement advance.

  
  bool advance(FlutterActorArtboard artboard, double elapsed) {
    _duration += elapsed;

    if (_duration > _actor.duration) {
      final double loopStart = _actor.duration - _loopAmount;
      final double loopProgress = _duration - _actor.duration;
      _duration = loopStart + loopProgress;
    }
    _actor.apply(_duration, artboard, _mix);
    return true;
  }

While our elapsed time is less than our animation's duration, we just move the animation along as we normally would (_actor.apply(...)). However, once our overall duration has exceeded the animation's duration, we're going to rewind by _loopAmount seconds to find our loopStart time, then we move partially forward by loopProgress into the animation to make sure the animation remains smooth for the frame rate.

Step 2: Using the controller in a widget

Now that we have our controller, let's use it in a simple widget to run the animation for our waving_golf_flag.flr file:

class WavingFlagPage extends StatefulWidget {
  
  State createState() => _WavingFlagState();
}

class _WavingFlagState extends State<WavingFlagPage> {
  final EndLoopController _loopController =
      EndLoopController('up_and_wave', 2.0);

  
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      padding: EdgeInsets.all(25.0),
      child: FlareActor(
        'assets/flare/waving_golf_flag.flr',
        alignment: Alignment.bottomCenter,
        fit: BoxFit.contain,
        controller: _loopController,
        isPaused: !mounted,
      ),
    );
  }
}

We set up the _loopController to loop the last 2.0 seconds of the 'upandwave' animation. Inside a basic container, we then initialize our FlareActor to load the flare asset, align it with the bottom center, make it grow to fit in as much of the container as we can, tell it to use our controller, and pause it until the component is fully mounted.

Assigning the controller is the magic sauce that makes this work. If you remove this line, then a default controller is used and the animation just runs once.

The last propery isPaused: !mounted is useful for preventing our animation from starting while we are still waiting on the Widgets to completely draw themselves for the first time.

All that is left is to use the WaveFlagPage widget somewhere in your app to display this new animation.

Looping a secondary animation

Step 1: DualAnimationLoopController class

As before, we're going to create a bare bones controller, but this time we need the names of the two animations we want to loop as well as the blending factor.

class DualAnimationLoopController implements FlareController {
  final String _startAnimationName;
  final String _loopAnimationName;
  final double _mix;

  DualAnimationLoopController(this._startAnimationName, this._loopAnimationName,
      [this._mix = 1.0]);

  bool _looping = false;
  double _duration = 0.0;
  ActorAnimation _startAnimation;
  ActorAnimation _loopAnimation;

  
  void initialize(FlutterActorArtboard artboard) {
    _startAnimation = artboard.getAnimation(_startAnimationName);
    _loopAnimation = artboard.getAnimation(_loopAnimationName);
  }

  
  bool advance(FlutterActorArtboard artboard, double elapsed) {}

  
  void setViewTransform(Mat2D viewTransform) {}
}

This is pretty similar to our last controller, except that we are now tracking a _looping state to determine if we are on the first or second animation. While _looping is false we're playing the first animation, once _looping becomes true then we are looping the second animation. Now the meat of the implementation: the advance method.

  
  bool advance(FlutterActorArtboard artboard, double elapsed) {
    _duration += elapsed;

    if (!_looping) {
      if (_duration < _startAnimation.duration) {
        _startAnimation.apply(_duration, artboard, _mix);
      } else {
        _looping = true;
        _duration -= _startAnimation.duration;
      }
    }
    if (_looping) {
      _duration %= _loopAnimation.duration;
      _loopAnimation.apply(_duration, artboard, _mix);
    }
    return true;
  }

Once we've gone through our whole first animation, we set _looping to true and reset our overall _duration back near zero (where our second animation will start). At this point, we now just keep looping through our second animation using _duration %= _loopAnimation.duration.

Step 2: Using the controller in a widget

Let's use this controller in a new widget now, but using our waving_golf_flag_dual_animation.flr asset instead. We have two named animations on this file: 'raise' and 'wave'. We're going to first play the 'raise' animation, then loop the 'wave' animation.

class WavingFlagDualPage extends StatefulWidget {
  
  State createState() => _WavingFlagDualPageState();
}

class _WavingFlagDualPageState extends State<WavingFlagDualPage> {
  final DualAnimationLoopController _loopController = DualAnimationLoopController('raise', 'wave');

  
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      padding: EdgeInsets.all(25.0),
      child: FlareActor(
        'assets/flare/waving_golf_flag_dual_animation.flr',
        alignment: Alignment.bottomCenter,
        fit: BoxFit.contain,
        controller: _loopController,
        isPaused: !mounted,
      ),
    );
  }
}

Everything is the same as the previous widget except the initialization code for the controller and the path to the flare asset. Oh, the magic of flare.

In Conclusion

Flare is extremely flexible in the control that it gives you over your assets, as you can see from our two looping examples above. Head on over to Rive to create a free account and put some Flare into your Flutter apps.

Brian Armstrong

Brian Armstrong

I'm Brian Armstrong, a SaaS developer with 15+ years programming experience. I am a Flutter evangelist and React.js enthusiast.