Add a Listener to the RiveAnimation widget when a StateMachineController has events.

This commit is contained in:
Luigi Rosso
2022-05-11 15:23:39 -07:00
committed by Luigi Rosso
parent f31b576209
commit 338d739bf7
7 changed files with 168 additions and 32 deletions

View File

@ -6,6 +6,7 @@ import 'package:rive_example/little_machine.dart';
import 'package:rive_example/play_one_shot_animation.dart'; import 'package:rive_example/play_one_shot_animation.dart';
import 'package:rive_example/play_pause_animation.dart'; import 'package:rive_example/play_pause_animation.dart';
import 'package:rive_example/simple_animation.dart'; import 'package:rive_example/simple_animation.dart';
import 'package:rive_example/simple_machine_action.dart';
import 'package:rive_example/simple_state_machine.dart'; import 'package:rive_example/simple_state_machine.dart';
import 'package:rive_example/state_machine_skills.dart'; import 'package:rive_example/state_machine_skills.dart';
@ -148,6 +149,20 @@ class Home extends StatelessWidget {
); );
}, },
), ),
const SizedBox(
height: 10,
),
ElevatedButton(
child: const Text('State Machine with Action'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute<void>(
builder: (context) => const StateMachineAction(),
),
);
},
),
], ],
), ),
), ),

View File

@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
class StateMachineAction extends StatefulWidget {
const StateMachineAction({Key? key}) : super(key: key);
@override
_StateMachineActionState createState() => _StateMachineActionState();
}
class _StateMachineActionState extends State<StateMachineAction> {
void _onRiveInit(Artboard artboard) {
final controller = StateMachineController.fromArtboard(artboard, 'Switch');
artboard.addController(controller!);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Light Switch'),
),
body: Center(
child: RiveAnimation.asset(
'assets/light_switch.riv',
fit: BoxFit.contain,
onInit: _onRiveInit,
),
),
);
}
}

View File

@ -112,6 +112,7 @@ class RiveRenderObject extends RiveRenderBox {
@override @override
void draw(Canvas canvas, Mat2D viewTransform) { void draw(Canvas canvas, Mat2D viewTransform) {
canvas.transform(viewTransform.mat4);
artboard.draw(canvas); artboard.draw(canvas);
} }
} }

View File

