此问题与
有关我正在尝试使用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
},
但它似乎根本不起作用。
如何将检测到的手指运动的整个宽度插值到我定义的有限范围内?
答案 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>
)
}