rive-flutter icon indicating copy to clipboard operation
rive-flutter copied to clipboard

Provide a way to subscribe to the rive animation ticker

Open lesnitsky opened this issue 2 years ago • 3 comments

It's easy enough to extend a simple animation controller so that it could be listened to and the outside world would know that the new frame was rendered, but this is not the case for StateMachineController. Check out this example. I had to rely on a source file directly and reimplement StateMachineController and partially copy the implementation, there's no other way to subscribe to the animation ticker (at least I didn't find one).

Would it be possible to make all animation controllers (including state machine) listenable?

I want to make this implementation cleaner. Here's what it looks like:

https://user-images.githubusercontent.com/6261302/226898221-f286721a-4885-4d09-8b07-cdf051c75587.mov

lesnitsky avatar Mar 22 '23 11:03 lesnitsky

Hey @lesnitsky

I tried to recreate something myself without looking at your code, and I basically got to the same result. Adding it here for transparency.

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late StateMachineController controller;

  void onInit(Artboard artboard) {
    controller = StateMachineOnTickController.fromArtboard(
      artboard,
      'State Machine 1',
      onTick: () {
        print('tick');
      },
    )!;
    artboard.addController(controller);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: RiveAnimation.asset(
        'assets/rotate-speed.riv',
        onInit: onInit,
      ),
    );
  }
}

class StateMachineOnTickController extends StateMachineController {
  StateMachineOnTickController(
    super.stateMachine, {
    core.OnStateChange? onStateChange,
    this.onTick,
  });

  final VoidCallback? onTick;

  static StateMachineController? fromArtboard(
    Artboard artboard,
    String stateMachineName, {
    core.OnStateChange? onStateChange,
    VoidCallback? onTick,
  }) {
    for (final animation in artboard.animations) {
      if (animation is StateMachine && animation.name == stateMachineName) {
        return StateMachineOnTickController(
          animation,
          onStateChange: onStateChange,
          onTick: onTick,
        );
      }
    }
    return null;
  }

  /// Override this to be notified on each animation frame.
  @override
  void apply(CoreContext core, double elapsedSeconds) {
    onTick?.call();
    super.apply(core, elapsedSeconds);
  }
}

How you're extending the State Machine Controller is completely fine.

It'll be simple enough for us to extend the API with a callback such as the one above if needed. Maybe then also supplying additional information, such as the elapsedSeconds. To make them listenable could have an impact on performance. We could potentially expose ListenableControllers alongside the regular ones - so that people can opt in to using them

HayesGordon avatar Mar 30 '23 15:03 HayesGordon

I neglected to add that the above requires src imports:

import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/state_machine_controller.dart' as core;

HayesGordon avatar Mar 30 '23 15:03 HayesGordon

Or alternatively we can create a Mixin, similar to AnimationLocalListenersMixin. Which is how Flutter's AnimationContoller works when calling addListener

HayesGordon avatar Mar 30 '23 15:03 HayesGordon