Skip to content

Commit d798b69

Browse files
authored
Adapt NativeViewGestureHandler to NativeDetector (#3638)
## Description This PR brings support for `NativeViewGestureHandler` into `NativeDetector`. It does so, by attaching handler into child instead of detector view. ## Status - ### Android ✅ - ### iOS ✅ ## Test plan <details> <summary>Tested on the following code:</summary> ```jsx import * as React from 'react'; import { StyleSheet, Text, View, ScrollView } from 'react-native'; import { GestureHandlerRootView, NativeDetector, useGesture, } from 'react-native-gesture-handler'; export default function App() { const items = Array.from({ length: 30 }, (_, index) => `Item ${index + 1}`); const gesture = useGesture('NativeViewGestureHandler', { onBegin: (e) => { console.log('onBegin', e); }, onStart: (e) => { console.log('onStart', e); }, onEnd: (e) => { console.log('onEnd', e); }, onFinalize: (e) => { console.log('onFinalize', e); }, onTouchesDown: (e) => { console.log('onTouchesDown', e); }, onTouchesMove: (e) => { console.log('onTouchesMove', e); }, onTouchesCancelled: (e) => { console.log('onTouchesCancelled', e); }, onTouchesUp: (e) => { console.log('onTouchesUp', e); }, onUpdate: (e) => { console.log('onUpdate', e); }, onChange: (e) => { console.log('onChange', e); }, onCancel: (e) => { console.log('onCancel', e); }, }); return ( <GestureHandlerRootView style={{ flex: 1, backgroundColor: 'white', paddingTop: 8 }}> <NativeDetector gesture={gesture}> <ScrollView style={styles.scrollView}> {items.map((item, index) => ( <View key={index} style={styles.item}> <Text style={styles.text}>{item}</Text> </View> ))} </ScrollView> </NativeDetector> </GestureHandlerRootView> ); } const styles = StyleSheet.create({ scrollView: { backgroundColor: 'lightgrey', marginHorizontal: 20, }, item: { flexDirection: 'row', justifyContent: 'center', alignItems: 'center', padding: 20, margin: 2, backgroundColor: 'white', borderRadius: 10, }, text: { fontSize: 20, color: 'black', }, }); ``` </details>
1 parent 16d9071 commit d798b69

14 files changed

+246
-59
lines changed

packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import com.facebook.react.bridge.WritableArray
1818
import com.facebook.react.uimanager.PixelUtil
1919
import com.swmansion.gesturehandler.BuildConfig
2020
import com.swmansion.gesturehandler.RNSVGHitTester
21+
import com.swmansion.gesturehandler.react.RNGestureHandlerDetectorView
2122
import com.swmansion.gesturehandler.react.RNGestureHandlerTouchEvent
2223
import com.swmansion.gesturehandler.react.eventbuilders.GestureHandlerEventDataBuilder
2324
import java.lang.IllegalStateException
@@ -30,6 +31,22 @@ open class GestureHandler {
3031
var tag = 0
3132
var view: View? = null
3233
private set
34+
val viewForEvents: RNGestureHandlerDetectorView
35+
get() {
36+
assert(actionType == ACTION_TYPE_NATIVE_DETECTOR) {
37+
"[react-native-gesture-handler] `viewForEvents` can only be used with NativeDetector."
38+
}
39+
40+
val detector = if (this is NativeViewGestureHandler) this.view?.parent else view
41+
42+
if (detector !is RNGestureHandlerDetectorView) {
43+
throw Error(
44+
"[react-native-gesture-handler] Expected RNGestureHandlerDetectorView to be the target for the event.",
45+
)
46+
}
47+
48+
return detector
49+
}
3350
var state = STATE_UNDETERMINED
3451
private set
3552
var x = 0f
@@ -823,6 +840,8 @@ open class GestureHandler {
823840
}
824841
}
825842

843+
open fun wantsToAttachDirectlyToView() = false
844+
826845
override fun toString(): String {
827846
val viewString = if (view == null) null else view!!.javaClass.simpleName
828847
return this.javaClass.simpleName + "@[" + tag + "]:" + viewString

packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ class NativeViewGestureHandler : GestureHandler() {
167167
this.hook = defaultHook
168168
}
169169

170+
override fun wantsToAttachDirectlyToView() = true
171+
170172
class Factory : GestureHandler.Factory<NativeViewGestureHandler>() {
171173
override val type = NativeViewGestureHandler::class.java
172174
override val name = "NativeViewGestureHandler"

packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.swmansion.gesturehandler.react
22

33
import android.content.Context
4+
import android.view.View
45
import com.facebook.react.bridge.ReadableArray
56
import com.facebook.react.uimanager.ThemedReactContext
67
import com.facebook.react.uimanager.UIManagerHelper
@@ -12,7 +13,8 @@ class RNGestureHandlerDetectorView(context: Context) : ReactViewGroup(context) {
1213
private val reactContext: ThemedReactContext
1314
get() = context as ThemedReactContext
1415
private var handlersToAttach: List<Int>? = null
15-
private var attachedHandlers = listOf<Int>()
16+
private var nativeHandlersToAttach: MutableSet<Int> = mutableSetOf()
17+
private var attachedHandlers: MutableSet<Int> = mutableSetOf()
1618
private var moduleId: Int = -1
1719

1820
fun setHandlerTags(handlerTags: ReadableArray?) {
@@ -35,6 +37,26 @@ class RNGestureHandlerDetectorView(context: Context) : ReactViewGroup(context) {
3537
handlersToAttach = null
3638
}
3739

40+
private fun shouldAttachGestureToChildView(tag: Int): Boolean {
41+
val registry = RNGestureHandlerModule.registries[moduleId]
42+
?: throw Exception("Tried to access a non-existent registry")
43+
44+
return registry.getHandler(tag)?.wantsToAttachDirectlyToView() ?: false
45+
}
46+
47+
// We override this `addView` because it is called inside `addView(child: View?, index: Int)`
48+
override fun addView(child: View, index: Int, params: LayoutParams?) {
49+
super.addView(child, index, params)
50+
51+
tryAttachHandlerToChildView(child.id)
52+
}
53+
54+
override fun removeViewAt(index: Int) {
55+
detachNativeGestureHandlers()
56+
57+
super.removeViewAt(index)
58+
}
59+
3860
private fun attachHandlers(newHandlers: List<Int>) {
3961
val registry = RNGestureHandlerModule.registries[moduleId]
4062
?: throw Exception("Tried to access a non-existent registry")
@@ -50,15 +72,53 @@ class RNGestureHandlerDetectorView(context: Context) : ReactViewGroup(context) {
5072
}
5173

5274
for (entry in changes) {
75+
val tag = entry.key
76+
5377
if (entry.value == GestureHandlerMutation.Attach) {
54-
// TODO: Attach to the child when attached gesture is a NativeGestureHandler, track children changes then
55-
registry.attachHandlerToView(
56-
entry.key,
57-
this.id,
58-
GestureHandler.ACTION_TYPE_NATIVE_DETECTOR,
59-
)
78+
// It might happen that `attachHandlers` will be called before children are added into view hierarchy. In that case we cannot
79+
// attach `NativeViewGestureHandlers` here and we have to do it in `addView` method.
80+
if (shouldAttachGestureToChildView(tag)) {
81+
nativeHandlersToAttach.add(tag)
82+
} else {
83+
registry.attachHandlerToView(tag, this.id, GestureHandler.ACTION_TYPE_NATIVE_DETECTOR)
84+
85+
attachedHandlers.add(tag)
86+
}
6087
} else if (entry.value == GestureHandlerMutation.Detach) {
61-
registry.detachHandler(entry.key)
88+
registry.detachHandler(tag)
89+
attachedHandlers.remove(tag)
90+
}
91+
}
92+
93+
// This covers the case where `NativeViewGestureHandlers` are attached after child views were created.
94+
val child = getChildAt(0)
95+
96+
if (child != null) {
97+
tryAttachHandlerToChildView(child.id)
98+
}
99+
}
100+
101+
private fun tryAttachHandlerToChildView(childId: Int) {
102+
val registry = RNGestureHandlerModule.registries[moduleId]
103+
?: throw Exception("Tried to access a non-existent registry")
104+
105+
for (tag in nativeHandlersToAttach) {
106+
registry.attachHandlerToView(tag, childId, GestureHandler.ACTION_TYPE_NATIVE_DETECTOR)
107+
108+
attachedHandlers.add(tag)
109+
}
110+
111+
nativeHandlersToAttach.clear()
112+
}
113+
114+
private fun detachNativeGestureHandlers() {
115+
val registry = RNGestureHandlerModule.registries[moduleId]
116+
?: throw Exception("Tried to access a non-existent registry")
117+
118+
for (tag in attachedHandlers) {
119+
if (shouldAttachGestureToChildView(tag)) {
120+
registry.detachHandler(tag)
121+
attachedHandlers.remove(tag)
62122
}
63123
}
64124
}

packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerEvent.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,14 @@ class RNGestureHandlerEvent private constructor() : Event<RNGestureHandlerEvent>
3333
dataBuilder: GestureHandlerEventDataBuilder<T>,
3434
useNativeAnimatedName: Boolean,
3535
) {
36-
val view = handler.view!!
36+
val view = if (handler.actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR) {
37+
handler.viewForEvents!!
38+
} else {
39+
handler.view!!
40+
}
41+
3742
super.init(UIManagerHelper.getSurfaceId(view), view.id)
43+
3844
this.actionType = actionType
3945
this.dataBuilder = dataBuilder
4046
this.useTopPrefixedName = useNativeAnimatedName

packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerEventDispatcher.kt

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -78,26 +78,23 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React
7878
sendEventForDeviceEvent(RNGestureHandlerEvent.EVENT_NAME, data)
7979
}
8080
GestureHandler.ACTION_TYPE_NATIVE_DETECTOR -> {
81-
val view = handler.view
82-
83-
if (view is RNGestureHandlerDetectorView) {
84-
if (handler.dispatchesAnimatedEvents) {
85-
val animatedEvent = RNGestureHandlerEvent.obtain(
86-
handler,
87-
handler.actionType,
88-
handlerFactory.createEventBuilder(handler),
89-
true,
90-
)
91-
view.dispatchEvent(animatedEvent)
92-
}
93-
94-
val event = RNGestureHandlerEvent.obtain(
81+
if (handler.dispatchesAnimatedEvents) {
82+
val animatedEvent = RNGestureHandlerEvent.obtain(
9583
handler,
9684
handler.actionType,
9785
handlerFactory.createEventBuilder(handler),
86+
true,
9887
)
99-
view.dispatchEvent(event)
88+
handler.viewForEvents!!.dispatchEvent(animatedEvent)
10089
}
90+
91+
val event = RNGestureHandlerEvent.obtain(
92+
handler,
93+
handler.actionType,
94+
handlerFactory.createEventBuilder(handler),
95+
)
96+
97+
handler.viewForEvents!!.dispatchEvent(event)
10198
}
10299
}
103100
}
@@ -156,17 +153,15 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React
156153
}
157154

158155
GestureHandler.ACTION_TYPE_NATIVE_DETECTOR -> {
159-
val view = handler.view
160-
if (view is RNGestureHandlerDetectorView) {
161-
val event = RNGestureHandlerStateChangeEvent.obtain(
162-
handler,
163-
newState,
164-
oldState,
165-
handler.actionType,
166-
handlerFactory.createEventBuilder(handler),
167-
)
168-
view.dispatchEvent(event)
169-
}
156+
val event = RNGestureHandlerStateChangeEvent.obtain(
157+
handler,
158+
newState,
159+
oldState,
160+
handler.actionType,
161+
handlerFactory.createEventBuilder(handler),
162+
)
163+
164+
handler.viewForEvents!!.dispatchEvent(event)
170165
}
171166
}
172167
}
@@ -199,11 +194,9 @@ class RNGestureHandlerEventDispatcher(private val reactApplicationContext: React
199194
sendEventForDeviceEvent(RNGestureHandlerEvent.EVENT_NAME, data)
200195
}
201196
GestureHandler.ACTION_TYPE_NATIVE_DETECTOR -> {
202-
val view = handler.view
203-
if (view is RNGestureHandlerDetectorView) {
204-
val event = RNGestureHandlerTouchEvent.obtain(handler, handler.actionType)
205-
view.dispatchEvent(event)
206-
}
197+
val event = RNGestureHandlerTouchEvent.obtain(handler, handler.actionType)
198+
199+
handler.viewForEvents!!.dispatchEvent(event)
207200
}
208201
}
209202
}

packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerStateChangeEvent.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,14 @@ class RNGestureHandlerStateChangeEvent private constructor() : Event<RNGestureHa
2727
actionType: Int,
2828
dataBuilder: GestureHandlerEventDataBuilder<T>,
2929
) {
30-
val view = handler.view!!
30+
val view = if (handler.actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR) {
31+
handler.viewForEvents!!
32+
} else {
33+
handler.view!!
34+
}
35+
3136
super.init(UIManagerHelper.getSurfaceId(view), view.id)
37+
3238
this.dataBuilder = dataBuilder
3339
this.newState = newState
3440
this.oldState = oldState

packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerTouchEvent.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,14 @@ class RNGestureHandlerTouchEvent private constructor() : Event<RNGestureHandlerT
1313
private var actionType = GestureHandler.ACTION_TYPE_JS_FUNCTION_NEW_API
1414

1515
private fun <T : GestureHandler> init(handler: T, actionType: Int) {
16-
val view = handler.view!!
16+
val view = if (handler.actionType == GestureHandler.ACTION_TYPE_NATIVE_DETECTOR) {
17+
handler.viewForEvents!!
18+
} else {
19+
handler.view!!
20+
}
21+
1722
super.init(UIManagerHelper.getSurfaceId(view), view.id)
23+
1824
extraData = createEventData(handler)
1925
coalescingKey = handler.eventCoalescingKey
2026
this.actionType = actionType

packages/react-native-gesture-handler/apple/Handlers/RNNativeViewHandler.mm

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,11 @@ - (void)handleTouchCancel:(UIView *)sender forEvent:(UIEvent *)event
229229
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:NO withPointerType:_pointerType]];
230230
}
231231

