繁体   English   中英

限制 PanResponder 移动 React Native

[英]Limit PanResponder movements React Native

这个问题与

我正在尝试使用 PanResponder 构建一个水平滑块。 我可以使用以下代码在 x 轴上移动元素,但我想限制可以移动它的范围。

这是一个带注释的示例:

export class MySlider extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            pan: new Animated.ValueXY()
        };
        this._panResponder = PanResponder.create({
            onStartShouldSetPanResponder : () => true,
            onPanResponderGrant: (e, gestureState) => {
                this.state.pan.setOffset(this.state.pan.__getValue());
                this.setState({isAddNewSession:true});
            },
                ///////////////////////////
                // CODE OF INTEREST BELOW HERE
                ///////////////////////////
            onPanResponderMove: (evt, gestureState) => {
                // I need this space to do some other functions
                // This is where I imagine I should implement constraint logic
                return Animated.event([null, {
                    dx: this.state.pan.x
                }])(evt, gestureState)
            },
            onPanResponderRelease: (e, gesture) => { 
                this.setState({isAddNewSessionModal:true});
                this.setState({isAddNewSession:false});
            }
        });
    render() {
        let { pan } = this.state;
        let translateX = pan.x;
        const styles = StyleSheet.create({
            pan: {
                transform: [{translateX:translateX}]
            },
            slider: {
                height: 44,
                width: 60,
                backgroundColor: '#b4b4b4'
            },
            holder: {
                height: 60,
                width: Dimensions.get('window').width,
                flexDirection: 'row',
                backgroundColor: 'transparent',
                justifyContent: 'space-between',
                borderStyle: 'solid',
                borderWidth: 8,
                borderColor: '#d2d2d2'
            }
        });
        const width = Dimensions.get('window').width - 70
        return (
            <View style={styles.holder}>
            <Animated.View 
                hitSlop={{ top: 16, left: 16, right: 16, bottom: 16 }} 
                style={[styles.pan, styles.slider]} 
                {...this._panResponder.panHandlers}/>           
            </View>
            )
    }
}

为了限制值使其不能低于 0,我尝试实现 if else 逻辑,如下所示:

        onPanResponderMove: (evt, gestureState) => {
            return (gestureState.dx > 0) ? Animated.event([null, {
                dx: this.state.pan1.x
            }])(evt, gestureState) : null
        },

但这有问题 - 它最初似乎有效,但最小 x 限制似乎有效增加。 我向后和向前滚动得越多,最小 x 限制似乎就会增加。

我也试过这个:

        onPanResponderMove: (evt, gestureState) => {
            return (this.state.pan1.x.__getValue() > 0) ? Animated.event([null, {
                dx: this.state.pan1.x
            }])(evt, gestureState) : null
        },

但它似乎根本不起作用。

如何将检测到的手指移动的整个宽度插入到我定义的有限范围内?

gestureState.dx是用户每次滑动手指从其原始位置移动的差异。 因此,只要用户抬起手指,它就会重置,这会导致您的问题。

有几种方法可以限制该值:

使用插值

let translateX = pan.x.interpolate({inputRange:[0,100],outputRange:[0,100],extrapolateLeft:"clamp"})虽然这有效,但用户向左滑动的次数越多,他就越需要向右滑动才能到达“真正的 0"

在发布时重置值

onPanResponderRelease: (e, gestureState)=>{
        this.state.pan.setValue({x:realPosition<0?0:realPosition.x,y:realPosition.y})

}

确保使用this.state.pan.addListener获取当前值并将其放入realPosition您可以允许向左滑动并在某种弹簧中将其动画化,或者只是使用先前的插值方法防止它完全消失。

但是您应该考虑使用其他东西,因为 PanResponder 不支持 useNativeDriver。 要么使用 scrollView (如果你想要 4 方向滚动,可以使用两个),它通过它的内容或类似 wix 的react-native-interactable来限制滚动。

我在查看React Native: Constraining Animated.Value上的链接帖子时发现了这篇文章。 您的问题似乎与我遇到的类似,我的解决方案已发布在那里。 基本上,dx 可以超出 b/c 范围,这只是累积距离,而我的解决方案是在 pan.setOffset 处设置上限,因此 dx 不会发疯。

