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:
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).
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
.
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;
}
bool
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.
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;
}
bool
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.