@ -360,6 +360,10 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
/// advances. /// advances.
final Set<RiveAnimationController> _animationControllers = {}; final Set<RiveAnimationController> _animationControllers = {};
/// Access a read-only iterator of currently applied animation controllers.
Iterable<RiveAnimationController> get animationControllers =>
_animationControllers;
/// Add an animation controller to this artboard. Playing will be scheduled if /// Add an animation controller to this artboard. Playing will be scheduled if
/// it's already playing. /// it's already playing.
bool addController(RiveAnimationController controller) { bool addController(RiveAnimationController controller) {

View File

@ -20,6 +20,7 @@ import 'package:rive/src/rive_core/animation/keyed_property.dart';
import 'package:rive/src/rive_core/animation/layer_state.dart'; import 'package:rive/src/rive_core/animation/layer_state.dart';
import 'package:rive/src/rive_core/animation/linear_animation.dart'; import 'package:rive/src/rive_core/animation/linear_animation.dart';
import 'package:rive/src/rive_core/animation/state_machine.dart'; import 'package:rive/src/rive_core/animation/state_machine.dart';
import 'package:rive/src/rive_core/animation/state_machine_event.dart';
import 'package:rive/src/rive_core/animation/state_machine_layer.dart'; 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/animation/state_transition.dart';
import 'package:rive/src/rive_core/artboard.dart'; import 'package:rive/src/rive_core/artboard.dart';
@ -158,7 +159,9 @@ class RiveFile {
case StateMachineLayerBase.typeKey: case StateMachineLayerBase.typeKey:
stackObject = StateMachineLayerImporter(object as StateMachineLayer); stackObject = StateMachineLayerImporter(object as StateMachineLayer);
break; break;
case StateMachineEventBase.typeKey:
stackObject = StateMachineEventImporter(object as StateMachineEvent);
break;
case EntryStateBase.typeKey: case EntryStateBase.typeKey:
case AnyStateBase.typeKey: case AnyStateBase.typeKey:
case ExitStateBase.typeKey: case ExitStateBase.typeKey:

View File

@ -194,30 +194,25 @@ abstract class RiveRenderBox extends RenderBox {
/// be called after advancing. Return true to prevent regular paint. /// be called after advancing. Return true to prevent regular paint.
bool customPaint(PaintingContext context, Offset offset) => false; bool customPaint(PaintingContext context, Offset offset) => false;
@protected Vec2D globalToArtboard(Offset globalPosition) {
@override var local = globalToLocal(globalPosition);
void paint(PaintingContext context, Offset offset) { var alignArtboard = computeAlignment();
_frameCallbackId = -1; var localToArtboard = Mat2D();
if (advance(_elapsedSeconds)) { var localAsVec = Vec2D.fromValues(local.dx, local.dy);
scheduleRepaint(); if (!Mat2D.invert(localToArtboard, alignArtboard)) {
} else { return localAsVec;
_stopwatch.stop();
} }
_elapsedSeconds = 0; return Vec2D.transformMat2D(Vec2D(), localAsVec, localToArtboard);
}
if (customPaint(context, offset)) {
return;
}
final Canvas canvas = context.canvas;
Mat2D computeAlignment([Offset offset = Offset.zero]) {
AABB bounds = aabb; AABB bounds = aabb;
double contentWidth = bounds[2] - bounds[0]; double contentWidth = bounds[2] - bounds[0];
double contentHeight = bounds[3] - bounds[1]; double contentHeight = bounds[3] - bounds[1];
if (contentWidth == 0 || contentHeight == 0) { if (contentWidth == 0 || contentHeight == 0) {
return; return Mat2D();
} }
double x = -1 * bounds[0] - double x = -1 * bounds[0] -
@ -229,9 +224,6 @@ abstract class RiveRenderBox extends RenderBox {
double scaleX = 1.0, scaleY = 1.0; double scaleX = 1.0, scaleY = 1.0;
canvas.save();
beforeDraw(canvas, offset);
switch (_fit) { switch (_fit) {
case BoxFit.fill: case BoxFit.fill:
scaleX = size.width / contentWidth; scaleX = size.width / contentWidth;
@ -278,14 +270,30 @@ abstract class RiveRenderBox extends RenderBox {
center[4] = x; center[4] = x;
center[5] = y; center[5] = y;
Mat2D.multiply(transform, transform, center); Mat2D.multiply(transform, transform, center);
return transform;
}
canvas.translate( @protected
offset.dx + size.width / 2.0 + (_alignment.x * size.width / 2.0), @override
offset.dy + size.height / 2.0 + (_alignment.y * size.height / 2.0), void paint(PaintingContext context, Offset offset) {
); _frameCallbackId = -1;
if (advance(_elapsedSeconds)) {
scheduleRepaint();
} else {
_stopwatch.stop();
}
_elapsedSeconds = 0;
canvas.scale(scaleX, scaleY); if (customPaint(context, offset)) {
canvas.translate(x, y); return;
}
final Canvas canvas = context.canvas;
canvas.save();
beforeDraw(canvas, offset);
var transform = computeAlignment(offset);
draw(canvas, transform); draw(canvas, transform);

View File

@ -1,5 +1,6 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:rive/rive.dart'; import 'package:rive/rive.dart';
import 'package:rive/src/rive_core/math/vec2d.dart';
/// Specifies whether a source is from an asset bundle or http /// Specifies whether a source is from an asset bundle or http
enum _Source { enum _Source {
@ -137,7 +138,8 @@ class _RiveAnimationState extends State<RiveAnimation> {
// controller specified, select a default animation // controller specified, select a default animation
final animationNames = widget.animations.isEmpty && final animationNames = widget.animations.isEmpty &&
widget.stateMachines.isEmpty && widget.stateMachines.isEmpty &&
widget.controllers.isEmpty widget.controllers.isEmpty &&
widget.onInit == null
? [artboard.animations.first.name] ? [artboard.animations.first.name]
: widget.animations; : widget.animations;
@ -169,13 +171,84 @@ class _RiveAnimationState extends State<RiveAnimation> {
super.dispose(); super.dispose();
} }
Vec2D? _toArtboard(Offset local) {
RiveRenderObject? riveRenderer;
var renderObject = context.findRenderObject();
if (renderObject is! RenderBox) {
return null;
}
renderObject.visitChildren(
(child) {
if (child is RiveRenderObject) {
riveRenderer = child;
}
},
);
if (riveRenderer == null) {
return null;
}
var globalCoordinates = renderObject.localToGlobal(local);
return riveRenderer!.globalToArtboard(globalCoordinates);
}
Widget _optionalHitTester(BuildContext context, Widget child) {
assert(_artboard != null);
var hasHitTesting = _artboard!.animationControllers.any((controller) =>
controller is StateMachineController &&
controller.hitShapes.isNotEmpty);
if (hasHitTesting) {
void hitHelper(PointerEvent event,
void Function(StateMachineController, Vec2D) callback) {
var artboardPosition = _toArtboard(event.localPosition);
if (artboardPosition != null) {
var stateMachineControllers = _artboard!.animationControllers
.whereType<StateMachineController>();
for (final stateMachineController in stateMachineControllers) {
callback(stateMachineController, artboardPosition);
}
}
}
return Listener(
onPointerDown: (details) => hitHelper(
details,
(controller, artboardPosition) =>
controller.pointerDown(artboardPosition),
),
onPointerUp: (details) => hitHelper(
details,
(controller, artboardPosition) =>
controller.pointerUp(artboardPosition),
),
onPointerHover: (details) => hitHelper(
details,
(controller, artboardPosition) =>
controller.pointerMove(artboardPosition),
),
onPointerMove: (details) => hitHelper(
details,
(controller, artboardPosition) =>
controller.pointerMove(artboardPosition),
),
child: child,
);
}
return child;
}
@override @override
Widget build(BuildContext context) => _artboard != null Widget build(BuildContext context) => _artboard != null
? Rive( ? _optionalHitTester(
artboard: _artboard!, context,
fit: widget.fit, Rive(
alignment: widget.alignment, artboard: _artboard!,
antialiasing: widget.antialiasing, fit: widget.fit,
alignment: widget.alignment,
antialiasing: widget.antialiasing,
),
) )
: widget.placeHolder ?? const SizedBox(); : widget.placeHolder ?? const SizedBox();
} }