Skip to content

Commit 28ba683

Browse files
authored
[Android] Fix gestures being able to activate despite their parent already being active (#3095)
## Description This PR fixes invalid activation of gestures nested inside other gestures, like `Pan` gesture nested inside `Native` gesture attached to `ScrollView` Gestures nested inside native elements such as `ScrollView` used to be able to steal pointers from their already active parents. That is no longer possible, already active parents cannot have their active pointers stolen. Related to #2622 ## Test plan - use the attached code in place of `EmptyExample.tsx` - start scrolling the `ScrollView` - while scrolling the `ScrollView`, drag the `Pan` gesture - see how before this PR, the `Pan` gesture activated, and with this PR it doesn't anymore ## Notes - tested this PR on each of the available examples, found no breaking changes - nested gestures may still be run simultaneously if it's explicitly stated using `Gesture.Simultaneous()` or `simultaneousWithExternalGesture()` ## Code <details> <summary> Collapsed code </summary> ```js import React from 'react'; import { StyleSheet, Text, View, ScrollView } from 'react-native'; import { Gesture, GestureDetector, GestureUpdateEvent, PanGestureHandlerEventPayload, } from 'react-native-gesture-handler'; import Animated, { SharedValue, useAnimatedStyle, useSharedValue, withSpring, } from 'react-native-reanimated'; export default function EmptyExample() { const firstExternalPosition = useSharedValue<{ x: number; y: number }>({ x: 0, y: 0, }); const secondExternalPosition = useSharedValue<{ x: number; y: number }>({ x: 0, y: 0, }); const nestedPosition = useSharedValue<{ x: number; y: number }>({ x: 0, y: 0, }); const setter = ( position: SharedValue<{ x: number; y: number; }> ) => { return (event: GestureUpdateEvent<PanGestureHandlerEventPayload>) => { 'worklet'; position.value = { x: event.translationX, y: event.translationY, }; }; }; const resetter = ( position: SharedValue<{ x: number; y: number; }> ) => { return () => { 'worklet'; position.value = { x: withSpring(0), y: withSpring(0), }; }; }; const scrollGesture = Gesture.Native(); const firstExternalPan = Gesture.Pan() .onUpdate(setter(firstExternalPosition)) .onFinalize(resetter(firstExternalPosition)); const secondExternalPan = Gesture.Pan() .onUpdate(setter(secondExternalPosition)) .onFinalize(resetter(secondExternalPosition)); const nestedPan = Gesture.Pan() // .simultaneousWithExternalGesture(scrollGesture) .onUpdate(setter(nestedPosition)) .onFinalize(resetter(nestedPosition)); const firstExternalAnimation = useAnimatedStyle(() => { return { ...styles.box, transform: [ { translateX: firstExternalPosition.value.x }, { translateY: firstExternalPosition.value.y }, ], }; }); const secondExternalAnimation = useAnimatedStyle(() => { return { ...styles.box, transform: [ { translateX: secondExternalPosition.value.x }, { translateY: secondExternalPosition.value.y }, ], }; }); const nestedAnimation = useAnimatedStyle(() => { return { ...styles.box, transform: [ { translateX: nestedPosition.value.x }, { translateY: nestedPosition.value.y }, ], }; }); return ( <View style={styles.container}> <View style={styles.externalContainer}> <GestureDetector gesture={firstExternalPan}> <Animated.View style={firstExternalAnimation}> <Text> Square showcasing 2 disconnected gestures can be moved independantly regardless of changes in this PR, and regardless if one of them is nested inside a native handler. </Text> </Animated.View> </GestureDetector> <GestureDetector gesture={secondExternalPan}> <Animated.View style={secondExternalAnimation}> <Text> Square showcasing 2 disconnected gestures can be moved independantly regardless of changes in this PR, and regardless if one of them is nested inside a native handler. </Text> </Animated.View> </GestureDetector> </View> <View> <GestureDetector gesture={scrollGesture}> <ScrollView style={styles.list}> <GestureDetector gesture={nestedPan}> <Animated.View style={nestedAnimation}> <Text>GH Gesture</Text> </Animated.View> </GestureDetector> {new Array(20) .fill(1) .map((value, index) => value * index) .map((value) => ( <View key={value} style={styles.element}> <Text>Entry no. {value}</Text> </View> ))} </ScrollView> </GestureDetector> </View> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', gap: 20, }, externalContainer: { flexDirection: 'row', gap: 20, marginTop: 300, }, box: { position: 'relative', backgroundColor: 'tomato', width: 200, height: 200, }, list: { width: 200, backgroundColor: 'plum', }, element: { margin: 1, height: 40, backgroundColor: 'orange', }, }); ``` </details>
1 parent 0e7edff commit 28ba683

File tree

1 file changed

+6
-2
lines changed

1 file changed

+6
-2
lines changed

android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt

+6-2
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,15 @@ class GestureHandlerOrchestrator(
8585
private fun hasOtherHandlerToWaitFor(handler: GestureHandler<*>) =
8686
gestureHandlers.any { !isFinished(it.state) && shouldHandlerWaitForOther(handler, it) }
8787

88-
private fun shouldBeCancelledByFinishedHandler(handler: GestureHandler<*>) = gestureHandlers.any { shouldHandlerWaitForOther(handler, it) && it.state == GestureHandler.STATE_END }
88+
private fun shouldBeCancelledByFinishedHandler(handler: GestureHandler<*>) =
89+
gestureHandlers.any { shouldHandlerWaitForOther(handler, it) && it.state == GestureHandler.STATE_END }
90+
91+
private fun shouldBeCancelledByActiveHandler(handler: GestureHandler<*>) =
92+
gestureHandlers.any { handler.hasCommonPointers(it) && it.state == GestureHandler.STATE_ACTIVE && !canRunSimultaneously(handler, it) }
8993

9094
private fun tryActivate(handler: GestureHandler<*>) {
9195
// If we are waiting for a gesture that has successfully finished, we should cancel handler
92-
if (shouldBeCancelledByFinishedHandler(handler)) {
96+
if (shouldBeCancelledByFinishedHandler(handler) || shouldBeCancelledByActiveHandler(handler)) {
9397
handler.cancel()
9498
return
9599
}

0 commit comments

Comments
 (0)