Reputation: 530
My <ScrollView>
has multiple children with custom animated component that is based on <TapGestureHandler
. The problem is that the animation turns on (scale and ripple) even when I scroll and it shouldn't. I want to block the animation on scrolling
I have tried:
onScrollBeginDrag
and onScrollEndDrag
- not liable, causes performance drop and too much duplicationdelayPressIn
) - does not workexample of the component:
const tapGestureEvent
= useAnimatedGestureHandler<TapGestureHandlerGestureEvent>({
onStart: (tapEvent) => {
const layout = measure(aRef)
width.value = layout.width
height.value = layout.height
centerX.value = tapEvent.x
centerY.value = tapEvent.y
pressed.value = true
rippleOpacity.value = 1
scale.value = 0
scale.value = withTiming(1, { duration: RIPPLE_DURATION })
},
onActive: () => {
runOnJS(onPressTap)()
},
onFinish: () => {
rippleOpacity.value = withTiming(0)
pressed.value = false
},
})
return (
<GestureHandlerRootView>
<Animated.View ref={aRef} style={disabledScaling ? {} : animatedStyle}>
<LinearGradient
start={LINEAR_GRADIENT_START}
end={LINEAR_GRADIENT_END}
colors={gradientColors}
style={[s.linearGradient, gradientStyle]}
>
<TapGestureHandler enabled={!disabled} onGestureEvent={tapGestureEvent}>
<Animated.View style={[style, s.animated]}>
{children}
<Animated.View style={rStyle} />
</Animated.View>
</TapGestureHandler>
</LinearGradient>
</Animated.View>
</GestureHandlerRootView>
)
I was looking for some kind of prop that could handle the scroll somehow, but did not find any solution for this.
Upvotes: 0
Views: 973
Reputation: 88
Gestures are indeed very tricky to configure! Here are few suggestions on how I would solve this.
onStart
triggers immediately after the user starts interacting with a Touchable. If you want to show animations only when user explicitly selects the item, you can adjust the config for touchable this way:
const tapGestureEvent
= useAnimatedGestureHandler<TapGestureHandlerGestureEvent>({
onEnd: (tapEvent) => { // <=========== use onEnd
const layout = measure(aRef)
width.value = layout.width
height.value = layout.height
centerX.value = tapEvent.x
centerY.value = tapEvent.y
pressed.value = true
rippleOpacity.value = 1
scale.value = 0
scale.value = withTiming(1, { duration: RIPPLE_DURATION })
/* moved logic from onFinish here, because onFinish is also a general event */
rippleOpacity.value = withTiming(0)
pressed.value = false
/* same for onActive */
runOnJS(onPressTap)()
},
})
onEnd
triggers when the action is finished and the responder is marked as the main receiver, there is a nice explanation of the whole interaction flow here: https://docs.swmansion.com/react-native-gesture-handler/docs/under-the-hood/states-events#state-flows
onResponder...
props of react-native
View
. RN has a full description in docs here: https://reactnative.dev/docs/gesture-responder-system
Like this:
const showPressIn = (x1: number, y1: number, cb: () => void) => {
aRef.current?.measure((layoutX, layoutY, layoutWidth, layouthHeight) => {
width.value = layoutWidth
height.value = layouthHeight
centerX.value = layoutX
centerY.value = layoutY
pressed.value = true
rippleOpacity.value = 1
scale.value = 0
scale.value = withTiming(1, { duration: RIPPLE_DURATION })
cb()
})
}
const showPressOut = () => {
const timer = setTimeout(() => {
InteractionManager.runAfterInteractions(() => {
rippleOpacity.value = withTiming(0)
pressed.value = false
onTap()
clearTimeout(timer)
})
}, 200)
}
const onResponderRelease = (e: GestureResponderEvent) => {
showPressIn(e.nativeEvent.locationX, e.nativeEvent.locationY, showPressOut)
}
const ViewProps: ViewProps = {
onResponderRelease, // <=== this called on interaction finish
onStartShouldSetResponder: () => true, // <=== this enables responding in the <View/>
}
return (
<Animated.View collapsable={false} ref={aRef} style={disabledScaling ? {} : animatedStyle}>
<LinearGradient
start={LINEAR_GRADIENT_START}
end={LINEAR_GRADIENT_END}
colors={gradientColors}
style={[s.linearGradient, gradientStyle]}
>
<View {...ViewProps}>
<Animated.View style={[style, s.animated]}>
{children}
<Animated.View style={rStyle} />
</Animated.View>
</View>
</LinearGradient>
</Animated.View>
)
But this solution is more buggy since the coordinates do not match the real ones. So your animation will not be centered in the right position, here is an issue that's closed but not solved: https://github.com/facebook/react-native/issues/31945. There are some screenshots of the current behavior in RN.
Also, it'll require hacks in measuring the View and timeouts for animations, InteractionManager
won't give the desired behavior, since those animations are not really the events it's built for.
Upvotes: 1