-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Description
Description
Continuing my adventures with these APIs.
I have three gestures:
- A top-level Pan handler for a swipeable drawer.
- A Pager. The Pager has multiple pages.
- A nested ScrollView on Pager's pages.
Here's how I want them to work:
- The nested ScrollView should take precedence over anything else.
- Then, the Pager.
- Then (only if we're on the first page), the Drawer.
This is proving quite tricky.
Example 1: Broken (requireExternalGestureToFail in parent)
Let's start with this baseline code:
import {useState} from 'react'
import {ScrollView, Text, View} from 'react-native'
import {
Gesture,
GestureDetector,
GestureHandlerRootView,
} from 'react-native-gesture-handler'
import PagerView from 'react-native-pager-view'
import Animated, {
useAnimatedStyle,
useSharedValue,
} from 'react-native-reanimated'
export default function App() {
const val = useSharedValue(0)
const [swipeEnabled, setSwipeEnabled] = useState(true)
const innerNative = Gesture.Native()
let pan = Gesture.Pan()
.requireExternalGestureToFail(innerNative)
.onBegin(() => {
'worklet'
console.log('begin')
val.set(0)
})
.onUpdate(e => {
'worklet'
console.log('update')
val.set(e.translationX)
})
.onEnd(() => {
'worklet'
console.log('end')
val.set(0)
})
.onFinalize(() => {
'worklet'
console.log('finalize')
val.set(0)
})
if (swipeEnabled) {
pan = pan.failOffsetX(-1).activeOffsetX(5)
} else {
pan = pan.failOffsetX([0, 0]).failOffsetY([0, 0])
}
const style = useAnimatedStyle(() => {
return {
flex: 1,
transform: [
{
translateX: val.value,
},
],
}
})
return (
<GestureHandlerRootView>
<GestureDetector gesture={pan}>
<Animated.View style={style}>
<Pager pan={pan} setSwipeEnabled={setSwipeEnabled}>
<InnerScrollView pan={pan} innerNative={innerNative} />
</Pager>
</Animated.View>
</GestureDetector>
</GestureHandlerRootView>
)
}
function Pager({children, pan, setSwipeEnabled}) {
const native = Gesture.Native().requireExternalGestureToFail(pan)
return (
<GestureDetector gesture={native}>
<PagerView
overdrag={true}
initialPage={0}
style={{
flex: 1,
backgroundColor: 'green',
}}
onPageSelected={e => {
setSwipeEnabled(e.nativeEvent.position === 0)
}}>
{children}
{children}
{children}
</PagerView>
</GestureDetector>
)
}
function InnerScrollView({pan, innerNative}) {
return (
<View
style={{
paddingTop: 200,
alignItems: 'center',
}}>
<GestureDetector gesture={innerNative}>
<ScrollView
horizontal
pagingEnabled
style={{
width: 300,
backgroundColor: 'yellow',
height: 200,
}}>
<Text
style={{
width: 1000,
}}>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
27 28 29 30 31 32 33 34 35 36 37 38 39 40
</Text>
</ScrollView>
</GestureDetector>
</View>
)
}Here, I'm declaring that the inner ScrollView should take precedence over Pan:
const innerNative = Gesture.Native()
let pan = Gesture.Pan()
.requireExternalGestureToFail(innerNative)However, this doesn't work. Neither on iOS nor on Android:
bug-1.mov
Example 2: Broken (blocksExternalGesture in parent)
Okay, now let's try to express the relationship in the reverse:
let pan = Gesture.Pan()
// ...
const innerNative = Gesture.Native().blocksExternalGesture(pan)This also doesn't work, neither on iOS nor on Android:
bug-2.mov
Example 3: Works (blocksExternalGesture in child)
Now let's move the innerNative declaration down the component chain.
We'll remove it here:
let pan = Gesture.Pan()
// ...
// ------- REMOVED IT HERE: -------
// const innerNative = Gesture.Native().blocksExternalGesture(pan)and add it here:
function InnerScrollView({pan}) {
// ------- ADDED IT HERE: -------
const innerNative = Gesture.Native().blocksExternalGesture(pan)Now it works!
finally.mov
Conclusions
To sum up:
requireExternalGestureToFailin the parent didn't work (both platforms)blocksExternalGesturein the parent didn't work either (both platforms)blocksExternalGesturein the child did work (both platforms)
This is with 2.20.2 + #3322 applied.
I wanted to test this with 2.22.0 but I couldn't get even the most basic view running locally because of some error about "unknown view tag" while rendering GestureDetector.
That said, this Snack (Example 1) is running 2.22.0-rc.1 and does reproduce the first issue, at least on Android. And this Snack (Example 2) reproduces the second issue. So I think, at least for Android, the bug is present in the latest.
Steps to reproduce
- See above
Snack or a link to a repository
https://snack.expo.dev/mzHvgIje0N9z2MuCjQfqW?platform=android
Gesture Handler version
2.22.0-rc.1
React Native version
0.76.3
Platforms
Android, iOS
JavaScript runtime
Hermes
Workflow
Expo bare workflow
Architecture
Paper (Old Architecture)
Build type
Debug mode
Device
Android emulator
Device model
No response
Acknowledgements
Yes