-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
Is your feature request related to a problem? Please describe.
Currently, there is no way to cancel or intercept a gesture. Example: if you set a tap to a child and a parent, both will trigger. Custom gestures must also be handled manually with touch events.
More often than not, when using tap, you just want one tap to trigger. Say you have a card and an user picture inside the card. If you tap anywhere on the card you want to open a URL or show more details about the card, if you tap on the user picture, you want to show the user profile. Currently this completely breaks in {N} and requires workarounds, while works out of the box (and is the default behavior) in other frameworks. This makes developing complex UIs frustrating and actually recudes the usability of TappableSpans #7076, since you can't reliably create a tap for a URL and another for the text, for example. If you check apps like Facebook and Instagram, this is used absolutely everywhere.
Describe the solution you'd like
A Gesture Arena with HitTestBehavior and AllowMultipleGestureRecognizer, similar to Flutter (https://medium.com/flutter-community/flutter-deep-dive-gestures-c16203b3434f), but allowing better bubbling (it seems flutter does not allow for two events of the same kind to be trigger simultaneously, like parent and child tap).
The current {N} implementation is like AllowMultipleGestureRecognizer = true and HitTestBehavior = translucent, but with the added factor that no event is unique. Multiple gestures are recognized loosely and they all bubble to the parents.
Ideally, we should be able to set additional properties to each event of a view (tap, drag, etc), register our own gesture recognizers (like a raw gesture that returns true when recognized), and generate relations between them (custom gesture can't trigger along with child/parent swipe, or child tap has precedence over parent tap or vice-versa, for example).
Honestly, any implementation that allows the user to eat an event type would work better than the current bubbling.
Describe alternatives you've considered
Using booleans to detect child events and gesture detection and discard events when needed. This adds a lot of complexity in the UI code.
Additional context
I've already done some research on the possible implications and implementations. Maybe we could work on a proper API proposal.
iOS:
Currently, we're already running all recognizers simulaneously. This behavior can be expanded to add relations. Since we have owner references, we should be able to get these dynamically for the view (https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/coordinating_multiple_gesture_recognizers)
NativeScript/tns-core-modules/ui/gestures/gestures.ios.ts
Lines 18 to 20 in 7d3f0d9
| public gestureRecognizerShouldRecognizeSimultaneouslyWithGestureRecognizer(gestureRecognizer: UIGestureRecognizer, otherGestureRecognizer: UIGestureRecognizer): boolean { | |
| return true; | |
| } |
Using the same owner references, we could also do the same when deciding for exclusivity, or discarding similar events if that's what the view wants
(https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/coordinating_multiple_gesture_recognizers/preferring_one_gesture_over_another)
Android:
Every touchable view on android returns true on onTouchEvent, so the event is always intercepted by the furthest child in the chain (http://codetheory.in/understanding-android-input-touch-events/). {N} solves this via an undocumented breaking change of NS 5.0 (mistakenly opened as #6606) in which all MotionEvents bubble up to their parents indiscriminately.
NativeScript/tns-core-modules/ui/core/view/view.android.ts
Lines 397 to 399 in cf533a7
| if (this.parent instanceof View) { | |
| this.parent.handleGestureTouch(event); | |
| } |
This behavior can be solved in Android by passing additional data up the chain, like what events were triggered by the MotionEvent. android.view.GestureDetector triggers them synchronously, so we could store a triggered variable that sets itself when an event has been triggered, which is later reset by the GesturesObserver which is handling it.
NativeScript/tns-core-modules/ui/gestures/gestures.android.ts
Lines 333 to 335 in 05c2460
| if (this._simpleGestureDetector) { | |
| this._simpleGestureDetector.onTouchEvent(motionEvent); | |
| } |
if(this._simpleGestureDetector.triggeredEvent) {
triggeredEvents.push(this._simpleGestureDetector.triggeredEvent);
this._simpleGestureDetector.triggeredEvent = null;
}
...
// check triggeredEvents self dependency
// trigger exclusive events (that can't be bubbled up)
// bubble up events that can be bubbled up
// on the topmost (activity) actually trigger all events that were queued
Possible edge case: parent wants to intercept event but child doesn't want bubbling.
Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.