这个问题与此有关
我正在尝试使用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
来限制滚动。
我遇到了类似的问题。这就是我在我的案例中所做的
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()
},
这是针对您的问题的解决方案,或者您可以将其用作替代解决方案。我没有在这个解决方案中使用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>
)
}
}