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

FlareActor callback never called #198

Open workerbee22 opened 4 years ago

workerbee22 commented 4 years ago

Continuing to try and get a basic countdown going. Sample code uses the flare speed control example https://github.com/luigi-rosso/flare_control_speed_example/blob/master/lib/main.dart

Using flare_flutter 1.7.3

This code never prints"finished":

import 'package:flutter/material.dart';

import 'dart:async';   // for Timer

import 'package:flutter/material.dart';

import 'package:flare_flutter/flare.dart';
import 'package:flare_flutter/flare_actor.dart';
import 'package:flare_flutter/flare_controller.dart';

import 'package:flare_dart/math/mat2d.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flare Single Digit',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flare Single Digit'),
    );
  }
}

/// Flare related -----------------------------------------------------------------------------------------

/// From Flare contact, see issue https://github.com/2d-inc/Flare-Flutter/issues/167
/// Sample code here: https://github.com/luigi-rosso/flare_control_speed_example/blob/master/lib/main.dart
class SpeedController extends FlareController {

  final String animationName;
  ActorAnimation _animation;
  double speed;
  double _time = 0;

  // Optional param spped defaults to 1 if not provided
  SpeedController(this.animationName, {this.speed = 1});

  @override
  bool advance(FlutterActorArtboard artboard, double elapsed) {

    if (_animation == null) {
      return false;
    }
    if (_animation.isLooping) {
      _time %= _animation.duration;
    }
    _animation.apply(_time, artboard, 1.0);
    _time += elapsed * speed;

    // Stop advancing if animation is done and we're not looping.
    return _animation.isLooping || _time < _animation.duration;
  }

  @override
  void initialize(FlutterActorArtboard artboard) {
    _animation = artboard.getAnimation(animationName);
  }

  @override
  void setViewTransform(Mat2D viewTransform) {
    // intentionally empty, we don't need the viewTransform in this controller
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  int digit = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            SizedBox(
              height: 150,
              width: 80,

              /// FlareActor()
              // Use controller to specify which animation and see above controls speed and thus duration
              // but the artboard ie. the digit is passed in here
              child: Digit(digit),
            ),

            RaisedButton(
              child: Text(
                "Next",
              ),
              elevation: 2.0,
              onPressed: () {
                setState(() {
                  if(digit == 9) {
                    digit = 0;
                  } else {
                    digit = digit + 1;
                  }
                });
              },
            ),
          ],
        ),
      ),
    );
  }
}

// Digit - single digit using the flare SpeedController
class Digit extends StatefulWidget {

  final int digitNew;

  Digit(this.digitNew);

  @override
  _DigitState createState() => _DigitState();
}

class _DigitState extends State<Digit> {

  SpeedController controller;

  @override
  void initState() {
    super.initState();
    // Instantiate a speed controlled controller for the animation
    controller = SpeedController("in", speed: 0.1);
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {

    return Container(
      margin: const EdgeInsets.all(0.0),
      padding: const EdgeInsets.all(0.0),
      child: Stack(
          children: <Widget>[
            SizedBox(
              height: 150,
              width: 80,

              /// FlareActor()
              // Use controller to specify which animation and see above controls speed and thus duration
              // but the artboard ie. the digit is passed in here
              child: FlareActor(
                "assets/io_2018_digits.flr",
                controller: controller,
                artboard: widget.digitNew.toString(),
                fit: BoxFit.contain,
                callback: (value) {
                  print('finished?');
                },
              ),
            ),

          ]
      ),
    );
  }
}
umberto-sonnino commented 4 years ago

The callback registered within FlareActor is used on the animations that are played through FlareActor only. Since you're playing animations with your SpeedController, you should add a callback parameter to that as well:

class SpeedController extends FlareController {
  [...]
  FlareCompletedCallback onAnimationCompleted;

  // Optional param spped defaults to 1 if not provided
  SpeedController(this.animationName, {this.speed = 1, this.onAnimationCompleted});

  @override
  bool advance(FlutterActorArtboard artboard, double elapsed) {

    if (_animation == null) {
      return false;
    }
    if (_animation.isLooping) {
      _time %= _animation.duration;
    }
    _animation.apply(_time, artboard, 1.0);
    _time += elapsed * speed;

    if (_time > _animation.duration && onAnimationCompleted != null) {
      onAnimationCompleted(_animation.name);
    }
    // Stop advancing if animation is done and we're not looping.
    return _animation.isLooping || _time < _animation.duration;
  }
  [...]
}
workerbee22 commented 4 years ago

Thanks @umberto-sonnino but is there any documentation on how FlareCompletedCallback works? Changes above crash when onAnimationCompleted(_animation.name); is executed.

umberto-sonnino commented 4 years ago

FlareCompletedCallback is just a type definition: typedef void FlareCompletedCallback(String name); It is a function that takes a String parameter and doesn't return any value.

In the snippet above, you need to specify a callback in the constructor for it to not crash. I'll update it with a null check! And when instantiating your SpeedController:

SpeedController(
 myAnimationName,
 onAnimationCompleted: (name) {
  print("Animation $name completed!");
 }
)