232+
- (BOOL)wantsToAttachDirectlyToView
233+
{
234+
return YES;
235+
}
236+
232237
#else
233238

234239
- (RNGestureHandlerEventExtraData *)eventExtraData:(RNDummyGestureRecognizer *)recognizer

packages/react-native-gesture-handler/apple/RNGestureHandler.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,10 @@
4141
- (void)sendEvent:(nonnull RNGestureHandlerStateChange *)event
4242
withActionType:(RNGestureHandlerActionType)actionType
4343
forAnimated:(BOOL)forAnimated
44-
forRecognizer:(UIGestureRecognizer *)recognizer;
44+
forView:(nonnull RNGHUIView *)detectorView;
4545

46-
- (void)sendNativeTouchEventForGestureHandler:(RNGestureHandler *)handler withPointerType:(NSInteger)pointerType;
46+
- (void)sendNativeTouchEventForGestureHandler:(nonnull RNGestureHandler *)handler
47+
withPointerType:(NSInteger)pointerType;
4748

4849
@end
4950

@@ -100,6 +101,8 @@
100101
- (void)sendEvent:(nonnull RNGestureHandlerStateChange *)event;
101102
- (void)sendTouchEventInState:(RNGestureHandlerState)state forViewWithTag:(nonnull NSNumber *)reactTag;
102103
- (nullable RNGHUIScrollView *)retrieveScrollView:(nonnull RNGHUIView *)view;
104+
- (nonnull RNGHUIView *)findViewForEvents;
105+
- (BOOL)wantsToAttachDirectlyToView;
103106

