-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Add a new gesture relation - blocksHandlers - working like reversed waitFor
#2627
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
bf59199
5e5aeeb
73ae79b
be695e0
c78c8b9
39581dc
03878fd
6115bd6
5cf17b9
5888b71
235615f
4ecbf39
e3ecbab
9e2a3f2
0385cf3
fc5c940
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| --- | ||
| id: gesture-composition | ||
| title: Composing gestures | ||
| sidebar_label: Composing gestures | ||
| title: Gesture composition & interactions | ||
| sidebar_label: Gesture composition & interactions | ||
| sidebar_position: 4 | ||
| --- | ||
|
|
||
|
|
@@ -200,6 +200,209 @@ function App() { | |
| } | ||
| ``` | ||
|
|
||
| ## Composition vs `simultaneousWithExternalGesture` and `requireExternalGestureToFail` | ||
| # Cross-component interactions | ||
|
|
||
| You may have noticed that gesture composition described above requires you to mount all of the composed gestures under a single `GestureDetector`, effectively attaching them to the same underlying component. If you want to make a relation between gestures that are attached to separate `GestureDetectors`, we have a separate mechanism for that: `simultaneousWithExternalGesture` and `requireExternalGestureToFail` methods that are available on every base gesture. They work exactly the same way as `simultaneousHandlers` and `waitFor` on gesture handlers, that is they just mark the relation between the gestures without joining them into single object. | ||
| You may have noticed that gesture composition described above requires you to mount all of the composed gestures under a single `GestureDetector`, effectively attaching them to the same underlying component. If you wish to customize how gestures interact with each other across multiple components, there are different mechanisms for that. | ||
|
|
||
| ## requireExternalGestureToFail | ||
|
|
||
| `requireExternalGestureToFail` allows for delaying activation of the handler until all handlers passed as arguments to this method fail (or don't begin at all). | ||
j-piasecki marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| For example, you may want to have two nested components, both of them can be tapped by the user to trigger different actions: outer view requires one tap, but the inner one requires 2 taps. If you don't want the first tap on the inner view to activate the outer handler, you must make the outer gesture wait until the inner one fails: | ||
|
|
||
| ```jsx | ||
| import React from 'react'; | ||
| import { View, StyleSheet } from 'react-native'; | ||
| import { | ||
| GestureDetector, | ||
| Gesture, | ||
| GestureHandlerRootView, | ||
| } from 'react-native-gesture-handler'; | ||
|
|
||
| export default function Example() { | ||
| const innerTap = Gesture.Tap() | ||
| .numberOfTaps(2) | ||
| .onStart(() => { | ||
| console.log('inner tap'); | ||
| }); | ||
|
|
||
| const outerTap = Gesture.Tap() | ||
| .onStart(() => { | ||
| console.log('outer tap'); | ||
| }) | ||
| .requireExternalGestureToFail(innerTap); | ||
|
|
||
| return ( | ||
| <GestureHandlerRootView style={styles.container}> | ||
| <GestureDetector gesture={outerTap}> | ||
| <View style={styles.outer}> | ||
| <GestureDetector gesture={innerTap}> | ||
| <View style={styles.inner} /> | ||
| </GestureDetector> | ||
| </View> | ||
| </GestureDetector> | ||
| </GestureHandlerRootView> | ||
| ); | ||
| } | ||
|
|
||
| const styles = StyleSheet.create({ | ||
| container: { | ||
| flex: 1, | ||
| alignItems: 'center', | ||
| justifyContent: 'center', | ||
| }, | ||
| outer: { | ||
| width: 250, | ||
| height: 250, | ||
| backgroundColor: 'lightblue', | ||
| }, | ||
| inner: { | ||
| width: 100, | ||
| height: 100, | ||
| backgroundColor: 'blue', | ||
| alignSelf: 'center', | ||
| }, | ||
| }); | ||
| ``` | ||
|
|
||
| ## requiredToFailByExternalGesture | ||
|
|
||
| `requiredToFailByExternalGesture` works similarily to `requireExternalGestureToFail` but the direction of the relation is reversed - insted of making the gesture it's called on wait for failure of gestures passed as arguments, it's making gestures passed as arguments wait for the gesture it's called on. It's especially usefull for making lists where the `ScrollView` component needs to wait for every gesture underneath it. All that's required is passing a ref of the `ScrollView` to the gesture object, for example: | ||
|
||
|
|
||
| ```jsx | ||
| import React, { useRef } from 'react'; | ||
| import { StyleSheet } from 'react-native'; | ||
| import { | ||
| GestureDetector, | ||
| Gesture, | ||
| GestureHandlerRootView, | ||
| ScrollView, | ||
| } from 'react-native-gesture-handler'; | ||
| import Animated, { | ||
| useSharedValue, | ||
| useAnimatedStyle, | ||
| withTiming, | ||
| } from 'react-native-reanimated'; | ||
|
|
||
| const ITEMS = ['red', 'green', 'blue', 'yellow']; | ||
|
|
||
| function Item({ backgroundColor, scrollRef }) { | ||
| const scale = useSharedValue(1); | ||
| const zIndex = useSharedValue(1); | ||
|
|
||
| const pinch = Gesture.Pinch() | ||
| .requiredToFailByExternalGesture(scrollRef) | ||
| .onBegin(() => { | ||
| zIndex.value = 100; | ||
| }) | ||
| .onChange((e) => { | ||
| scale.value *= e.scaleChange; | ||
| }) | ||
| .onFinalize(() => { | ||
| scale.value = withTiming(1, undefined, (finished) => { | ||
| if (finished) { | ||
| zIndex.value = 1; | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| const animatedStyles = useAnimatedStyle(() => ({ | ||
| transform: [{ scale: scale.value }], | ||
| zIndex: zIndex.value, | ||
| })); | ||
|
|
||
| return ( | ||
| <GestureDetector gesture={pinch}> | ||
| <Animated.View | ||
| style={[ | ||
| { backgroundColor: backgroundColor }, | ||
| styles.item, | ||
| animatedStyles, | ||
| ]} | ||
| /> | ||
| </GestureDetector> | ||
| ); | ||
| } | ||
|
|
||
| export default function Example() { | ||
| const scrollRef = useRef(); | ||
|
|
||
| return ( | ||
| <GestureHandlerRootView style={styles.container}> | ||
| <ScrollView style={styles.container} ref={scrollRef}> | ||
| {ITEMS.map((item) => ( | ||
| <Item backgroundColor={item} key={item} scrollRef={scrollRef} /> | ||
| ))} | ||
| </ScrollView> | ||
| </GestureHandlerRootView> | ||
| ); | ||
| } | ||
|
|
||
| const styles = StyleSheet.create({ | ||
| container: { | ||
| flex: 1, | ||
| }, | ||
| item: { | ||
| flex: 1, | ||
| aspectRatio: 1, | ||
| }, | ||
| }); | ||
| ``` | ||
|
|
||
| ## simultaneousWithExternalGesture | ||
|
|
||
| `simultaneousWithExternalGesture` allows gestures across different components to be recognized simultaneously. We can modify the example from `requireExternalGestureToFail` to showcase this: let's say you have two nested views, again both with tap gesture attached. This time, both of them require one tap, but tapping the inner one should also activate the gesture attached to the outer view: | ||
|
||
|
|
||
| ```jsx | ||
| import React from 'react'; | ||
| import { View, StyleSheet } from 'react-native'; | ||
| import { | ||
| GestureDetector, | ||
| Gesture, | ||
| GestureHandlerRootView, | ||
| } from 'react-native-gesture-handler'; | ||
|
|
||
| export default function Example() { | ||
| const innerTap = Gesture.Tap() | ||
| .onStart(() => { | ||
| console.log('inner tap'); | ||
| }); | ||
|
|
||
| const outerTap = Gesture.Tap() | ||
| .onStart(() => { | ||
| console.log('outer tap'); | ||
| }) | ||
| .simultaneousWithExternalGesture(innerTap); | ||
|
|
||
| return ( | ||
| <GestureHandlerRootView style={styles.container}> | ||
| <GestureDetector gesture={outerTap}> | ||
| <View style={styles.outer}> | ||
| <GestureDetector gesture={innerTap}> | ||
| <View style={styles.inner} /> | ||
| </GestureDetector> | ||
| </View> | ||
| </GestureDetector> | ||
| </GestureHandlerRootView> | ||
| ); | ||
| } | ||
|
|
||
| const styles = StyleSheet.create({ | ||
| container: { | ||
| flex: 1, | ||
| alignItems: 'center', | ||
| justifyContent: 'center', | ||
| }, | ||
| outer: { | ||
| width: 250, | ||
| height: 250, | ||
| backgroundColor: 'lightblue', | ||
| }, | ||
| inner: { | ||
| width: 100, | ||
| height: 100, | ||
| backgroundColor: 'blue', | ||
| alignSelf: 'center', | ||
| }, | ||
| }); | ||
| ``` | ||
Uh oh!
There was an error while loading. Please reload this page.