feat: add isTouchScrollEnabled

Resolves https://github.com/rive-app/rive-flutter/issues/436, and
- https://rive.app/community/forums/support/fsnLLkXUaA62/flutter-scrolling-not-working-as-expected-on-mobile/ft3UcorwBVAf

Our web runtime has a `isTouchScrollEnabled`. This reproduces that behaviour.

```
  /// 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`.
```

I made some TODOs in the code for future considerations. We might want to opt in to give users more control over which gestures should be registered, and we can potentially be smart about doing this conditionally, depending on what the Rive graphic allows you to do. But this requires more investigation.

https://github.com/user-attachments/assets/81119bed-cb8a-4672-9559-d1c85832bad9

Diffs=
8d1fdd16ad feat: add isTouchScrollEnabled (#8651)

Co-authored-by: Gordon <pggordonhayes@gmail.com>
This commit is contained in:
HayesGordon
2024-11-27 19:17:23 +00:00
parent 1848316d20
commit 16d549f77c
6 changed files with 64 additions and 13 deletions

View File

@ -1 +1 @@
7986d64d8371531716ea3f038dcbec5da187e6cd
8d1fdd16ad196c3acca18faa89db2e1f4ef94365

View File

@ -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).

View File

@ -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();
}

View File

@ -466,8 +466,6 @@ class StateMachineController extends RiveAnimationController<CoreContext>
late CoreContext core;
final _recognizer = ImmediateMultiDragGestureRecognizer();
@override
bool init(CoreContext core) {
this.core = core;
@ -734,9 +732,6 @@ class StateMachineController extends RiveAnimationController<CoreContext>
hitEvent: ListenerType.down,
pointerEvent: event,
);
if (hitResult != HitResult.none) {
_recognizer.addPointer(event);
}
return hitResult;
}

View File

@ -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

View File

@ -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<RiveAnimation> {
enablePointerEvents: _shouldAddHitTesting,
behavior: widget.behavior,
speedMultiplier: widget.speedMultiplier,
isTouchScrollEnabled: widget.isTouchScrollEnabled,
)
: widget.placeHolder ?? const SizedBox();
}