ReactNative动画(下)

来源:互联网 发布:中国 种族歧视 知乎 编辑:程序博客网 时间:2024/06/06 05:45

在上篇动画入门文章中我们了解了在 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',      width:  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.50           150          0.5100         0101         0200         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>        );    }}
复制代码

最终效果如下:


来源:http://www.cnblogs.com/vajoy/p/5425836.html
0 0
原创粉丝点击