• ReactNative入门 —— 动画篇(下)


    在上篇动画入门文章中我们了解了在 React Native 中简单的动画的实现方式,本篇将作为上篇的延续,介绍如何使用 Animated 实现一些比较复杂的动画。

    动画组合

    在 Animated 中提供了一些有趣的API方法来轻松地按我们的需求实现组合动画,它们分别是 Animated.parallel、Animated.sequence、Animated.stagger、Animated.delay。

    我们会分别介绍这些方法,并从中学习到一些其它有用的API。最后我们会得到一个有趣的DOGE动画 

    1. Animated.parallel

    并行执行一系列指定动画的方法,其格式如下:

    Animated.parallel(Animates<Array>, [conf<Object>])

    第一个参数接受一个元素为动画的数组,通过执行 start() 方法可以并行执行该数组中的所有方法。

    如果数组中任意动画被中断的话,该数组内对应的全部动画会一起停止,不过我们可以通过第二个(可选)参数 conf 来取消这种牵连特性:

    {stopTogether: false}

    我们先看一个简单的、没有使用 Animated.parallel 的例子:

    class AwesomeProject extends Component {
        constructor(props) {
            super(props);
            this.state = {
                grassTransY : new Animated.Value(Dimensions.get('window').height/2),
                bigDogeTrans : new Animated.ValueXY({
                    x: 100,
                    y: 298
                })
            }
        }
    
        componentDidMount() {
            Animated.timing(this.state.grassTransY, {
                toValue: 200,
                duration: 1000,
                easing: Easing.bezier(0.15, 0.73, 0.37, 1.2)
            }).start();
    
            Animated.timing(this.state.bigDogeTrans, {
                toValue: {
                    x : Dimensions.get('window').width/2 - 139,
                    y : -200
                },
                duration: 2000,
                delay: 1000
            }).start();
        }
    
        render() {
            return (
                <View style={styles.container}>
                    <Animated.View style={[styles.doges, {transform: this.state.bigDogeTrans.getTranslateTransform()}]} >
                        <Image source={require('./src/img/bdoge.png')}/>
                    </Animated.View>
    
                    <Animated.View style={[styles.grass, {transform: [{translateY: this.state.grassTransY}]}]}></Animated.View>
    
                </View>
    
    
            );
        }
    }
    
    var styles = StyleSheet.create({
        grass: {
          position: 'absolute',
            Dimensions.get('window').width,
          backgroundColor: '#A3D900',
          height: 240
        },
        doges: {
            position: 'absolute'
        },
        container: {
            flex: 1,
            justifyContent: 'center',
            alignItems: 'center',
            backgroundColor: '#73B9FF'
        }
    });

    执行如下:

    更改为 Animated.parallel 形式为(只需要修改 componentDidMount 代码块):

        componentDidMount() {
            var timing = Animated.timing;
            Animated.parallel([
                timing(this.state.grassTransY, {
                    toValue: 200,
                    duration: 1000,
                    easing: Easing.bezier(0.15, 0.73, 0.37, 1.2)
                }),
                timing(this.state.bigDogeTrans, {
                    toValue: {
                        x : Dimensions.get('window').width/2 - 139,
                        y : -200
                    },
                    duration: 2000,
                    delay: 1000
                })
            ]).start();
        }

    执行后效果是一致的。

    不过对于上方的代码,这里提一下俩处API:

            new Animated.ValueXY({
                    x: 100,
                    y: 298
            })

    以及我们给 doge 设置的样式:

    {transform: this.state.bigDogeTrans.getTranslateTransform()}

    它们是一个语法糖,Animated.ValueXY 方法会生成一个 x 和 y 的映射对象,方便后续使用相关方法将该对象转换为需要的样式对象。

    例如这里我们通过 .getTranslateTransform() 方法将该 ValueXY 对象转换为 translate 的样式值应用到组件上,即其初始样式等价于 

    {transform: [{
        translateX: 100
    },
    {
        translateY: 298
    }]}

    注意在 Animated.timing 中设置动画终值时需要以

    { x: XXX,
      y: YYY
    }

    的形式来做修改:

                timing(this.state.bigDogeTrans, {
                    toValue: {  //注意这里
                        x : Dimensions.get('window').width/2 - 139,
                        y : -200
                    },
                    duration: 2000,
                    delay: 1000
                })

    另外,除了能将 ValueXY 对象转为 translateX/Y 的 getTranslateTransform() 方法,我们还能通过 getLayout() 方法来将 ValueXY 对象转为 {left, top} 形式的样式对象:

     style={{
       transform: this.state.anim.getTranslateTransform()
     }}

    不过这里就不举例了。

    我们回到 Animated.parallel 方法的话题来。我们对开头的代码做小小的改动来学习一个新的API—— interpolate 插值函数:

    class AwesomeProject extends Component {
        constructor(props) {
            super(props);
            this.state = {
                //注意这里初始化value都为0
                grassTransY : new Animated.Value(0),
                bigDogeTransY : new Animated.Value(0)
            }
        }
    
        componentDidMount() {
            var timing = Animated.timing;
            Animated.parallel(['grassTransY', 'bigDogeTransY'].map((prop, i) => {
                var _conf = {
                    toValue: 1,    //注意这里设置最终value都为1
                    duration: 1000 + i * 1000
                };
                i || (_conf.easing = Easing.bezier(0.15, 0.73, 0.37, 1.2));
    
                return timing(this.state[prop], _conf)
            })).start();
        }
    
        render() {
            return (
                <View style={styles.container}>
                    <Animated.View style={[styles.doges, {transform: [{
                            translateX: Dimensions.get('window').width/2 - 139
                        },
                        {
                            translateY: this.state.bigDogeTransY.interpolate({
                                inputRange: [0, 1],  //动画value输入范围
                                outputRange: [298, -200]  //对应的输出范围
                        })
                    }]}]}>
                        <Image source={require('./src/img/bdoge.png')}/>
                    </Animated.View>
    
                    <Animated.View style={[styles.grass, {transform: [{
                        translateY: this.state.grassTransY.interpolate({
                            inputRange: [0, 1],
                            outputRange: [Dimensions.get('window').height/2, 200]
                        })
                    }]}]}></Animated.View>
    
                </View>
            );
        }
    }

    注意我们这里统一把动画属性初始值都设为0:

            this.state = {
                //注意这里初始化value都为0
                grassTransY : new Animated.Value(0),
                bigDogeTransY : new Animated.Value(0)
            }

    然后又把动画属性的终值设为1:

                var _conf = {
                    toValue: 1,    //注意这里设置最终value都为1
                    duration: 1000
                };
    
                return timing(this.state[prop], _conf)

    然后通过 interpolate 插值函数将 value 映射为正确的值:

                        translateY: this.state.bigDogeTransY.interpolate({
                            inputRange: [0, 1],  //动画value输入范围
                            outputRange: [298, -200]  //对应的输出范围
                        })

    这意味着当 value 的值为0时,interpolate 会将其转为 298 传给组件;当 value 的值为1时则转为 -200。

    因此当value的值从0变化到1时,interpolate 会将其转为 (298 - 498 * value) 的值。

    事实上 inputRange 和 outputRange 的取值非常灵活,我们看官网的例子:

    value.interpolate({
      inputRange: [-300, -100, 0, 100, 101],
      outputRange: [300,    0, 1,   0,   0],
    });

    其映射为:

    Input       Output
    -400        450
    -300        300
    -200        150
    -100        0
    -50         0.5
    0           1
    50          0.5
    100         0
    101         0
    200         0

    2. Animated.sequence

    Animated的动画是异步执行的,如果希望它们能以队列的形式一个个逐步执行,那么 Animated.sequence 会是一个最好的实现。其语法如下:

    Animated.sequence(Animates<Array>)

    事实上了解了开头的 parallel 方法,后面几个方法都是一样套路了。

    来个例子,我们依旧直接修改上方代码即可:

        componentDidMount() {
            var timing = Animated.timing;
            Animated.sequence(['grassTransY', 'bigDogeTransY'].map((prop, i) => {
                var _conf = {
                    toValue: 1
                };
                if(i==0){
                    _conf.easing = Easing.bezier(0.15, 0.73, 0.37, 1.2)
                }
    
                return timing(this.state[prop], _conf)
            })).start();
        }

    这样 doge 的动画会在 草地出来的动画结束后才开始执行:

    3. Animated.stagger

    该方法为 sequence 的变异版,支持传入一个时间参数来设置队列动画间的延迟,即让前一个动画结束后,隔一段指定时间才开始执行下一个动画。其语法如下:

    Animated.stagger(delayTime<Number>, Animates<Array>)

    其中 delayTime 为指定的延迟时间(毫秒),我们继续拿前面的代码来开刀(为了给力点我们再加入一个running doge的动画事件)

    class AwesomeProject extends Component {
        constructor(props) {
            super(props);
            this.state = {
                grassTransY : new Animated.Value(0),
                bigDogeTransY : new Animated.Value(0),
                runningDogeTrans : new Animated.ValueXY({
                    x: Dimensions.get('window').width,
                    y: Dimensions.get('window').height/2 - 120
                })
            }
        }
    
        componentDidMount() {
            var timing = Animated.timing;
            Animated.stagger(1500, ['grassTransY', 'bigDogeTransY', 'runningDogeTrans'].map((prop, i) => {
                var _conf = {
                    toValue: 1
                };
                if(i==0){
                    _conf.easing = Easing.bezier(0.15, 0.73, 0.37, 1.2)
                }
    
                if(i==2){ //running doge
                    _conf.toValue = {
                        x: Dimensions.get('window').width - 150,
                        y: Dimensions.get('window').height/2 - 120
                    }
                }
    
                return timing(this.state[prop], _conf)
            })).start();
        }
    
        render() {
            return (
                <View style={styles.container}>
                    <Animated.View style={[styles.doges, {transform: [{
                        translateX: Dimensions.get('window').width/2 - 139
                    },
                    {
                        translateY: this.state.bigDogeTransY.interpolate({
                            inputRange: [0, 1],  
                            outputRange: [298, -200]
                        })
                    }]}]}>
                        <Image source={require('./src/img/bdoge.png')}/>
                    </Animated.View>
    
                    <Animated.View style={[styles.grass, {transform: [{
                        translateY: this.state.grassTransY.interpolate({
                            inputRange: [0, 1],
                            outputRange: [Dimensions.get('window').height/2, 200]
                        })
                    }]}]}></Animated.View>
    
                    <Animated.View style={[styles.doges, {
                        transform: this.state.runningDogeTrans.getTranslateTransform()
                    }]}>
                        <Image source={require('./src/img/sdoge.gif')}/>
                    </Animated.View>
    
                </View>
            );
        }
    }

    我们把三个动画间隔时间设定为 2000 毫秒,执行效果如下:

    4. Animated.delay

    噢这个接口实在太简单了,就是设置一段动画的延迟时间,接收一个时间参数(毫秒)作为指定延迟时长:

    Animated.delay(delayTime<Number>)

    常规还是跟好基友 Animated.sequence 一同使用,我们继续修改前面的代码:

    class AwesomeProject extends Component {
        constructor(props) {
            super(props);
            this.state = {
                grassTransY : new Animated.Value(0),
                bigDogeTransY : new Animated.Value(0),
                runningDogeTrans : new Animated.ValueXY({
                    x: Dimensions.get('window').width,
                    y: Dimensions.get('window').height/2 - 120
                })
            }
        }
    
        componentDidMount() {
            var timing = Animated.timing;
            Animated.sequence([
                Animated.delay(1000),  //延迟1秒再开始执行动画
    
                timing(this.state.grassTransY, {
                    toValue: 1,
                    duration: 1000,
                    easing: Easing.bezier(0.15, 0.73, 0.37, 1.2)
                }),
    
                timing(this.state.bigDogeTransY, {
                    toValue: 1,
                    duration: 3000
                }),
    
                Animated.delay(2000),  //延迟2秒再执行running doge动画
    
                timing(this.state.runningDogeTrans, {
                    toValue: {
                        x: Dimensions.get('window').width - 150,
                        y: Dimensions.get('window').height/2 - 120
                    },
                    duration: 2000
                }),
    
                Animated.delay(1000),  //1秒后跑到中间
    
                timing(this.state.runningDogeTrans, {
                    toValue: {
                        x: Dimensions.get('window').width/2 - 59,
                        y: Dimensions.get('window').height/2 - 180
                    },
                    duration: 1000
                })
            ]
    
            ).start();
        }
    
        render() {
            return (
                <View style={styles.container}>
                    <Animated.View style={[styles.doges, {transform: [{
                        translateX: Dimensions.get('window').width/2 - 139
                    },
                    {
                        translateY: this.state.bigDogeTransY.interpolate({
                            inputRange: [0, 1],  //动画value输入范围
                            outputRange: [298, -200]  //对应的输出范围
                        })
                    }]}]}>
                        <Image source={require('./src/img/bdoge.png')}/>
                    </Animated.View>
    
                    <Animated.View style={[styles.grass, {transform: [{
                        translateY: this.state.grassTransY.interpolate({
                            inputRange: [0, 1],
                            outputRange: [Dimensions.get('window').height/2, 200]
                        })
                    }]}]}></Animated.View>
    
                    <Animated.View style={[styles.doges, {
                        transform: this.state.runningDogeTrans.getTranslateTransform()
                    }]}>
                        <Image source={require('./src/img/sdoge.gif')}/>
                    </Animated.View>
    
                </View>
            );
        }
    }

    执行如下:

    到这里我们基本就掌握了 RN 动画的常用API了,对于本章的 Doge 动画我们再搞复杂一点——再加一只running doge,然后在动画结束后,让最大的Doge头一直不断地循环旋转:

    class AwesomeProject extends Component {
        constructor(props) {
            super(props);
            this.state = {
                grassTransY : new Animated.Value(0),
                bigDogeTransY : new Animated.Value(0),
                bigDogeRotate : new Animated.Value(0),
                runningDogeTrans : new Animated.ValueXY({
                    x: Dimensions.get('window').width,
                    y: Dimensions.get('window').height/2 - 120
                }),
                runningDoge2Trans : new Animated.ValueXY({
                    x: Dimensions.get('window').width,
                    y: Dimensions.get('window').height/2 - 90
                })
            }
        }
    
        componentDidMount() {
            var timing = Animated.timing;
            Animated.sequence([
                Animated.delay(1000),  //延迟1秒再开始执行动画
    
                timing(this.state.grassTransY, {
                    toValue: 1,
                    duration: 1000,
                    easing: Easing.bezier(0.15, 0.73, 0.37, 1.2)
                }),
    
                timing(this.state.bigDogeTransY, {
                    toValue: 1,
                    duration: 3000
                }),
    
                Animated.parallel([
                    Animated.sequence([
                        timing(this.state.runningDogeTrans, {
                            toValue: {
                                x: Dimensions.get('window').width - 150,
                                y: Dimensions.get('window').height/2 - 120
                            },
                            duration: 2000
                        }),
    
                        Animated.delay(1000),  //1秒后跑到中间
    
                        timing(this.state.runningDogeTrans, {
                            toValue: {
                                x: Dimensions.get('window').width/2 - 99,
                                y: Dimensions.get('window').height/2 - 180
                            },
                            duration: 1000
                        })
                    ]),
    
                    Animated.sequence([
                        timing(this.state.runningDoge2Trans, {
                            toValue: {
                                x: Dimensions.get('window').width/2 + 90,
                                y: Dimensions.get('window').height/2 - 90
                            },
                            duration: 2000
                        }),
    
                        Animated.delay(1000),
    
                        timing(this.state.runningDoge2Trans, {
                            toValue: {
                                x: Dimensions.get('window').width/2 + 20,
                                y: Dimensions.get('window').height/2 - 110
                            },
                            duration: 1000
                        })
                    ])
    
                ])
            ]
    
            ).start(()=>{
                this.bigDogeRotate()
            });
        }
    
        //大doge一直不断循环
        bigDogeRotate(){
            this.state.bigDogeRotate.setValue(0);  //重置Rotate动画值为0
            Animated.timing(this.state.bigDogeRotate, {
                toValue: 1,
                duration: 5000
            }).start(() => this.bigDogeRotate())
        }
    
        render() {
            return (
                <View style={styles.container}>
                    <Animated.View style={[styles.doges, {transform: [{
                        translateX: Dimensions.get('window').width/2 - 139
                    },
                    {
                        translateY: this.state.bigDogeTransY.interpolate({
                            inputRange: [0, 1],  //动画value输入范围
                            outputRange: [298, -200]  //对应的输出范围
                        })
                    },
                    {
                        rotateZ: this.state.bigDogeRotate.interpolate({
                                inputRange: [0, 1],  //动画value输入范围
                                outputRange: ['0deg', '360deg']  //对应的输出范围
                        })
                    }]}]}>
                        <Image source={require('./src/img/bdoge.png')}/>
                    </Animated.View>
    
                    <Animated.View style={[styles.grass, {transform: [{
                        translateY: this.state.grassTransY.interpolate({
                            inputRange: [0, 1],
                            outputRange: [Dimensions.get('window').height/2, 200]
                        })
                    }]}]}></Animated.View>
    
                    <Animated.View style={[styles.doges, {
                        transform: this.state.runningDogeTrans.getTranslateTransform()
                    }]}>
                        <Image source={require('./src/img/sdoge.gif')}/>
                    </Animated.View>
    
                    <Animated.View style={[styles.doges, {
                        transform: this.state.runningDoge2Trans.getTranslateTransform()
                    }]}>
                        <Image source={require('./src/img/sdoge.gif')}/>
                    </Animated.View>
    
                </View>
            );
        }
    }
    View Code

    最终效果如下:

    写完这篇文章都凌晨了,我也是蛮拼的 

    共勉~

    donate

  • 相关阅读:
    Castle IOC容器内幕故事(下)
    Castle IOC容器实践之TypedFactory Facility(一)
    Castle ActiveRecord学习实践(9):使用ActiveRecord的一些技巧
    Web2.0改变了我的生活
    Castle IOC容器与Spring.NET配置之比较
    Castle IOC容器实践之Startable Facility(二)
    Castle IOC容器构建配置详解(二)
    Castle IOC容器实践之TypedFactory Facility(二)
    Castle IOC容器快速入门
    Castle IOC容器组件生命周期管理
  • 原文地址:https://www.cnblogs.com/vajoy/p/5425836.html
Copyright © 2020-2023  润新知