TouchableOpacity与父级PanResponder配合使用

24

我有一个父 PanResponder 和一个子 TouchableOpacity。问题在于 TouchableOpacity 无法响应点击事件,因为 PanResponder 占用了点击。

我尝试按照这篇指南进行操作,但没有成功:http://browniefed.com/blog/react-native-maintain-touchable-items-with-a-parent-panresponder/

这是我的代码:

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) => {
            },
        });
这是我的视图:
<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>

谢谢你

8个回答

41

我这种情况的解决方案是修改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)                  
}

16

完美!我只需要调整一下,因为这个很敏感。

onMoveShouldSetPanResponder: (evt, gestureState) => {
      const { dx, dy } = gestureState
      return dx > 2 || dx < -2 || dy > 2 || dy < -2
}

3
如果我可以给你更多的投票,我会的。谢谢!我的手指有点粗,无法调整灵敏度。这很完美。 - Caleb Davenport
5
这个函数运行良好。但是我不是检查负数,而是使用 Math.abs:return Math.abs(gestureState.dx) > 2 || Math.abs(gestureState.dy) > 2; - captainskippah

10

我遇到了完全相同的问题。解决方案是以上解决方案的组合。必须为onMoveShouldSetPanResponder和onMoveShouldSetPanResponderCapture都添加条件。这是我所做的:

 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)
  },


2
你真是救命稻草!我苦苦挣扎了数小时,试图理解为什么在使用 PanResponder 的 AnimatedView 中没有一个 Touchable 有效。应该在某个地方写明,除非 PanResponder 是“无效”的,否则 PanResponder 内部的任何内容都不能发出其 Touchable 事件。 - Topsy
很高兴能帮助你,Topsy!你也可以尝试从'react-native-gesture-handler'导入TouchableOpacity。这样可能会表现更好/修复这种情况。 - Stuart Gough

6
this.panResponder = PanResponder.create({
    onStartShouldSetPanResponderCapture: (evt, gestureState) => false,
});

2
似乎每个人都有略微不同的情况...这里是我使用的PanResponder.create属性组合(我试图允许点击传递到子元素,但捕获PanResponder移动事件):
onStartShouldSetPanResponder:        ( e, state ) => false,
onStartShouldSetPanResponderCapture: ( e, state ) => false,
onMoveShouldSetPanResponder:         ( e, state ) => true,
onMoveShouldSetPanResponderCapture:  ( e, state ) => true,

我希望这能帮助其他人,而不会以某种方式回来困扰我!


1

我遇到了类似的问题,但对于我来说,在容器视图中包含Pan视图,同时将按钮放在绝对位置并保持其在Pan视图上方是有帮助的。这种方法可能不适用于所有情况,但对于那些可以绝对定位按钮的情况(例如我的情况),它是有效的。

以下是一个具有Pan视图和按钮的组件的示例代码:

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>
    );
  }
}

1
我的Android解决方案。其他所有的都返回false
onMoveShouldSetPanResponder: (evt, { dx, dy }) => dx !== 0 || dy !== 0,

0

上述的修复方法对我没有用,但是下面这个方法有效:

PanResponder.create中将onStartShouldSetPanResponderCapture设置为false

以下是我的panResponder代码:

 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;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接