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
最終效果如下:
寫完這篇文章都凌晨了,我也是蠻拼的
共勉~
總結
以上是生活随笔為你收集整理的ReactNative入门 —— 动画篇(下)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android studio 虚拟机ad
- 下一篇: 免税和免征增值税的区别