我们先看一下 京东加入购物车的动画是怎样的: 治好你的颈椎病!
然后看一下,我目前的实现效果(后续会完善) :
分析一下:
看上去很简单,从加入购物车按钮的位置跳出商品图片(缩小版) , 然后以抛物线形式跳到购物车里.
好了 开工 .
1. rn的动画,我们使用animated来实现:
constructor(props) { super(props); this.state = { number: 0, heightValue: new Animated.Value(0), widthValue: new Animated.Value(0), springValue: new Animated.Value(0), } }
首先,设定3个初始的动画值: Y轴距离,X轴距离,购物车数量字符的变大变小效果.
2. 然后设置动画方法:
this.state.heightValue.setValue(0); this.state.widthValue.setValue(0); this.setState({ number: this.state.number + 1 })Animated.parallel([ Animated.timing(this.state.heightValue, { toValue: 1, duration: 1000, easing: Easing.linear,// 线性的渐变函数 }), Animated.timing(this.state.heightValue, { toValue: 1, duration: 1000, easing: Easing.linear,// 线性的渐变函数 }) ]).start(() => this.spring());
spring() { this.state.springValue.setValue(0); Animated.spring( this.state.springValue, { toValue: 1, firction: 1 }).start(); }
第一个是在1秒的时间内从0变1,第二个动画是跟踪速度使动画更连贯的spring创建的动画, friction 能帮你更好地控制 spring 动画。
2. 然后 ,介绍一个方法:
插值(Interpolation)
Animated
API还有一个很强大的部分就是interpolate
插值函数。它可以接受一个输入区间,然后将其映射到另一个的输出区间。下面是一个一个简单的从0-1区间到0-100区间的映射示例:
value.interpolate({
inputRange: [0, 1],
outputRange: [0, 100],
});
interpolate
还支持定义多个区间段落,常用来定义静止区间等。举个例子,要让输入在接近-300时取相反值,然后在输入接近-100时到达0,然后在输入接近0时又回到1,接着一直到输入到100的过程中逐步回到0,最后形成一个始终为0的静止区间,对于任何大于100的输入都返回0。具体写法如下:
value.interpolate({
inputRange: [-300, -100, 0, 100, 101],
outputRange: [300, 0, 1, 0, 0],
});
它的最终映射结果如下:
输入 | 输出 |
---|---|
-400 | 450 |
-300 | 300 |
-200 | 150 |
-100 | 0 |
-50 | 0.5 |
0 | 1 |
50 | 0.5 |
100 | 0 |
101 | 0 |
200 | 0 |
interpolate
还支持到字符串的映射,从而可以实现颜色以及带有单位的值的动画变换。例如你可以像下面这样实现一个旋转动画:
value.interpolate({
inputRange: [0, 360],
outputRange: ['0deg', '360deg']
})
interpolation
还支持任意的渐变函数,其中有很多已经在Easing
类中定义了,包括二次、指数、贝塞尔等曲线以及step、bounce等方法。interpolation
还支持限制输出区间outputRange
。你可以通过设置extrapolate
、extrapolateLeft
或extrapolateRight
属性来限制输出区间。默认值是extend
(允许超出),不过你可以使用clamp
选项来阻止输出值超过outputRange
。
const aheight = this.state.heightValue.interpolate({ inputRange: [0, 0.25, 0.5, 0.75, 1], outputRange: [25, 75, 100, 75, 25] }); const awidth = this.state.heightValue.interpolate({ inputRange: [0, 0.25, 0.5, 0.75, 1], outputRange: [120, 140, 160, 180, 200] }); const springBig = this.state.springValue.interpolate({ inputRange: [0, 0.5, 1], outputRange: [1, 1.5, 1] });
前两个是Y轴和X轴的坐标,也可以理解为距离右侧的距离(实际可以直接给定动画图片的坐标,左右边距)
3. 最后 设置动画的 View的样式, 把动画效果应用到里面:
<Animated.View style={[styles.numberView, { transform: [{ scale: springBig }] }] } onLayout={(e) => { // alert(e.nativeEvent.layout.y); }}> <Text style={[styles.numberText, {color: 'red'}]}>{this.state.number}</Text> </Animated.View>
这个是购物车里字的变化,放大它自己
<Animated.Image source={require('./img/aa.jpg')} style={{width: 20, height: 20, position: 'absolute', bottom: aheight, right: awidth}}/>
这是图片位置的变化.我这里是控制它距离下面和右面的距离.
好了,暂时可以基本实现我们想要的效果了.下面附上源码:
/** * Created by 卓原 on 2017/8/17. * zhuoyuan93@gmail.com */ import React from 'react'; import { View, Text, StyleSheet, Animated, TouchableOpacity, Easing } from 'react-native'; export default class ShoppingCart extends React.Component { constructor(props) { super(props); this.state = { number: 0, heightValue: new Animated.Value(0), widthValue: new Animated.Value(0), // fontValue: new Animated.Value(0), springValue: new Animated.Value(0), } } render() { const aheight = this.state.heightValue.interpolate({ inputRange: [0, 0.25, 0.5, 0.75, 1], outputRange: [25, 75, 100, 75, 25] }); const awidth = this.state.heightValue.interpolate({ inputRange: [0, 0.25, 0.5, 0.75, 1], outputRange: [120, 140, 160, 180, 200] }); /*const font = this.state.fontValue.interpolate({ inputRange: [0, 0.5, 1], outputRange: [16, 22, 16] });*/ const springBig = this.state.springValue.interpolate({ inputRange: [0, 0.5, 1], outputRange: [1, 1.5, 1] }); return ( <View style={styles.container}> <Text>购物车动画</Text> <Animated.Image source={require('./img/aa.jpg')} style={{width: 20, height: 20, position: 'absolute', bottom: aheight, right: awidth}}/> <View style={styles.shopcart}> <View style={{flex: 2, flexDirection: 'row'}}> <View style={styles.bottomItem}> <Text>客服</Text> </View> <View style={styles.bottomItem}> <Text>后仓</Text> </View> <View style={styles.bottomItem}> <Text>购物车</Text> <Animated.View style={[styles.numberView, { transform: [{ scale: springBig }] }] } onLayout={(e) => { // alert(e.nativeEvent.layout.y); }}> <Text style={[styles.numberText, {color: 'red'}]}>{this.state.number}</Text> </Animated.View> </View> </View> <TouchableOpacity onPress={() => { this.startAnimated(); }}> <View style={[styles.bottomItem, {backgroundColor: 'red'}]}> <Text style={styles.numberText}>加入购物车</Text> </View> </TouchableOpacity> <View style={[styles.bottomItem, {backgroundColor: 'green'}]}> <Text style={styles.numberText}>看左面{'\n'}加入{'\n'}购物车</Text> </View> </View> </View> ) } startAnimated() { this.state.heightValue.setValue(0); this.state.widthValue.setValue(0); // this.state.fontValue.setValue(0); this.setState({ number: this.state.number + 1 }) // Animated.sequence([ Animated.parallel([ Animated.timing(this.state.heightValue, { toValue: 1, duration: 1000, easing: Easing.linear,// 线性的渐变函数 }), Animated.timing(this.state.heightValue, { toValue: 1, duration: 1000, easing: Easing.linear,// 线性的渐变函数 }), // ]), // Animated.timing(this.state.fontValue, { // toValue: 1, // duration: 500, // easing: Easing.linear,// 线性的渐变函数 // }), ]).start(() => this.spring()); } spring() { this.state.springValue.setValue(0); Animated.spring( this.state.springValue, { toValue: 1, firction: 1 }).start(); } } const styles = StyleSheet.create({ container: { flex: 1, marginTop: 22, backgroundColor: '#F5FCFF', }, shopcart: { position: 'absolute', bottom: 0, height: 50, width: 375, flexDirection: 'row', backgroundColor: 'white' }, bottomItem: { flex: 1, alignItems: 'center', justifyContent: 'center', borderWidth: 1 }, numberView: { position: 'absolute', right: 5, top: 2, backgroundColor: 'white', borderWidth: 2, borderColor: 'red', width: 24, height: 24, borderRadius: 12, justifyContent: 'center', alignItems: 'center', }, numberText: { color: 'white', textAlign: 'center', padding: 0, includeFontPadding: false } });
未完待续...
如果有更好的实现方式,欢迎留言~