mirror of
https://github.com/rive-app/rive-flutter.git
synced 2025-06-23 07:18:17 +08:00
Add a Listener to the RiveAnimation widget when a StateMachineController has events.
This commit is contained in:
@ -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(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
32
example/lib/simple_machine_action.dart
Normal file
32
example/lib/simple_machine_action.dart
Normal 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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:
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user