I have a use case where I need to know about drag events but don't actually want to animate the direct child of the PanGestureHandler.
For example, the following code for a "Draggable" component does not work:
<View>
{/* Note how the Animated.View is outside PanGestureHandler */}
<Animated.View
style={[
{
transform: [
{ translateX: this._translateX },
{ translateY: this._translateY },
],
},
]}
>
{/* Clone on drag */}
{this.state.active && this.props.children}
</Animated.View>
{/* Listen for drag events */}
<PanGestureHandler
onGestureEvent={this._onGestureEvent}
onHandlerStateChange={this._onHandlerStateChange}>
{this.props.children}
</PanGestureHandler>
</View>
Is this simply not possible?
Hey @scottmas! Thanks for reporting!
I haven't yet tried to replicate this but my first guess is that you actually should try adding a View as a direct children of PanGestureHandler and use it to wrap {this.props.children}. The reason is that gesture handlers don't actually map to native views in react native and so they need to reference something in order for it to receive events.
Let me know if that changes anything. Otherwise I can find some time later this week to help debug. This usecase should definitely be possible.
Sadly, that doesn't fix it. I get an error when I try to drag the element in either case: "Expected onGestureHandlerEvent listener to be a function, instead got a type function". And it works perfectly if I just have the Animated.View as the direct child of PanGestureHandler.
My constructor and handler functions are extremely standard.
Narrowed down the problem. Apparently Animated.event(...) doesn't always return a function. So I'm not sure what the upstream React Native is smoking for that to be the case - doesn't make sense to me. But if I set the onGestureEvent property of PanGestureHandler to look like so, it doesn't blow up (although it still doesn't quite work properly):
onGestureEvent={(e) => typeof this._onGestureEvent === 'function' && this._onGestureEvent(e)}
Thanks for investigating it. What if instead putting View as a direct child of handler component you put Animated.View?
Yep, that works. But only if the Animated.View actually animates on drag.
You trying this on iOS or Android?
Same behavior on both, at least the Android emulator.
Would you be able to extract some minimal working component that illustrate that issue? That would help me a ton in reproducing and debugging this issue
Sure. Using Expo:
import React, { Component, Text } from 'react';
import { View, StyleSheet, Animated } from 'react-native';
import { Constants, DangerZone } from 'expo';
const { GestureHandler } = DangerZone;
const { PanGestureHandler, State } = GestureHandler;
export default class App extends Component {
constructor(props) {
super(props);
this.state = {active: false};
this._translateX = new Animated.Value(0);
this._translateY = new Animated.Value(0);
this._onGestureEvent = Animated.event(
[
{
nativeEvent: {
translationX: this._translateX,
translationY: this._translateY,
},
},
],
{ useNativeDriver: true }
);
}
_onHandlerStateChange = event => {
const newState = event.nativeEvent.state;
const oldState = event.nativeEvent.oldState;
const active = newState === State.ACTIVE;
if(this.state.active !== active){
this.setState({
active
});
}
if (oldState === State.ACTIVE) {
Animated.spring(this._translateX, {toValue: 0, useNativeDriver: true}).start();
Animated.spring(this._translateY, {toValue: 0, useNativeDriver: true}).start();
}
};
render() {
return (
<View style={styles.container}>
{this.state.active &&
<Animated.View
style={[
styles.redBox,
{
transform: [
{ translateX: this._translateX },
{ translateY: this._translateY },
],
},
]}
>
</Animated.View>
}
<PanGestureHandler
onGestureEvent={this._onGestureEvent}
onHandlerStateChange={this._onHandlerStateChange}>
<View style={styles.redBox}/>
</PanGestureHandler>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
},
redBox: {
width: 200,
height: 200,
backgroundColor: 'red'
}
});
I've confirmed that it works as expected when using a vanilla gesture event handler and calling setValue instead of using Animated.Event. AKA:
this._onGestureEvent = (e) => {
this._translateX.setValue(e.nativeEvent.translationX);
this._translateY.setValue(e.nativeEvent.translationY)
}
But this sadly doesn't let me do useNativeDriver and performance does suffer a bit.
Hey @scottmas sorry for late response on that.
I just tried you example app two comments from now and just made one change in it – replaced View that's inside PanGestureHandler with Animated.View:
***************
*** 66,70 ****
onGestureEvent={this._onGestureEvent}
onHandlerStateChange={this._onHandlerStateChange}>
! <View style={styles.redBox} />
</PanGestureHandler>
</View>
--- 66,70 ----
onGestureEvent={this._onGestureEvent}
onHandlerStateChange={this._onHandlerStateChange}>
! <Animated.View style={styles.redBox} />
</PanGestureHandler>
</View>
Panning on the red box makes the other box move now. Not sure if that's what you'd expect to happen
Huh, yes, that does work. That's a good work around. So it looks like PanGestureHandler does require a child Animated.View right now.
Yes, apparently. Sorry for the trouble this limitation has caused. I’ll update documentation to better explain what can be put in as a child of handler component
Seems like a pretty innocuous limitation. It's a bit weird in this case to have a Animated view without any animated properties, but doesn't affect anything.
Should I close this? I looked for a natural place to insert this into the documentation and nothing felt like the right place. All the examples have PanGestureHandler with a child Animated.View so I'm guessing this limitation in practice shouldn't come up too much.
Yes, thanks I think once I have time to finish new docs this information is going to be included
Thanks for reporting this issue, I can confirm as well as the suggested workaround 🙋🏼♂️
Yup I can confirm too. Still not corrected in the docs though in 2020.
Just bumped into this! It also worked for me with child View and useNativeDriver: false.
Most helpful comment
Hey @scottmas sorry for late response on that.
I just tried you example app two comments from now and just made one change in it – replaced View that's inside PanGestureHandler with Animated.View:
Panning on the red box makes the other box move now. Not sure if that's what you'd expect to happen