'TouchableOpacity with parent PanResponder
I have a parent PanResponder
with a child TouchableOpacity.
What happens is that the TouchableOpacity
doesn't respond to clicks because the PanResponder
takes the click.
I have tried to follow this guide but no success:
http://browniefed.com/blog/react-native-maintain-touchable-items-with-a-parent-panresponder/
this is my code:
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => false,
onMoveShouldSetResponderCapture: () => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => {
return gestureState.dx != 0 && gestureState.dy != 0;
},
onPanResponderGrant: (evt, gestureState) => {
let isFirst = gestureState.y0 > 164 ? false : true;
this.setState({animObj: isFirst, isUsingCurtain: true});
},
onPanResponderMove: (evt, gestureState) => {
//let Y = this.state.animObj ? gestureState.moveY - this.state.currentHeaderHeight : gestureState.moveY - this.state.currentHeaderHeight ;// - this.state.currentHeaderHeight;
let Y = gestureState.moveY - this.state.currentHeaderHeight + 20
if (Y < 0) {
return false
}
this.state.animCurtain.setValue(Y);
gestureState.moveY > height / 2 ? this.setState({curtainOnMiddle: true}) : this.setState({curtainOnMiddle: false})
},
onPanResponderTerminationRequest: (evt, gestureState) => true,
onPanResponderRelease: (evt, gestureState) => {
if (((height / 2) > gestureState.moveY)) {//slide back up1
this._CurtainAnimation(0, false);
} else {//slide to bottom
let val = height - calcSize(180);
this._CurtainAnimation(val, true);
}
},
onPanResponderTerminate: (evt, gestureState) => {
},
onPanResponderStart: (e, gestureState) => {
},
});
and this is my View :
<Animated.View
style={[styles.bottomHPHeader, TopArroOpacity ? {opacity: TopArroOpacity} : ""]} {...this._panResponder.panHandlers}>
<TouchableOpacity onPress={() => this._animateAutoCurtain()}>
{this.state.curtainOnMiddle ?
<AIIcon image={require('../../../../assets/images/homepage/close_drawer_arrow.png')}
boxSize={30}/>
: <AIIcon image={require('../../../../assets/images/homepage/open_drawer_arrow.png')}
boxSize={30}/>}
</TouchableOpacity></Animated.View>
Thank you
Solution 1:[1]
The solution for my case was to modify onMoveShouldSetPanResponder
onMoveShouldSetPanResponder: (evt, gestureState) => {
//return true if user is swiping, return false if it's a single click
return !(gestureState.dx === 0 && gestureState.dy === 0)
}
Solution 2:[2]
Perfect! I just needed to adjust because of the sensitivity.
onMoveShouldSetPanResponder: (evt, gestureState) => {
const { dx, dy } = gestureState
return dx > 2 || dx < -2 || dy > 2 || dy < -2
}
Solution 3:[3]
I had the same exact issue. The solution was a combination of the above solutions. Had to add a condition for both the onMoveShouldSetPanResponder and onMoveShouldSetPanResponderCapture. Here is what I did.
onMoveShouldSetPanResponder: (_, gestureState) => {
const { dx, dy } = gestureState
return (dx > 2 || dx < -2 || dy > 2 || dy < -2)
},
onMoveShouldSetPanResponderCapture: (_, gestureState) => {
const { dx, dy } = gestureState
return (dx > 2 || dx < -2 || dy > 2 || dy < -2)
},
Solution 4:[4]
this.panResponder = PanResponder.create({
onStartShouldSetPanResponderCapture: (evt, gestureState) => false,
});
Solution 5:[5]
I came up to the similar problem but for me having a container view containing the pan view inside while having the button on absolute position & keeping it above pan view did help. It could not fit for everyone but for the cases (like mine) where button can be absolutely positioned, it works.
Here's a sample code of a component that has the pan view and button.
import React, { Component } from "react";
import {
Animated,
View,
Text,
StyleSheet,
PanResponder,
TouchableOpacity
} from "react-native";
export class ToolRuler extends Component {
constructor(props) {
super(props);
this.state = {
moveX: 0,
moveY: 0
};
}
componentWillMount() {
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => false,
onMoveShouldSetResponderCapture: () => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
onPanResponderGrant: (evt, gestureState) => {
// anything
},
onPanResponderMove: (evt, gestureState) => {
this.setState({
moveX: gestureState.moveX,
moveY: gestureState.moveY
});
},
onPanResponderTerminationRequest: (evt, gestureState) => true,
onPanResponderRelease: (evt, gestureState) => {
this.setState({
moveX: gestureState.moveX,
moveY: gestureState.moveY
});
},
onPanResponderTerminate: (evt, gestureState) => {},
onPanResponderStart: (e, gestureState) => {}
});
}
render() {
return (
<View
style={{
flex: 1,
position: "relative",
alignItems: "center",
justifyContent: "center"
}}
>
<Animated.View
{...this._panResponder.panHandlers}
style={{ flex: 1, width: "100%", backgroundColor: "#ccc" }}
>
<Text style={{ marginTop: 200, textAlign: "center" }}>
x: {this.state.moveX}, y: {this.state.moveY}
</Text>
</Animated.View>
<TouchableOpacity
style={{
position: "absolute"
}}
onPress={() => alert("I am tapped!")}
>
<Text>Click Me</Text>
</TouchableOpacity>
</View>
);
}
}
Solution 6:[6]
My solution for Android. All others return false
onMoveShouldSetPanResponder: (evt, { dx, dy }) => dx !== 0 || dy !== 0,
Solution 7:[7]
It seems a slightly different situation works for everyone... here is the combination of PanResponder.create
properties which worked for me (I was trying to allow taps through to child elements, but capture moves with the PanResponder:
onStartShouldSetPanResponder: ( e, state ) => false,
onStartShouldSetPanResponderCapture: ( e, state ) => false,
onMoveShouldSetPanResponder: ( e, state ) => true,
onMoveShouldSetPanResponderCapture: ( e, state ) => true,
I hope this helps someone else and it doesn't somehow come back to haunt me!
Solution 8:[8]
The above fixes did not work for me but the following did -
set onStartShouldSetPanResponderCapture
to false
in PanResponder.create
Below is my panResponder code
const panResponder = React.useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => true,
// We need this code to listen for onPress vs pan events
onStartShouldSetPanResponderCapture: () => false,
onMoveShouldSetPanResponder: () => true,
onMoveShouldSetPanResponderCapture: () => true,
onPanResponderTerminationRequest: () => true,
onPanResponderMove: (e, gestureState) => {
if (gestureState.dy > 0) {
Animated.event([null, { dy: pan.y }], { useNativeDriver: false })(
e,
gestureState,
);
}
},
onPanResponderRelease: (evt, gestureState) => {
// If pulled down far enough, dismiss modal
if (gestureState.dy > SWIPE_DISTANCE) onCancel();
else {
// If not dismissed slide back to standard height
Animated.spring(pan, {
toValue: { x: 0, y: 0 },
useNativeDriver: false,
}).start();
}
},
onShouldBlockNativeResponder: () => true,
}),
).current;
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Maxim Toyberman |
Solution 2 | Florian Bonniec |
Solution 3 | Stuart Gough |
Solution 4 | |
Solution 5 | necixy |
Solution 6 | woodpav |
Solution 7 | James Cushing |
Solution 8 | littlebird99 |