TouchableOpacity with parent PanResponder
Asked Answered
D

9

24

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

Daugava answered 30/11, 2017 at 8:23 Comment(0)
D
41

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)                  
}
Daugava answered 3/12, 2017 at 8:13 Comment(0)
W
17

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
}
Wieland answered 2/10, 2018 at 13:51 Comment(2)
If I could give you more votes, I would. Thank you! I have fat fingers and couldn't figure out how to adjust for sensitivity. This is perfectHanseatic
This one works fine. But instead of checking for negative, I just instead Math.abs: return Math.abs(gestureState.dx) > 2 || Math.abs(gestureState.dy) > 2;Viridi
S
10

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)
  },
Sturdy answered 1/7, 2019 at 10:10 Comment(2)
You freaking life saver ! I struggled for hours trying to understand why not a single touchable was working in an AnimatedView that use a PanResponder. It should be written somewhere that nothing inside a panResponder can emmit it's touchable event, unless the PanResponder is "inactive"Whew
Happy to help Topsy! You could also try importing TouchableOpacity from 'react-native-gesture-handler'. This may also behave better / fix instances like this.Sturdy
S
6
this.panResponder = PanResponder.create({
    onStartShouldSetPanResponderCapture: (evt, gestureState) => false,
});
Sivan answered 29/5, 2018 at 5:20 Comment(0)
M
2

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!

Mashie answered 8/9, 2019 at 18:1 Comment(0)
M
1

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>
    );
  }
}
Monomer answered 26/4, 2018 at 10:4 Comment(0)
B
1

My solution for Android. All others return false

onMoveShouldSetPanResponder: (evt, { dx, dy }) => dx !== 0 || dy !== 0,
Bowser answered 23/6, 2019 at 19:24 Comment(0)
R
0

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;
Reckless answered 14/4, 2022 at 22:52 Comment(0)
T
0

also check if you do not have onStartShouldSetPanResponder: () => true

Tenorio answered 7/6 at 6:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.