react-native 仿京东加入购物车动画效果


我们先看一下 京东加入购物车的动画是怎样的: 治好你的颈椎病!


然后看一下,我目前的实现效果(后续会完善) : 



分析一下:

看上去很简单,从加入购物车按钮的位置跳出商品图片(缩小版) , 然后以抛物线形式跳到购物车里.

好了 开工 .

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],
});

它的最终映射结果如下:

输入 输出
-400450
-300300
-200150
-1000
-500.5
01
500.5
1000
1010
2000

interpolate还支持到字符串的映射,从而可以实现颜色以及带有单位的值的动画变换。例如你可以像下面这样实现一个旋转动画:

 value.interpolate({
   inputRange: [0, 360],
   outputRange: ['0deg', '360deg']
 })

interpolation还支持任意的渐变函数,其中有很多已经在Easing类中定义了,包括二次、指数、贝塞尔等曲线以及step、bounce等方法。interpolation还支持限制输出区间outputRange。你可以通过设置extrapolateextrapolateLeftextrapolateRight属性来限制输出区间。默认值是extend(允许超出),不过你可以使用clamp选项来阻止输出值超过outputRange

我们这里分别对3个动画的输入进行映射:

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
    }
});

未完待续...

如果有更好的实现方式,欢迎留言~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值