ReactNative入门 —— 动画篇(下)
在上篇?jiǎng)赢?huà)入門(mén)文章中我們了解了在 React Native 中簡(jiǎn)單的動(dòng)畫(huà)的實(shí)現(xiàn)方式,本篇將作為上篇的延續(xù),介紹如何使用Animated 實(shí)現(xiàn)一些比較復(fù)雜的動(dòng)畫(huà)。
動(dòng)畫(huà)組合
在 Animated 中提供了一些有趣的API方法來(lái)輕松地按我們的需求實(shí)現(xiàn)組合動(dòng)畫(huà),它們分別是 Animated.parallel、Animated.sequence、Animated.stagger、Animated.delay。
我們會(huì)分別介紹這些方法,并從中學(xué)習(xí)到一些其它有用的API。最后我們會(huì)得到一個(gè)有趣的DOGE動(dòng)畫(huà)
1.Animated.parallel
并行執(zhí)行一系列指定動(dòng)畫(huà)的方法,其格式如下:
Animated.parallel(Animates<Array>, [conf<Object>])
第一個(gè)參數(shù)接受一個(gè)元素為動(dòng)畫(huà)的數(shù)組,通過(guò)執(zhí)行 start() 方法可以并行執(zhí)行該數(shù)組中的所有方法。
如果數(shù)組中任意動(dòng)畫(huà)被中斷的話,該數(shù)組內(nèi)對(duì)應(yīng)的全部動(dòng)畫(huà)會(huì)一起停止,不過(guò)我們可以通過(guò)第二個(gè)(可選)參數(shù) conf 來(lái)取消這種牽連特性:
{stopTogether: false}
我們先看一個(gè)簡(jiǎn)單的、沒(méi)有使用 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'
}
});
執(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í)行后效果是一致的。
不過(guò)對(duì)于上方的代碼,這里提一下倆處API:
new Animated.ValueXY({
x: 100,
y: 298
})
以及我們給 doge 設(shè)置的樣式:
{transform: this.state.bigDogeTrans.getTranslateTransform()}
它們是一個(gè)語(yǔ)法糖,Animated.ValueXY方法會(huì)生成一個(gè) x 和 y 的映射對(duì)象,方便后續(xù)使用相關(guān)方法將該對(duì)象轉(zhuǎn)換為需要的樣式對(duì)象。
例如這里我們通過(guò).getTranslateTransform() 方法將該ValueXY對(duì)象轉(zhuǎn)換為 translate 的樣式值應(yīng)用到組件上,即其初始樣式等價(jià)于
{transform: [{
translateX: 100
},
{
translateY: 298
}]}
注意在Animated.timing 中設(shè)置動(dòng)畫(huà)終值時(shí)需要以
{ x: XXX,
y: YYY
}
的形式來(lái)做修改:
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()方法,我們還能通過(guò)getLayout() 方法來(lái)將ValueXY 對(duì)象轉(zhuǎn)為{left, top} 形式的樣式對(duì)象:
style={{
transform: this.state.anim.getTranslateTransform()
}}
不過(guò)這里就不舉例了。
我們回到 Animated.parallel 方法的話題來(lái)。我們對(duì)開(kāi)頭的代碼做小小的改動(dòng)來(lái)學(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)畫(huà)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)畫(huà)屬性初始值都設(shè)為0:
this.state = {
//注意這里初始化value都為0
grassTransY : new Animated.Value(0),
bigDogeTransY : new Animated.Value(0)
}
然后又把動(dòng)畫(huà)屬性的終值設(shè)為1:
var _conf = {
toValue: 1, //注意這里設(shè)置最終value都為1
duration: 1000
};
return timing(this.state[prop], _conf)
然后通過(guò)interpolate插值函數(shù)將 value 映射為正確的值:
translateY: this.state.bigDogeTransY.interpolate({
inputRange: [0, 1], //動(dòng)畫(huà)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)畫(huà)是異步執(zhí)行的,如果希望它們能以隊(duì)列的形式一個(gè)個(gè)逐步執(zhí)行,那么Animated.sequence 會(huì)是一個(gè)最好的實(shí)現(xiàn)。其語(yǔ)法如下:
Animated.sequence(Animates<Array>)
事實(shí)上了解了開(kāi)頭的 parallel 方法,后面幾個(gè)方法都是一樣套路了。
來(lái)個(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à)會(huì)在 草地出來(lái)的動(dòng)畫(huà)結(jié)束后才開(kāi)始執(zhí)行:
3.Animated.stagger
該方法為 sequence 的變異版,支持傳入一個(gè)時(shí)間參數(shù)來(lái)設(shè)置隊(duì)列動(dòng)畫(huà)間的延遲,即讓前一個(gè)動(dòng)畫(huà)結(jié)束后,隔一段指定時(shí)間才開(kāi)始執(zhí)行下一個(gè)動(dòng)畫(huà)。其語(yǔ)法如下:
Animated.stagger(delayTime<Number>, Animates<Array>)
其中 delayTime 為指定的延遲時(shí)間(毫秒),我們繼續(xù)拿前面的代碼來(lái)開(kāi)刀(為了給力點(diǎn)我們?cè)偌尤胍粋€(gè)running doge的動(dòng)畫(huà)事件):
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)畫(huà)間隔時(shí)間設(shè)定為 2000 毫秒,執(zhí)行效果如下:
4.Animated.delay
噢這個(gè)接口實(shí)在太簡(jiǎn)單了,就是設(shè)置一段動(dòng)畫(huà)的延遲時(shí)間,接收一個(gè)時(shí)間參數(shù)(毫秒)作為指定延遲時(shí)長(zhǎng):
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秒再開(kāi)始執(zhí)行動(dòng)畫(huà)
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)畫(huà)
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)畫(huà)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)畫(huà)的常用API了,對(duì)于本章的 Doge 動(dòng)畫(huà)我們?cè)俑銖?fù)雜一點(diǎn)——再加一只running doge,然后在動(dòng)畫(huà)結(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秒再開(kāi)始執(zhí)行動(dòng)畫(huà)
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)畫(huà)值為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)畫(huà)value輸入范圍
outputRange: [298, -200] //對(duì)應(yīng)的輸出范圍
})
},
{
rotateZ: this.state.bigDogeRotate.interpolate({
inputRange: [0, 1], //動(dòng)畫(huà)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>
);
}
}
View Code
最終效果如下:
寫(xiě)完這篇文章都凌晨了,我也是蠻拼的
共勉~
總結(jié)
以上是生活随笔為你收集整理的ReactNative入门 —— 动画篇(下)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: android studio 虚拟机ad
- 下一篇: Gson转换导致int转换成double