实现的效果如下
http://www.jianshu.com/p/70cd3b2df301
GIF.gif
主要结合了react-native的触摸和动画事件,可通过点击和滑动进行操作。
组件结构
四个滑块是由父组件map而来,因此只分析一个。以touch部分在左边为标准,滑块结构如下
<View style={styles.container}>
<Animated.View
style={[
styles.touch,
{
transform: [
{translateX: this._animatedValue.x}
],
}
]}
>
</Animated.View>
<View style={styles.card}>
</View>
</View>
实质上只是分成了左右结构,左边的touch较为特殊,因为要实现动画效果,由动画组件代替。 想用动画实现什么属性进行变化可通过在style中对该属性的值用Animated.Value()进行初始化。比如想让touch的宽度用动画进行变化, 便可初始化宽度为width: new Animated.Value(0).
开始
起初,没有引入动画,将touch定位设置为relative,在触摸事件中监听其onLayout,通过setState实时刷新位置,代码实现见这一版。 为了性能,为了交互,也为了折腾,引入Animated与PanResponder,让这两个好基友一起做点什么。
关于Animated和PanResponder的详细介绍可查看本文底部讲得非常好的参考链接,下面说实现。
constructor
constructor(props) {
super(props);
this.state = {
isTouch:
false,
blockInLeft:
true,
}
this._containerWidth =
null;
this._touchBlockWidth =
null;
this._touchTimeStamp =
null;
this._startAnimation =
this._startAnimation.bind(
this)
this._animatedDivisionValue = new Animated.Value(
0);
}
触摸事件注册
componentWillMount() {
this._animatedValue = new Animated.ValueXY()
this._value = {x:
0}
this._animatedValue.addListener((value) =>
this._value = value);
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder:
this._handleStartShouldSetPanResponder.bind(
this),
onMoveShouldSetPanResponder:
this._handleMoveShouldSetPanResponder.bind(
this),
onPanResponderGrant:
this._handlePanResponderGrant.bind(
this),
onPanResponderMove:
this._handlePanResponderMove.bind(
this),
onPanResponderRelease:
this._handlePanResponderEnd.bind(
this),
onPanResponderTerminate:
this._handlePanResponderEnd.bind(
this),
});
}
与动画的结合
_handleStartShouldSetPanResponder(e, gestureState){
const tick = new Date().getTime();
if (tick -
this._touchTimeStamp <
500) {
return false;
}
this._touchTimeStamp = tick;
return true;
}
_handleMoveShouldSetPanResponder(e, gestureState){
return true;
}
_handlePanResponderGrant(e, gestureState){
this.setState({
isTouch:
true
})
this._animatedValue.setOffset({x:
this._value.x});
this._animatedValue.setValue({x:
0});
}
_handlePanResponderMove(e, gestureState) {
let canTouchLength =
this._containerWidth -
this._touchBlockWidth
if ( (
this.state.blockInLeft && gestureState.dx >
0 && gestureState.dx < canTouchLength) || (!
this.state.blockInLeft && gestureState.dx <
0 && gestureState.dx > -canTouchLength) ) {
this._animatedValue.setValue({x: gestureState.dx})
}
}
_handlePanResponderEnd(e, gestureState){
let canTouchLength =
this._containerWidth -
this._touchBlockWidth
let moveDistance = gestureState.moveX !==
0 ? gestureState.moveX - gestureState.x0 :
0;
const toRight = moveDistance>
0 ?
true :
false;
moveDistance = Math.abs(moveDistance)
const middleValue = canTouchLength /
2
let endValue =
0
if ( (
this.state.blockInLeft && moveDistance ===
0) || (toRight &&
this.state.blockInLeft && (moveDistance > middleValue)) ) {
endValue = canTouchLength
this.setState({
blockInLeft:
false
})
}
else if ( (!
this.state.blockInLeft && moveDistance ===
0) || (!toRight && !
this.state.blockInLeft && (moveDistance > middleValue)) ) {
endValue = -canTouchLength
this.setState({
blockInLeft:
true
})
}
this._startAnimation(endValue);
this.setState({
isTouch:
false
})
}
_startAnimation(endValue) {
Animated.spring(
this._animatedValue, {
toValue: endValue,
tension:
80
}).start(
() => {
}
);
}
这是整个触摸与动画结合的实践。对于touch移动后另一边的信息也发生移动,可通过监听touch的blockInLeft,用margin对另一边信息进行定位,这是我试过最简单而且没有副作用的方法。 还想实现的一个功能是,随着touch从一边移动到另一边,底部文字的透明度从1 -> 0 -> 1 这样切换。 代码可以精简,性能还可以优化,先提供一个实现该功能的方法。欢迎拍砖指正,交流学习。
参考文章
React-Native 触摸与动画「指尖上的魔法」- 谈谈 React Native 中的手势React Native Animation Book 谢谢奉献