一個應用要想生動豐富,不可能缺少動畫這種效果,React Native提供了兩個互補的動畫系統:用於全域性的佈局動畫LayoutAnimation,和用於建立更精細的互動控制的動畫Animated。
LayoutAnimation
直接使用
LayoutAnimation的動畫不是一個單獨的存在,比如,有兩個view,一個view放大,會把另外一個view擠走,這時,其實兩個view都是有動畫的,所以LayoutAnimation常用來更新flexbox佈局,因為它可以無需測量或者計算特定屬性就能直接產生動畫。尤其是當佈局變化可能影響到父節點(譬如“檢視更多”展開動畫既增加父節點的尺寸又會將位於本行之下的所有行向下推動)時,如果不使用LayoutAnimation,可能就需要顯式宣告元件的座標,才能使得所有受影響的元件能夠同步執行動畫。
我先上一下官方的程式碼,再進行逐行解釋:
import React, { Component } from 'react';
import {
AppRegistry,
Platform,
Text,
View,
NativeModules,
LayoutAnimation,
TouchableOpacity,
StyleSheet,
} from 'react-native';
const { UIManager } = NativeModules;
UIManager.setLayoutAnimationEnabledExperimental &&
UIManager.setLayoutAnimationEnabledExperimental(true);
export default class TestLayout extends Component {
state = {
w: 100,
h: 100,
};
_onPress = () => {
LayoutAnimation.spring();
this.setState({w: this.state.w + 15, h: this.state.h + 15})
}
render() {
return (
Press me!
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
box: {
width: 200,
height: 200,
backgroundColor: 'red',
},
button: {
backgroundColor: 'black',
paddingHorizontal: 20,
paddingVertical: 15,
marginTop: 15,
},
buttonText: {
color: '#fff',
fontWeight: 'bold',
},
});
AppRegistry.registerComponent('TestLayout', () => TestLayout); 複製程式碼
分析:
如果需要動畫適配android,需要加上UIManager.setLayoutAnimationEnabledExperimental &&
UIManager.setLayoutAnimationEnabledExperimental(true);
定義一個state
這個state
是元件內可變的,我們先定義一個初始寬高,接著將這個寬高,賦值給一個View,同時增加一個按鈕,按鈕的點選事件:
_onPress = () => {
LayoutAnimation.spring();
this.setState({w: this.state.w + 15, h: this.state.h + 15})
}複製程式碼
也就是將我們定義的初始寬高,增加15,由於我們已將state的屬性值設定給了View,所以,View的寬高,隨之改變。如下圖所示:
這種動畫增加很簡單,因為不用單獨給某個元件設定什麼,只需要LayoutAnimation.spring();
即可,當然還有LayoutAnimation. linear()
以及LayoutAnimation.easeInEaseOut()
對應的含義如下:
- spring //彈跳
- linear //線性
- easeInEaseOut //緩入緩出
引申
其實官方還提供了兩種方式,修改程式碼測試一下:
_onPress = () => {
// LayoutAnimation.easeInEaseOut();
requestAnimationFrame(()=>{
this.setState({w: this.state.w + 15, h: this.state.h + 15})
})
}複製程式碼
這種方式並不是RN提供的API,而是web的一種渲染模式,所以動畫顯得有些生硬,並且由於頻繁地銷燬、重繪,效能不好。
如果執意使用上述方式,可以稍微再做下修改,使用原生元件的 setNativeProps 方法來做對應實現,它會直接修改元件底層特性,不會重繪元件:
_onPress = () => {
// LayoutAnimation.easeInEaseOut();
requestAnimationFrame(()=>{
this.refs.box.setNativeProps({
style: {
width :this.state.w+15,
height : this.state.h+15
}
});
})
}複製程式碼
同時給view加個ref:
Press me!
複製程式碼
自定義
上面提到的動畫效果,如果不能滿足開發需求,還可以進行自定義:
_onPress = () => {
// LayoutAnimation.easeInEaseOut();
LayoutAnimation.configureNext({
duration: 200,
create: {
type: LayoutAnimation.Types.linear,
property: LayoutAnimation.Properties.opacity,
},
update: {
type: LayoutAnimation.Types.easeInEaseOut
}
});
this.setState({ w: this.state.w + 15, h: this.state.h + 15 });
}複製程式碼
這裡對引數進行一下說明,自定義動畫需要設定的引數主要是
- duration動畫持續的時長
- create建立一個新的動畫
- update 當檢視被更新的時候所使用的動畫
其中type是動畫型別包括spring,linear,easeInEaseOut,easeIn,easeOut,keyboard
其中property是動畫屬性包括opacity,scaleXY
Animated
Animated庫使得開發者可以非常容易地實現各種各樣的動畫和互動方式,並且具備極高的效能。Animated旨在以宣告的形式來定義動畫的輸入與輸出,在其中建立一個可配置的變化函式,然後使用簡單的start/stop方法來控制動畫按順序執行。
這個對於從Android轉向ReactNative開發的同學,還是很好理解的。
Animated僅封裝了四個可以動畫化的元件:View、Text、Image和ScrollView,不過你也可以使用Animated.createAnimatedComponent()來封裝你自己的元件。
單個動畫
現在修改一下上面使用的程式碼:
import React, { Component } from 'react';
import {
AppRegistry,
Platform,
Text,
View,
Animated,
ToastAndroid,
NativeModules,
LayoutAnimation,
TouchableOpacity,
StyleSheet,
} from 'react-native';
const { UIManager } = NativeModules;
UIManager.setLayoutAnimationEnabledExperimental &&
UIManager.setLayoutAnimationEnabledExperimental(true);
export default class TestLayout extends Component {
state = {
w: 100,
h: 100,
};
constructor(props) {
super(props);
this.state = {
fadeAnim: new Animated.Value(0), // 透明度初始值設為0
};
}
_onPress = () => {
Animated.timing( // 隨時間變化而執行的動畫型別
this.state.fadeAnim, // 動畫中的變數值
{
toValue: 1, // 透明度最終變為1,即完全不透明
}
).start();
}
render() {
return (
Test Animated!
Press me!
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
box: {
width: 200,
height: 200,
backgroundColor: 'red',
},
button: {
backgroundColor: 'black',
paddingHorizontal: 20,
paddingVertical: 15,
marginTop: 15,
},
buttonText: {
color: '#fff',
fontWeight: 'bold',
},
});
AppRegistry.registerComponent('TestLayout', () => TestLayout);複製程式碼
現在根據程式碼來解釋一下動畫是如何實現的。
在建構函式中建立Animated,出事狀態透明度為0
this.state = { fadeAnim: new Animated.Value(0), // 透明度初始值設為0 };複製程式碼
2.AnimatedValue繫結到Style的可動畫屬性
Test Animated! 複製程式碼3.使用Animated.timing來建立自動的動畫,當然也可以使用Animated.event來根據手勢,觸控更新動畫的狀態,這裡我們希望是動畫隨著時間的變化而變化,用timing,同時,在按鈕的點選事件中start動畫:
_onPress = () => { Animated.timing( // 隨時間變化而執行的動畫型別 this.state.fadeAnim, // 動畫中的變數值 { toValue: 1, // 透明度最終變為1,即完全不透明 } ).start(); }複製程式碼
同時我們也可以根據,需要配置一些其他係數,如時長:
_onPress = () => { Animated.timing( // 隨時間變化而執行的動畫型別 this.state.fadeAnim, // 動畫中的變數值 { toValue: 1, // 透明度最終變為1,即完全不透明 duration: 2000, } ).start(); }複製程式碼
組合動畫
組合動畫主要分為如下幾種: Animated.parallel、Animated.sequence、Animated.stagger、Animated.delay。
修改一下上面的例子,先看一下Animated.parallel(只貼出程式碼中不同的地方):constructor(props) { super(props); this.state = { transFirstAnim: new Animated.ValueXY({ x: 10, y: 20 }), transSecondAnim: new Animated.ValueXY({ x: 10, y: 40 }) }; } _onPress = () => { Animated.parallel([ Animated.timing(this.state.transFirstAnim, { toValue: { x : 30, y :30 }, duration: 2000, delay: 1000 } ), Animated.timing(this.state.transSecondAnim, { toValue: { x : 30, y :50 }, duration: 2000, delay: 1000 }) ]).start(); } render() { return (
Test Animated111! Test Animated222! Press me! 這樣執行觀看,可以發現,兩行字是以相同的速度一起開始運動的(抓到的動圖太大,不上傳了)。
同樣我們可以將 Animated.parallel改為Animated.sequence、Animated.stagger、Animated.delay進行測試。
- Animated.parallel 兩個動畫同時進行
- Animated.sequence 兩個動畫按順序進行
- Animated.stagger 接受一系列動畫陣列和一個延遲時間,按照序列,每隔一個延遲時間後執行下一個動畫(其實就是插入了delay的parrllel)
- Animated.delay 生成一個延遲時間(基於timing的delay引數生成)
總結
基本以上介紹的功能可以覆蓋大部分需求,當然還有插值,原生驅動等特性,暫時我還沒有用到,需要使用的朋友可以關注官網。
如果你也做ReactNative開發,並對ReactNative感興趣,歡迎關注我的公眾號,加入我們的討論群: