From 84d99621c9d1cf446942fd123c96244ed504cfc7 Mon Sep 17 00:00:00 2001 From: matt Sullivan Date: Mon, 21 Jun 2021 14:34:56 -0700 Subject: [PATCH] Adds onStateChange callback --- CHANGELOG.md | 7 ++- README.md | 2 +- example/lib/little_machine.dart | 19 ++++++- .../controllers/state_machine_controller.dart | 12 +++-- .../rive_core/state_machine_controller.dart | 51 +++++++++++++++++-- 5 files changed, 81 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ba0116..04a44d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,13 @@ +## [0.7.21] - 2021-06-21 14:00:00 +- Adds onStateChange callback to state machine controllers + ## [0.7.20] - 2021-06-19 19:01:10 -- Quick start fixes in README.md. +- Quick start fixes in README.md + ## [0.7.19] - 2021-06-18 12:00:00 - BREAKING CHANGE: onInit callback now takes an artboard as a parameter - Adds simple state machine example + ## [0.7.18] - 2021-06-14 12:00:00 - Adds ability to pass controllers into RiveAnimation widgets - Adds autoplay option to SimpleAnimation controller diff --git a/README.md b/README.md index 1e9124f..b5cdc38 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Runtime docs are available in [Rive's help center](https://help.rive.app/runtime ```yaml dependencies: - rive: ^0.7.20 + rive: ^0.7.21 ``` ## Quick Start diff --git a/example/lib/little_machine.dart b/example/lib/little_machine.dart index d2237b4..7653db0 100644 --- a/example/lib/little_machine.dart +++ b/example/lib/little_machine.dart @@ -15,6 +15,9 @@ class _LittleMachineState extends State { /// Tracks if the animation is playing by whether controller is running. bool get isPlaying => _controller?.isActive ?? false; + /// Message that displays when state has changed + String stateChangeMessage = ''; + Artboard? _riveArtboard; StateMachineController? _controller; SMIInput? _trigger; @@ -33,8 +36,11 @@ class _LittleMachineState extends State { // The artboard is the root of the animation and gets drawn in the // Rive widget. final artboard = file.mainArtboard; - var controller = - StateMachineController.fromArtboard(artboard, 'State Machine 1'); + var controller = StateMachineController.fromArtboard( + artboard, + 'State Machine 1', + onStateChange: _onStateChange, + ); if (controller != null) { artboard.addController(controller); _trigger = controller.findInput('Trigger 1'); @@ -44,6 +50,12 @@ class _LittleMachineState extends State { ); } + /// Do something when the state machine changes state + void _onStateChange(String stateMachineName, String stateName) => setState( + () => stateChangeMessage = + 'State Changed in $stateMachineName to $stateName', + ); + @override Widget build(BuildContext context) { return Scaffold( @@ -71,6 +83,9 @@ class _LittleMachineState extends State { artboard: _riveArtboard!, ), ), + const SizedBox(height: 10), + Text('$stateChangeMessage'), + const SizedBox(height: 10), ], ), ), diff --git a/lib/src/controllers/state_machine_controller.dart b/lib/src/controllers/state_machine_controller.dart index cec66d8..2c8d5fe 100644 --- a/lib/src/controllers/state_machine_controller.dart +++ b/lib/src/controllers/state_machine_controller.dart @@ -119,7 +119,10 @@ class StateMachineController extends core.StateMachineController { /// A list of inputs available in the StateMachine. Iterable get inputs => _inputs; - StateMachineController(StateMachine stateMachine) : super(stateMachine) { + StateMachineController( + StateMachine stateMachine, { + core.OnStateChange? onStateChange, + }) : super(stateMachine, onStateChange: onStateChange) { isActive = true; for (final input in stateMachine.inputs) { switch (input.coreType) { @@ -140,10 +143,13 @@ class StateMachineController extends core.StateMachineController { /// [stateMachineName]. Returns the [StateMachineController] or null if no /// [StateMachine] with [stateMachineName] is found. static StateMachineController? fromArtboard( - Artboard artboard, String stateMachineName) { + Artboard artboard, + String stateMachineName, { + core.OnStateChange? onStateChange, + }) { for (final animation in artboard.animations) { if (animation is StateMachine && animation.name == stateMachineName) { - return StateMachineController(animation); + return StateMachineController(animation, onStateChange: onStateChange); } } return null; diff --git a/lib/src/rive_core/state_machine_controller.dart b/lib/src/rive_core/state_machine_controller.dart index c14f6ff..c4e7b6d 100644 --- a/lib/src/rive_core/state_machine_controller.dart +++ b/lib/src/rive_core/state_machine_controller.dart @@ -1,5 +1,6 @@ import 'dart:collection'; +import 'package:flutter/scheduler.dart'; import 'package:rive/src/core/core.dart'; import 'package:flutter/foundation.dart'; import 'package:rive/src/rive_core/animation/animation_state_instance.dart'; @@ -12,6 +13,16 @@ import 'package:rive/src/rive_core/animation/state_machine_layer.dart'; import 'package:rive/src/rive_core/animation/state_transition.dart'; import 'package:rive/src/rive_core/rive_animation_controller.dart'; +import 'animation/animation_state.dart'; +import 'animation/entry_state.dart'; +import 'animation/exit_state.dart'; + +/// Callback signature for satate machine state changes +typedef OnStateChange = void Function(String, String); + +/// Callback signature for layer state changes +typedef OnLayerStateChange = void Function(LayerState); + class LayerController { final StateMachineLayer layer; final StateInstance anyStateInstance; @@ -23,7 +34,11 @@ class LayerController { double _mix = 1.0; double _mixFrom = 1.0; - LayerController(this.layer) + /// Optional callback which is called when a state changes + /// Takes the state machine name and state name + final OnLayerStateChange? onLayerStateChange; + + LayerController(this.layer, {this.onLayerStateChange}) : assert(layer.anyState != null), anyStateInstance = layer.anyState!.makeInstance() { _changeState(layer.entryState); @@ -167,6 +182,10 @@ class LayerController { // Make sure to reset _waitingForExit to false if we succeed at taking a // transition. _waitingForExit = false; + // State has changed, fire the callback if there's one + if (_currentState != null) { + onLayerStateChange?.call(_currentState!.state); + } return true; } else if (allowed == AllowTransition.waitingForExit) { _waitingForExit = true; @@ -179,9 +198,14 @@ class LayerController { class StateMachineController extends RiveAnimationController { final StateMachine stateMachine; final inputValues = HashMap(); - StateMachineController(this.stateMachine); final layerControllers = []; + /// Optional callback for state changes + final OnStateChange? onStateChange; + + /// Constructor that takes a state machine and optional state change callback + StateMachineController(this.stateMachine, {this.onStateChange}); + void _clearLayerControllers() { for (final layer in layerControllers) { layer.dispose(); @@ -189,12 +213,33 @@ class StateMachineController extends RiveAnimationController { layerControllers.clear(); } + /// Handles state change callbacks + void _onStateChange(LayerState layerState) => + SchedulerBinding.instance?.addPostFrameCallback((_) { + String stateName = 'unknown'; + print('Layer state type ${layerState.runtimeType}'); + if (layerState is AnimationState && layerState.animation != null) { + stateName = layerState.animation!.name; + } else if (layerState is EntryState) { + stateName = 'EntryState'; + } else if (layerState is AnyState) { + stateName = 'EntryState'; + } else if (layerState is ExitState) { + stateName = 'ExitState'; + } + + onStateChange?.call(stateMachine.name, stateName); + }); + @override bool init(CoreContext core) { _clearLayerControllers(); for (final layer in stateMachine.layers) { - layerControllers.add(LayerController(layer)); + layerControllers.add(LayerController( + layer, + onLayerStateChange: _onStateChange, + )); } // Make sure triggers are all reset.