Commit 28ba683
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- android/src/main/java/com/swmansion/gesturehandler/core
1 file changed
+6
-2
lines changedLines changed: 6 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
85 | 85 | | |
86 | 86 | | |
87 | 87 | | |
88 | | - | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
89 | 93 | | |
90 | 94 | | |
91 | 95 | | |
92 | | - | |
| 96 | + | |
93 | 97 | | |
94 | 98 | | |
95 | 99 | | |
| |||
0 commit comments