2d-inc / Flare-Flutter

Load and get full control of your Rive files in a Flutter project using this library.
https://rive.app/
MIT License
2.55k stars 469 forks source link

How to stop flare animation loop ? #200

Closed EArminjon closed 3 years ago

EArminjon commented 4 years ago

Hello,

How stop flare animation loop ?

import 'package:flare_dart/math/mat2d.dart';
import 'package:flare_flutter/flare.dart';
import 'package:flare_flutter/flare_controller.dart';

class SingleLoopController extends FlareController {
  final String _animation;
  final double _loopAmount;
  final double _mix;
  ActorAnimation _actor;
  double _duration = 0;

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

  @override
  void initialize(FlutterActorArtboard artBoard) {
    _actor = artBoard.getAnimation(_animation);
  }

  @override
  bool advance(FlutterActorArtboard artBoard, double elapsed) {
    _duration += elapsed;

    if (_duration >= _actor.duration * _loopAmount) {
      isActive.value = false;
      return false;
    }
    _actor.apply(_duration, artBoard, _mix);
    return true;
  }

  @override
  void setViewTransform(Mat2D viewTransform) {}
}

I try to do it in advance function but true/false do nothing...

On this code example i try to manage a flare animation to set the number of repetition.

Can you help me ?

umberto-sonnino commented 4 years ago

It looks like you're on the right path, using a Controller is the way to go! But you're never actually looping the animation:

if (_duration >= _actor.duration * _loopAmount) {
 isActive.value = false;
 return false;
}

This'll stop the animation once the current _duration variable is bigger than animation duration by the number of iterations you want it to last, but you keep incrementing _duration nonetheless, so the _duration time is allowed to increment beyond the animation duration.

To loop a number of times, you might want to resort to using a control variable such as _loopCount and do this instead:

if(_loopCount >= _loopAmount) {
 // Looped enough times!
 return false;
}
_duration += elapsed;

if (_duration >= _actor.duration) {
 // Loop!
 _count++;
 _duration %= _actor.duration;
}
_actor.apply(_duration, artBoard, _mix);
return true;

Also, assuming you're attaching the Controller on a FlareActor, there's no need to set isActive within the Controller, that's already handled by FlareActor when the Controller returns false the first time.

EArminjon commented 4 years ago
if (_loopCount >= _loopAmount) {
      // Looped enough times!
      print("RETURN FALSE");
      return false;
    }
    _duration += elapsed;

    if (_duration >= _actor.duration) {
      // Loop!
      _loopCount++;
      _duration %= _actor.duration;
    }
    _actor.apply(_duration, artBoard, _mix);
    return true;

When it return false, animation is not stopped :'(

I/flutter ( 1502): RETURN FALSE
I/flutter ( 1502): RETURN FALSE
I/flutter ( 1502): RETURN FALSE
I/flutter ( 1502): RETURN FALSE
I/flutter ( 1502): RETURN FALSE
I/flutter ( 1502): RETURN FALSE
[...]
umberto-sonnino commented 4 years ago

How are you instantiating your FlareActor? If you specified an animation parameter for FlareActor, but also a FlareController attached to it, that FlareActor will keep playing.

The animation parameter is meant to be an easy way to run a single animation, but if you have a more elaborate setup, with multiple animations mixing together, you should resort to using just the Controllers.

EArminjon commented 4 years ago

Thx for your answer :) !

Container(
                    width: 100,
                    height: 100,
                    child: FlareActor(
                      "assets/animations/success.flr",
                      alignment: Alignment.center,
                      fit: BoxFit.contain,
                      controller: SingleLoopController("Animate", 1),
                    //  animation: "Animate",
                    ),
                  ),

It works ! I try now to freeze the last image of the animation when all loop are completed :

@override
  bool advance(FlutterActorArtboard artBoard, double elapsed) {
    if (_loopCount >= _loopAmount) {
      // Looped enough times!
      _actor.apply(_actor.duration, artBoard, 1);
      return false;
    }
    _duration += elapsed;

    if (_duration >= _actor.duration) {
      // Loop!
      _loopCount++;
      _duration %= _actor.duration;
    }
    _actor.apply(_duration, artBoard, _mix);
    return true;
  }

It seem to work but I didn't know if i do well :/ And last, question : how clear animation (clear the artboard) in controller ?

umberto-sonnino commented 4 years ago

If you want to freeze the animation on the last frame it'd be safer to perform the check like this instead:

_duration += elapsed;
if (_duration >= actor.duration) {
 _loopCount++;
 if (_loopCount >= _loopAmount) {
  // Early out.
  _actor.apply(_actor.duration, artBoard, 1);
  return false;
 }
 // Loop!
 _duration %= _actor.duration;
}
[...]

This is to prevent any popping in the animation, as with the code above you'd be first applying the looped frame of the animation, and then the last one right away.

By 'clear', do you mean just paint an empty monochromatic canvas?

EArminjon commented 4 years ago

"By 'clear', do you mean just paint an empty monochromatic canvas?"

Yes, transparent for example.

EArminjon commented 4 years ago

Thanks, for your previous reply, it help me a lot !!!