104107
#if !TARGET_OS_OSX
105108
- (BOOL)isUIScrollViewPanGestureRecognizer:(nonnull UIGestureRecognizer *)gestureRecognizer;

packages/react-native-gesture-handler/apple/RNGestureHandler.mm

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ - (void)handleGesture:(UIGestureRecognizer *)recognizer inState:(RNGestureHandle
307307
RNGestureHandlerEventExtraData *eventData = [self eventExtraData:recognizer];
308308

309309
NSNumber *tag = [self chooseViewForInteraction:recognizer].reactTag;
310+
310311
if (tag == nil && _actionType == RNGestureHandlerActionTypeNativeDetector) {
311312
tag = @(recognizer.view.tag);
312313
}
@@ -371,17 +372,25 @@ - (void)sendEventsInState:(RNGestureHandlerState)state
371372
}
372373
}
373374

375+
- (RNGHUIView *)findViewForEvents
376+
{
377+
return
378+
[self isKindOfClass:[RNNativeViewGestureHandler class]] && _actionType == RNGestureHandlerActionTypeNativeDetector
379+
? self.recognizer.view.superview
380+
: self.recognizer.view;
381+
}
382+
374383
- (void)sendEvent:(RNGestureHandlerStateChange *)event
375384
{
376385
[self.emitter sendEvent:event
377386
withActionType:self.actionType
378387
forAnimated:_dispatchesAnimatedEvents
379-
forRecognizer:self.recognizer];
388+
forView:[self findViewForEvents]];
380389
}
381390

382391
- (void)sendTouchEventInState:(RNGestureHandlerState)state forViewWithTag:(NSNumber *)reactTag
383392
{
384-
if (self.actionType == RNGestureHandlerActionTypeNativeDetector) {
393+
if (_actionType == RNGestureHandlerActionTypeNativeDetector) {
385394
[self.emitter sendNativeTouchEventForGestureHandler:self withPointerType:_pointerType];
386395
} else {
387396
id extraData = [RNGestureHandlerEventExtraData forEventType:_pointerTracker.eventType
@@ -399,7 +408,7 @@ - (void)sendTouchEventInState:(RNGestureHandlerState)state forViewWithTag:(NSNum
399408
[self.emitter sendEvent:event
400409
withActionType:self.actionType
401410
forAnimated:_dispatchesAnimatedEvents
402-
forRecognizer:self.recognizer];
411+
forView:self.recognizer.view];
403412
}
404413
}
405414

@@ -679,4 +688,9 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceive
679688
return YES;
680689
}
681690

691+
- (BOOL)wantsToAttachDirectlyToView
692+
{
693+
return NO;
694+
}
695+
682696
@end

0 commit comments

Comments
 (0)