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_pause_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/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
void draw(Canvas canvas, Mat2D viewTransform) {
canvas.transform(viewTransform.mat4);
artboard.draw(canvas);
}
}

View File

@ -360,6 +360,10 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
/// advances.
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
/// it's already playing.
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/linear_animation.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_transition.dart';
import 'package:rive/src/rive_core/artboard.dart';
@ -158,7 +159,9 @@ class RiveFile {
case StateMachineLayerBase.typeKey:
stackObject = StateMachineLayerImporter(object as StateMachineLayer);
break;
case StateMachineEventBase.typeKey:
stackObject = StateMachineEventImporter(object as StateMachineEvent);
break;
case EntryStateBase.typeKey:
case AnyStateBase.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.
bool customPaint(PaintingContext context, Offset offset) => false;
@protected
@override
void paint(PaintingContext context, Offset offset) {
_frameCallbackId = -1;
if (advance(_elapsedSeconds)) {
scheduleRepaint();
} else {
_stopwatch.stop();
Vec2D globalToArtboard(Offset globalPosition) {
var local = globalToLocal(globalPosition);
var alignArtboard = computeAlignment();
var localToArtboard = Mat2D();
var localAsVec = Vec2D.fromValues(local.dx, local.dy);
if (!Mat2D.invert(localToArtboard, alignArtboard)) {
return localAsVec;
}
_elapsedSeconds = 0;
if (customPaint(context, offset)) {
return;
}
final Canvas canvas = context.canvas;
return Vec2D.transformMat2D(Vec2D(), localAsVec, localToArtboard);
}
Mat2D computeAlignment([Offset offset = Offset.zero]) {
AABB bounds = aabb;
double contentWidth = bounds[2] - bounds[0];
double contentHeight = bounds[3] - bounds[1];
if (contentWidth == 0 || contentHeight == 0) {
return;
return Mat2D();
}
double x = -1 * bounds[0] -
@ -229,9 +224,6 @@ abstract class RiveRenderBox extends RenderBox {
double scaleX = 1.0, scaleY = 1.0;
canvas.save();
beforeDraw(canvas, offset);
switch (_fit) {
case BoxFit.fill:
scaleX = size.width / contentWidth;
@ -278,14 +270,30 @@ abstract class RiveRenderBox extends RenderBox {
center[4] = x;
center[5] = y;
Mat2D.multiply(transform, transform, center);
return transform;
}
canvas.translate(
offset.dx + size.width / 2.0 + (_alignment.x * size.width / 2.0),
offset.dy + size.height / 2.0 + (_alignment.y * size.height / 2.0),
);
@protected
@override
void paint(PaintingContext context, Offset offset) {
_frameCallbackId = -1;
if (advance(_elapsedSeconds)) {
scheduleRepaint();
} else {
_stopwatch.stop();
}
_elapsedSeconds = 0;
canvas.scale(scaleX, scaleY);
canvas.translate(x, y);
if (customPaint(context, offset)) {
return;
}
final Canvas canvas = context.canvas;
canvas.save();
beforeDraw(canvas, offset);
var transform = computeAlignment(offset);
draw(canvas, transform);

View File

@ -1,5 +1,6 @@
import 'package:flutter/widgets.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
enum _Source {
@ -137,7 +138,8 @@ class _RiveAnimationState extends State<RiveAnimation> {
// controller specified, select a default animation
final animationNames = widget.animations.isEmpty &&
widget.stateMachines.isEmpty &&
widget.controllers.isEmpty
widget.controllers.isEmpty &&
widget.onInit == null
? [artboard.animations.first.name]
: widget.animations;
@ -169,13 +171,84 @@ class _RiveAnimationState extends State<RiveAnimation> {
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
Widget build(BuildContext context) => _artboard != null
? Rive(
artboard: _artboard!,
fit: widget.fit,
alignment: widget.alignment,
antialiasing: widget.antialiasing,
? _optionalHitTester(
context,
Rive(
artboard: _artboard!,
fit: widget.fit,
alignment: widget.alignment,
antialiasing: widget.antialiasing,
),
)
: widget.placeHolder ?? const SizedBox();
}