这是针对您的问题的变通解决方案,或者您可以将其用作替代解决方案。 我没有在这个解决方案中使用pan 这个想法是,限制父视图内的滑块移动。 所以它不会转移到父母身上。 考虑下面的代码

 export default class MySlider extends Component<Props> {
  constructor(props) {
    super(props);
    this.containerBounds={
      width:0
    }
    this.touchStart=8;
    this.sliderWidth= 60;
    this.containerBorderWidth=8
    this.state = {
      frameStart:0
    };

    this._panResponder = PanResponder.create({
      onStartShouldSetPanResponder: () => true,
      onPanResponderGrant: (e, gestureState) => {
        this.touchStart=this.state.frameStart;
        this.setState({ isAddNewSession: true });
      },

      onPanResponderMove: (evt, gestureState) => {
        frameStart = this.touchStart + gestureState.dx;
        if(frameStart<0){    
          frameStart=0
        }else if(frameStart+this.sliderWidth>this.containerBounds.width-2*this.containerBorderWidth){             
          frameStart=this.containerBounds.width-this.sliderWidth-2*this.containerBorderWidth
        }

        this.setState({
          frameStart:frameStart
        })

      },
      onPanResponderRelease: (e, gesture) => {
        this.setState({ isAddNewSessionModal: true });
        this.setState({ isAddNewSession: false });
      }
    });

  }
    render() {

      const styles = StyleSheet.create({

        slider: {
          height: 44,
          width:  this.sliderWidth,
          backgroundColor: '#b4b4b4'
        },
        holder: {
          height: 60,
          width: Dimensions.get('window').width,
          flexDirection: 'row',
          backgroundColor: 'transparent',
          justifyContent: 'space-between',
          borderStyle: 'solid',
          borderWidth: this.containerBorderWidth,
          borderColor: '#d2d2d2'
        }
      });
      return (
        <View style={styles.holder}
        onLayout={event => {
          const layout = event.nativeEvent.layout;
          this.containerBounds.width=layout.width;
      }}>
          <Animated.View
            hitSlop={{ top: 16, left: 16, right: 16, bottom: 16 }}
            style={[{left:this.state.frameStart}, styles.slider]}
            {...this._panResponder.panHandlers} />
        </View>
      )
    }
  }

就像上面@AlonBarDavid 所说的那样, gestureState.dx是用户每次滑动手指从其原始位置移动的差异。 因此,只要用户抬起手指,它就会重置,这会导致您的问题。

一种解决方案是创建第二个变量来保存与前一次触摸的offset位置,然后将其添加到gestureState.x值中。


const maxVal = 50; // use whatever value you want the max horizontal movement to be

const Toggle = () => {

  const [animation, setAnimation] = useState(new Animated.ValueXY());
  const [offset, setOffset] = useState(0);

  const handlePanResponderMove = (e, gestureState) => {

    // use the offset from previous touch to determine current x-pos
    const newVal = offset + gestureState.dx > maxVal ? maxVal : offset + gestureState.dx < 0 ? 0 : offset + gestureState.dx;
    animation.setValue({x: newVal, y: 0 });
    // setOffset(newVal); // <- works in hooks, but better to put in handlePanResponderRelease function below. See comment underneath answer for more.
  };

  const handlePanResponderRelease = (e, gestureState) => {
    console.log("release");
    // set the offset value for the next touch event (using previous offset value and current gestureState.dx)
    const newVal = offset + gestureState.dx > maxVal ? maxVal : offset + gestureState.dx < 0 ? 0 : offset + gestureState.dx;
    setOffset(newVal);
  }

  const animatedStyle = {
    transform: [...animation.getTranslateTransform()]
  }

  const _panResponder = PanResponder.create({
    onStartShouldSetPanResponder: () => true,
    onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
    onMoveShouldSetPanResponder: () => true,
    onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
    onPanResponderGrant: () => console.log("granted"),
    onPanResponderMove: (evt, gestureState) => handlePanResponderMove(evt, gestureState),
    onPanResponderRelease: (e, g) => handlePanResponderRelease(e, g)
  });

  const panHandlers = _panResponder.panHandlers;

  return (

    <View style={{ width: 80, height: 40 }}>
      <Animated.View { ...panHandlers } style={[{ position: "absolute", backgroundColor: "red", height: "100%", width: "55%", borderRadius: 50, opacity: 0.5 }, animatedStyle ]} />
    </View>
  )

}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM