ReactNative入門 —— 動(dòng)畫篇(下)
在上篇?jiǎng)赢嬋腴T文章中我們了解了在 React Native 中簡單的動(dòng)畫的實(shí)現(xiàn)方式,本篇將作為上篇的延續(xù),介紹如何使用 Animated 實(shí)現(xiàn)一些比較復(fù)雜的動(dòng)畫。

動(dòng)畫組合
在 Animated 中提供了一些有趣的API方法來輕松地按我們的需求實(shí)現(xiàn)組合動(dòng)畫,它們分別是 Animated.parallel、Animated.sequence、Animated.stagger、Animated.delay。
我們會(huì)分別介紹這些方法,并從中學(xué)習(xí)到一些其它有用的API。最后我們會(huì)得到一個(gè)有趣的DOGE動(dòng)畫 ![]()
1. Animated.parallel
并行執(zhí)行一系列指定動(dòng)畫的方法,其格式如下:
Animated.parallel(Animates<Array>, [conf<Object>])
第一個(gè)參數(shù)接受一個(gè)元素為動(dòng)畫的數(shù)組,通過執(zhí)行 start() 方法可以并行執(zhí)行該數(shù)組中的所有方法。
如果數(shù)組中任意動(dòng)畫被中斷的話,該數(shù)組內(nèi)對(duì)應(yīng)的全部動(dòng)畫會(huì)一起停止,不過我們可以通過第二個(gè)(可選)參數(shù) conf 來取消這種牽連特性:
{stopTogether: false}
我們先看一個(gè)簡單的、沒有使用 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' } });
執(zhí)行如下:

更改為 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(); }
執(zhí)行后效果是一致的。

