ReactNative入門 —— 動畫篇(下)

在上篇動畫入門文章中咱們瞭解了在 React Native 中簡單的動畫的實現方式,本篇將做爲上篇的延續,介紹如何使用 Animated 實現一些比較複雜的動畫。數組

動畫組合異步

在 Animated 中提供了一些有趣的API方法來輕鬆地按咱們的需求實現組合動畫,它們分別是 Animated.parallel、Animated.sequence、Animated.stagger、Animated.delay。ide

咱們會分別介紹這些方法,並從中學習到一些其它有用的API。最後咱們會獲得一個有趣的DOGE動畫 函數

 

1. Animated.parallel學習

並行執行一系列指定動畫的方法,其格式以下:flex

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

第一個參數接受一個元素爲動畫的數組,經過執行 start() 方法能夠並行執行該數組中的全部方法。動畫

若是數組中任意動畫被中斷的話,該數組內對應的所有動畫會一塊兒中止,不過咱們能夠經過第二個(可選)參數 conf 來取消這種牽連特性:ui

{stopTogether: false}

咱們先看一個簡單的、沒有使用 Animated.parallel 的例子:this

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

執行以下:spa

更改成 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

相關文章
相關標籤/搜索