限制PanResponder动作React Native

时间:2018-06-30 11:36:37

标签: 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,我曾尝试实现以下逻辑:

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

但它似乎根本不起作用。

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

5 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:1)

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

答案 2 :(得分:0)

我遇到了类似的问题。这就是我所做的事情

 onPanResponderMove: (e, gestureState) => {

                let marginLeft = this.state.marginLeft + gestureState.dx;
                marginLeft = marginLeft < 0 ? 0 : (marginLeft > this.state.marginLeft ? this.state.marginLeft : marginLeft);
                Animated.timing(this.state.pan, {
                    toValue: marginLeft,
                    duration: 10
                }).start()

            },

答案 3 :(得分:0)

这是针对您问题的解决方法,也可以将其用作替代解决方案。我在此解决方案中未使用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>
      )
    }
  }

答案 4 :(得分:0)

就像@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>
  )

}