diff --git a/.rive_head b/.rive_head index 3a3da295..249c701c 100644 --- a/.rive_head +++ b/.rive_head @@ -1 +1 @@ -7986d64d8371531716ea3f038dcbec5da187e6cd +8d1fdd16ad196c3acca18faa89db2e1f4ef94365 diff --git a/CHANGELOG.md b/CHANGELOG.md index a1be7159..c0304b38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## Upcoming + +- Adds the `isTouchScrollEnabled` property to `RiveAnimation` and `Rive` widgets. When `true` allows scrolling behavior to occur on Rive widgets when a touch/drag action is performed on touch-enabled devices. Defauls to `false`, which means Rive will "absorb" the pointer down event and a scroll cannot be triggered if the touch occured within a Rive Listener area. Setting to `true` will impact Rive's capability to handle multiple gestures simultaneously. + ## 0.13.18 - Bump to latest `rive_common`, v0.4.13. Resolves [issues building rive_common downstream](https://github.com/rive-app/rive-flutter/issues/354#issuecomment-2491004291). diff --git a/lib/src/rive.dart b/lib/src/rive.dart index 5b740f14..0a8af09c 100644 --- a/lib/src/rive.dart +++ b/lib/src/rive.dart @@ -4,6 +4,8 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:rive/src/controllers/state_machine_controller.dart'; import 'package:rive/src/rive_core/artboard.dart'; +import 'package:rive/src/rive_core/state_machine_controller.dart' + show HitResult; import 'package:rive/src/rive_render_box.dart'; import 'package:rive/src/runtime_artboard.dart'; import 'package:rive_common/math.dart'; @@ -99,6 +101,14 @@ class Rive extends LeafRenderObjectWidget { /// Default `1.0`. final double speedMultiplier; + /// For Rive Listeners, allows scrolling behavior to still occur on Rive + /// widgets when a touch/drag action is performed on touch-enabled devices. + /// Otherwise, scroll behavior may be prevented on touch/drag actions on the + /// widget by default. + /// + /// Default `false`. + final bool isTouchScrollEnabled; + const Rive({ required this.artboard, super.key, @@ -111,6 +121,7 @@ class Rive extends LeafRenderObjectWidget { this.alignment = Alignment.center, this.clipRect, this.speedMultiplier = 1.0, + this.isTouchScrollEnabled = false, }); @override @@ -128,7 +139,8 @@ class Rive extends LeafRenderObjectWidget { ..enableHitTests = enablePointerEvents ..cursor = cursor ..behavior = behavior - ..speedMultiplier = speedMultiplier; + ..speedMultiplier = speedMultiplier + ..isTouchScrollEnabled = isTouchScrollEnabled; } @override @@ -147,7 +159,8 @@ class Rive extends LeafRenderObjectWidget { ..enableHitTests = enablePointerEvents ..cursor = cursor ..behavior = behavior - ..speedMultiplier = speedMultiplier; + ..speedMultiplier = speedMultiplier + ..isTouchScrollEnabled = isTouchScrollEnabled; } } @@ -245,6 +258,20 @@ class RiveRenderObject extends RiveRenderBox implements MouseTrackerAnnotation { return false; } + // TODO: A possible alternative to [isTouchScrollEnabled] is to allow + // users to set custom recognizers. Or for us to provide + // heuristics on whether a Rive graphic has certain gestures: + // - Pointer down/up + // - Drag + // - Scroll + // - etc. + // With this information we can better decide which recognizers to use, + // while optionally allowing end users to override it, or provide custom ones. + // Can be considered for `rive_native`. + // + // https://api.flutter.dev/flutter/gestures/GestureArenaManager-class.html + final _recognizer = ImmediateMultiDragGestureRecognizer(); + @override void handleEvent(PointerEvent event, HitTestEntry entry) { assert(debugHandleEvent(event, entry)); @@ -252,11 +279,13 @@ class RiveRenderObject extends RiveRenderBox implements MouseTrackerAnnotation { return; } if (event is PointerDownEvent) { - _hitHelper( - event, - (controller, artboardPosition) => - controller.pointerDown(artboardPosition, event), - ); + _hitHelper(event, (controller, artboardPosition) { + final hitResult = controller.pointerDown(artboardPosition, event); + + if (hitResult != HitResult.none && !isTouchScrollEnabled) { + _recognizer.addPointer(event); + } + }); } if (event is PointerUpEvent) { _hitHelper( @@ -338,6 +367,7 @@ class RiveRenderObject extends RiveRenderBox implements MouseTrackerAnnotation { @override void dispose() { _artboard.redraw.removeListener(scheduleRepaint); + _recognizer.dispose(); super.dispose(); } diff --git a/lib/src/rive_core/state_machine_controller.dart b/lib/src/rive_core/state_machine_controller.dart index 60f3d73e..1e5ef31a 100644 --- a/lib/src/rive_core/state_machine_controller.dart +++ b/lib/src/rive_core/state_machine_controller.dart @@ -466,8 +466,6 @@ class StateMachineController extends RiveAnimationController late CoreContext core; - final _recognizer = ImmediateMultiDragGestureRecognizer(); - @override bool init(CoreContext core) { this.core = core; @@ -734,9 +732,6 @@ class StateMachineController extends RiveAnimationController hitEvent: ListenerType.down, pointerEvent: event, ); - if (hitResult != HitResult.none) { - _recognizer.addPointer(event); - } return hitResult; } diff --git a/lib/src/rive_render_box.dart b/lib/src/rive_render_box.dart index a540af77..cd3c6948 100644 --- a/lib/src/rive_render_box.dart +++ b/lib/src/rive_render_box.dart @@ -13,6 +13,7 @@ abstract class RiveRenderBox extends RenderBox { Rect? _clipRect; bool _tickerModeEnabled = true; bool _enableHitTests = false; + bool _isTouchScrollEnabled = false; bool get useArtboardSize => _useArtboardSize; @@ -89,6 +90,14 @@ abstract class RiveRenderBox extends RenderBox { } } + bool get isTouchScrollEnabled => _isTouchScrollEnabled; + + set isTouchScrollEnabled(bool value) { + if (value != _isTouchScrollEnabled) { + _isTouchScrollEnabled = value; + } + } + bool _paintedLastFrame = false; @override diff --git a/lib/src/widgets/rive_animation.dart b/lib/src/widgets/rive_animation.dart index bfa47eb7..c8d61775 100644 --- a/lib/src/widgets/rive_animation.dart +++ b/lib/src/widgets/rive_animation.dart @@ -76,6 +76,14 @@ class RiveAnimation extends StatefulWidget { /// Default `1.0`. final double speedMultiplier; + /// For Rive Listeners, allows scrolling behavior to still occur on Rive + /// widgets when a touch/drag action is performed on touch-enabled devices. + /// Otherwise, scroll behavior may be prevented on touch/drag actions on the + /// widget by default. + /// + /// Default `false`. + final bool isTouchScrollEnabled; + /// Creates a new [RiveAnimation] from an asset bundle. /// /// *Example:* @@ -98,6 +106,7 @@ class RiveAnimation extends StatefulWidget { this.behavior = RiveHitTestBehavior.opaque, this.objectGenerator, this.speedMultiplier = 1, + this.isTouchScrollEnabled = false, Key? key, }) : name = asset, file = null, @@ -128,6 +137,7 @@ class RiveAnimation extends StatefulWidget { this.behavior = RiveHitTestBehavior.opaque, this.objectGenerator, this.speedMultiplier = 1, + this.isTouchScrollEnabled = false, Key? key, }) : name = url, file = null, @@ -156,6 +166,7 @@ class RiveAnimation extends StatefulWidget { this.behavior = RiveHitTestBehavior.opaque, this.objectGenerator, this.speedMultiplier = 1, + this.isTouchScrollEnabled = false, Key? key, }) : name = path, file = null, @@ -185,6 +196,7 @@ class RiveAnimation extends StatefulWidget { this.controllers = const [], this.onInit, this.speedMultiplier = 1, + this.isTouchScrollEnabled = false, Key? key, this.behavior = RiveHitTestBehavior.opaque, }) : name = null, @@ -356,6 +368,7 @@ class RiveAnimationState extends State { enablePointerEvents: _shouldAddHitTesting, behavior: widget.behavior, speedMultiplier: widget.speedMultiplier, + isTouchScrollEnabled: widget.isTouchScrollEnabled, ) : widget.placeHolder ?? const SizedBox(); }