不過對(duì)于上方的代碼,這里提一下倆處API:
new Animated.ValueXY({ x: 100, y: 298 })
以及我們給 doge 設(shè)置的樣式:
{transform: this.state.bigDogeTrans.getTranslateTransform()}
它們是一個(gè)語法糖,Animated.ValueXY 方法會(huì)生成一個(gè) x 和 y 的映射對(duì)象,方便后續(xù)使用相關(guān)方法將該對(duì)象轉(zhuǎn)換為需要的樣式對(duì)象。
例如這里我們通過 .getTranslateTransform() 方法將該 ValueXY 對(duì)象轉(zhuǎn)換為 translate 的樣式值應(yīng)用到組件上,即其初始樣式等價(jià)于
{transform: [{ translateX: 100 }, { translateY: 298 }]}
注意在 Animated.timing 中設(shè)置動(dòng)畫終值時(shí)需要以
{ x: XXX,
y: YYY
}
的形式來做修改:
timing(this.state.bigDogeTrans, { toValue: { //注意這里 x : Dimensions.get('window').width/2 - 139, y : -200 }, duration: 2000, delay: 1000 })

另外,除了能將 ValueXY 對(duì)象轉(zhuǎn)為 translateX/Y 的 getTranslateTransform() 方法,我們還能通過 getLayout() 方法來將 ValueXY 對(duì)象轉(zhuǎn)為 {left, top} 形式的樣式對(duì)象:
style={{ transform: this.state.anim.getTranslateTransform() }}
不過這里就不舉例了。

我們回到 Animated.parallel 方法的話題來。我們對(duì)開頭的代碼做小小的改動(dòng)來學(xué)習(xí)一個(gè)新的API—— interpolate 插值函數(shù):
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, //注意這里設(shè)置最終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], //動(dòng)畫value輸入范圍 outputRange: [298, -200] //對(duì)應(yīng)的輸出范圍 }) }]}]}> <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> ); } }
注意我們這里統(tǒng)一把動(dòng)畫屬性初始值都設(shè)為0:
this.state = { //注意這里初始化value都為0 grassTransY : new Animated.Value(0), bigDogeTransY : new Animated.Value(0) }
然后又把動(dòng)畫屬性的終值設(shè)為1:
var _conf = { toValue: 1, //注意這里設(shè)置最終value都為1 duration: 1000 }; return timing(this.state[prop], _conf)
然后通過 interpolate 插值函數(shù)將 value 映射為正確的值:
translateY: this.state.bigDogeTransY.interpolate({ inputRange: [0, 1], //動(dòng)畫value輸入范圍 outputRange: [298, -200] //對(duì)應(yīng)的輸出范圍 })
這意味著當(dāng) value 的值為0時(shí),interpolate 會(huì)將其轉(zhuǎn)為 298 傳給組件;當(dāng) value 的值為1時(shí)則轉(zhuǎn)為 -200。
因此當(dāng)value的值從0變化到1時(shí),interpolate 會(huì)將其轉(zhuǎn)為 (298 - 498 * value) 的值。

事實(shí)上 inputRange 和 outputRange 的取值非常靈活,我們看官網(wǎng)的例子:
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的動(dòng)畫是異步執(zhí)行的,如果希望它們能以隊(duì)列的形式一個(gè)個(gè)逐步執(zhí)行,那么 Animated.sequence 會(huì)是一個(gè)最好的實(shí)現(xiàn)。其語法如下:
Animated.sequence(Animates<Array>)
事實(shí)上了解了開頭的 parallel 方法,后面幾個(gè)方法都是一樣套路了。
來個(gè)例子,我們依舊直接修改上方代碼即可:
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 的動(dòng)畫會(huì)在 草地出來的動(dòng)畫結(jié)束后才開始執(zhí)行:


3. Animated.stagger
該方法為 sequence 的變異版,支持傳入一個(gè)時(shí)間參數(shù)來設(shè)置隊(duì)列動(dòng)畫間的延遲,即讓前一個(gè)動(dòng)畫結(jié)束后,隔一段指定時(shí)間才開始執(zhí)行下一個(gè)動(dòng)畫。其語法如下:
Animated.stagger(delayTime<Number>, Animates<Array>)
其中 delayTime 為指定的延遲時(shí)間(毫秒),我們繼續(xù)拿前面的代碼來開刀(為了給力點(diǎn)我們?cè)偌尤胍粋€(gè)running doge的動(dòng)畫事件):
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> ); } }
我們把三個(gè)動(dòng)畫間隔時(shí)間設(shè)定為 2000 毫秒,執(zhí)行效果如下:


4. Animated.delay
噢這個(gè)接口實(shí)在太簡單了,就是設(shè)置一段動(dòng)畫的延遲時(shí)間,接收一個(gè)時(shí)間參數(shù)(毫秒)作為指定延遲時(shí)長:
Animated.delay(delayTime<Number>)
常規(guī)還是跟好基友 Animated.sequence 一同使用,我們繼續(xù)修改前面的代碼:
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秒再開始執(zhí)行動(dòng)畫 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秒再執(zhí)行running doge動(dòng)畫 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], //動(dòng)畫value輸入范圍 outputRange: [298, -200] //對(duì)應(yīng)的輸出范圍 }) }]}]}> <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> ); } }
執(zhí)行如下:


到這里我們基本就掌握了 RN 動(dòng)畫的常用API了,對(duì)于本章的 Doge 動(dòng)畫我們?cè)俑銖?fù)雜一點(diǎn)——再加一只running doge,然后在動(dòng)畫結(jié)束后,讓最大的Doge頭一直不斷地循環(huán)旋轉(zhuǎn):
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秒再開始執(zhí)行動(dòng)畫 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一直不斷循環(huán) bigDogeRotate(){ this.state.bigDogeRotate.setValue(0); //重置Rotate動(dòng)畫值為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], //動(dòng)畫value輸入范圍 outputRange: [298, -200] //對(duì)應(yīng)的輸出范圍 }) }, { rotateZ: this.state.bigDogeRotate.interpolate({ inputRange: [0, 1], //動(dòng)畫value輸入范圍 outputRange: ['0deg', '360deg'] //對(duì)應(yīng)的輸出范圍 }) }]}]}> <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> ); } }
最終效果如下:

寫完這篇文章都凌晨了,我也是蠻拼的
共勉~

浙公網(wǎng)安備 33010602011771號(hào)