mirror of
https://github.com/rive-app/rive-flutter.git
synced 2025-06-22 14:41:27 +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_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(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
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
|
||||
void draw(Canvas canvas, Mat2D viewTransform) {
|
||||
canvas.transform(viewTransform.mat4);
|
||||
artboard.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
return Vec2D.transformMat2D(Vec2D(), localAsVec, localToArtboard);
|
||||
}
|
||||
|
||||
final Canvas canvas = context.canvas;
|
||||
|
||||
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);
|
||||
|
||||
|
@ -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(
|
||||
? _optionalHitTester(
|
||||
context,
|
||||
Rive(
|
||||
artboard: _artboard!,
|
||||
fit: widget.fit,
|
||||
alignment: widget.alignment,
|
||||
antialiasing: widget.antialiasing,
|
||||
),
|
||||
)
|
||||
: widget.placeHolder ?? const SizedBox();
|
||||
}
|
||||
|
Reference in New Issue
Block a user