另外,我在Github上建立了1個(gè)倉(cāng)庫(kù)來(lái)搜集優(yōu)秀的React Native庫(kù)和優(yōu)秀的博客等
ReactNativeMaterials
目前,React Native的版本是0.28,主要的動(dòng)畫分為兩大類
目前React native的release速度還是比較快的,每隔2周左右就release1次。
本文默許讀者已
react-native init Demo --verbose
初始化了1個(gè)Demo項(xiàng)目1個(gè)最基本的Animated創(chuàng)建進(jìn)程以下
Animated.Value
,設(shè)置初始值,比如1個(gè)視圖的opacity
屬性,最開始設(shè)置Animated.Value(0)
,來(lái)表示動(dòng)畫的開始時(shí)候,視圖是全透明的。Animated.timing
來(lái)創(chuàng)建自動(dòng)的動(dòng)畫,或使用Animated.event
來(lái)根據(jù)手勢(shì),觸摸,Scroll的動(dòng)態(tài)更新動(dòng)畫的狀態(tài)(本文會(huì)側(cè)重講授Animated.timing
)Animated.timeing.start()
開始動(dòng)畫基于上述的原理,我們來(lái)實(shí)現(xiàn)第1個(gè)動(dòng)畫。
創(chuàng)建1個(gè)Demo工程的時(shí)候,運(yùn)行后,摹擬器截圖應(yīng)當(dāng)是醬紫的。
然后,只保存第1行文字,然后我們給這個(gè)默許的視圖創(chuàng)建fade in動(dòng)畫,效果以下
代碼
class Demo extends React.Component {
state: { //可以不寫,我這里寫是為了去除flow正告
fadeAnim: Object,
};
constructor(props) {
super(props);
this.state = {
fadeAnim: new Animated.Value(0), //設(shè)置初始值
};
}
componentDidMount() {
Animated.timing(
this.state.fadeAnim,//初始值
{toValue: 1}//結(jié)束值
).start();//開始
}
render() {
return (
<View style={styles.container}>
<Animated.Text style={{opacity: this.state.fadeAnim}}>//綁定到屬性
Welcome to React Native!
</Animated.Text>
</View>
);
}
}
所以說(shuō),簡(jiǎn)單的動(dòng)畫就是用Animated.Value
指定初始值,然后在Animated.timing
中設(shè)置結(jié)束值,其他的交給React native讓它自動(dòng)創(chuàng)建,我們只需要調(diào)用start
開始動(dòng)畫便可。
在當(dāng)前版本0.27種,可動(dòng)畫的視圖包括
static decay(value, config)
阻尼,將1個(gè)值根據(jù)阻尼系數(shù)動(dòng)畫到 0static timing(value, config
根據(jù)時(shí)間函數(shù)來(lái)處理,常見的比如線性,加速開始減速結(jié)束等等,支持自定義時(shí)間函數(shù)static spring(value, config)
彈性動(dòng)畫static add(a, b)
將兩個(gè)Animated.value
相加,返回1個(gè)新的static multiply(a, b)
將兩個(gè)Animated.value
相乘,返回1個(gè)新的static modulo(a, modulus)
,將a對(duì)modulus取余,類似操作符%static delay(time)
延遲1段時(shí)間static sequence(animations)
順次開始1組動(dòng)畫,后1個(gè)在前1個(gè)結(jié)束后才會(huì)開始,如果其中1個(gè)動(dòng)畫中途停止,則全部動(dòng)畫組停止static parallel(animations, config?)
,同時(shí)開始1組動(dòng)畫,默許1個(gè)動(dòng)畫中途停止,則全都停止。可以通過(guò)設(shè)置stopTogether
來(lái)重寫這1特性static stagger(time, animations)
,1組動(dòng)畫可以同時(shí)履行,但是會(huì)依照延遲順次開始static event(argMapping, config?)
,利用手勢(shì),Scroll來(lái)手動(dòng)控制動(dòng)畫的狀態(tài)static createAnimatedComponent(Component)
,自定義的讓某1個(gè)Component支持動(dòng)畫Value
,類型是AnimatedValue
,驅(qū)動(dòng)基本動(dòng)畫AnimatedValueXY
,類型是AnimatedValueXY
,驅(qū)動(dòng)2維動(dòng)畫1個(gè)AnimatedValue1次可以驅(qū)動(dòng)多個(gè)可動(dòng)畫屬性,但是1個(gè)AnimatedValue1次只能由1個(gè)機(jī)制驅(qū)動(dòng)。比如,1個(gè)Value可以同時(shí)動(dòng)畫View的透明度和位置,但是1個(gè)Value1次只能采取線性時(shí)間函數(shù)
constructor(value)
構(gòu)造器setValue(value)
直接設(shè)置值,會(huì)致使動(dòng)畫終止setOffset(offset)
設(shè)置當(dāng)前的偏移量flattenOffset()
將偏移量合并到最初值中,并把偏移量設(shè)為0,addListener(callback) ,removeListener(id),removeAllListeners()
,增加1個(gè)異步的動(dòng)畫監(jiān)聽者stopAnimation(callback?)
終止動(dòng)畫,并在動(dòng)畫結(jié)束后履行callbackinterpolate(config)
插值,在更新可動(dòng)畫屬性前用插值函數(shù)對(duì)當(dāng)前值進(jìn)行變換animate(animation, callback)
通常在React Native內(nèi)部使用stopTracking(),track(tracking)
通常在React Native內(nèi)部使用和AnimatedValue類似,用在2維動(dòng)畫,使用起來(lái)和AnimatedValue類似,這里不在介紹,這里是文檔。
有了上文的知識(shí)支持,我們可以設(shè)計(jì)并實(shí)現(xiàn)1個(gè)更加復(fù)雜的動(dòng)畫了。
效果
代碼(省略了import和style)
class Demo extends React.Component {
state: {
fadeAnim: Animated,
currentAlpha:number,
};
constructor(props) {
super(props);
this.state = {//設(shè)置初值
currentAlpha: 1.0,//標(biāo)志位,記錄當(dāng)前value
fadeAnim: new Animated.Value(1.0)
};
}
startAnimation(){
this.state.currentAlpha = this.state.currentAlpha == 1.0?0.0:1.0;
Animated.timing(
this.state.fadeAnim,
{toValue: this.state.currentAlpha}
).start();
}
render() {
return (
<View style={styles.container}>
<Animated.Text style={{opacity: this.state.fadeAnim, //透明度動(dòng)畫
transform: [//transform動(dòng)畫
{
translateY: this.state.fadeAnim.interpolate({
inputRange: [0, 1],
outputRange: [60, 0] //線性插值,0對(duì)應(yīng)60,0.6對(duì)應(yīng)30,1對(duì)應(yīng)0
}),
},
{
scale:this.state.fadeAnim
},
],
}}>
Welcome to React Native!
</Animated.Text>
<TouchableOpacity onPress = {()=> this.startAnimation()} style={styles.button}>
<Text>Start Animation</Text>
</TouchableOpacity>
</View>
);
}
}
通過(guò)上文的講授,相信讀者已對(duì)如何用Animated創(chuàng)建動(dòng)畫有了最基本的認(rèn)識(shí)。而有些時(shí)候,我們需要根據(jù)Scroll或手勢(shì)來(lái)手動(dòng)的控制動(dòng)畫的進(jìn)程。這就是我接下來(lái)要講的。
手動(dòng)控制動(dòng)畫的核心是Animated.event
,
這里的Aniamted.event的輸入是1個(gè)數(shù)組,用來(lái)做數(shù)據(jù)綁定
比如,
ScrollView中
onScroll={Animated.event(
[{nativeEvent: {contentOffset: {x: this.state.xOffset}}}]//把contentOffset.x綁定給this.state.xOffset
)}
Pan手勢(shì)中
onPanResponderMove: Animated.event([
null,//疏忽native event
{dx: this.state.pan.x, dy: this.state.pan.y},//dx,dy分別綁定this.state.pan.x和this.state.pan.y
])
目標(biāo)效果 - 隨著ScrollView的相左滑動(dòng),最左側(cè)的1個(gè)Image透明度逐步下降為0
核心代碼
var deviceHeight = require('Dimensions').get('window').height;
var deviceWidth = require('Dimensions').get('window').width;
class Demo extends React.Component {
state: {
xOffset: Animated,
};
constructor(props) {
super(props);
this.state = {
xOffset: new Animated.Value(1.0)
};
}
render() {
return (
<View style={styles.container}>
<ScrollView horizontal={true} //水平滑動(dòng)
showsHorizontalScrollIndicator={false}
style={{width:deviceWidth,height:deviceHeight}}//設(shè)置大小
onScroll={Animated.event(
[{nativeEvent: {contentOffset: {x: this.state.xOffset}}}]//把contentOffset.x綁定給this.state.xOffset
)}
scrollEventThrottle={100}//onScroll回調(diào)間隔
>
<Animated.Image source={require('./s1.jpg')}
style={{height:deviceHeight,
width:deviceWidth,
opacity:this.state.xOffset.interpolate({//映照到0.0,1.0之間
inputRange: [0,375],
outputRange: [1.0, 0.0]
}),}}
resizeMode="cover"
/>
<Image source={require('./s2.jpg')} style={{height:deviceHeight, width:deviceWidth}} resizeMode="cover" />
<Image source={require('./s3.jpg')} style={{height:deviceHeight, width:deviceWidth}} resizeMode="cover" />
</ScrollView>
</View>
);
}
}
React Native最經(jīng)常使用的手勢(shì)就是PanResponser,
由于本文側(cè)重講授動(dòng)畫,所以不會(huì)特別詳細(xì)的介紹PanResponser,僅僅介紹用到的幾個(gè)屬性和回調(diào)方法
onStartShouldSetPanResponder: (event, gestureState) => {}//是不是相應(yīng)pan手勢(shì)
onPanResponderMove: (event, gestureState) => {}//在pan移動(dòng)的時(shí)候進(jìn)行的回調(diào)
onPanResponderRelease: (event, gestureState) => {}//手離開屏幕
onPanResponderTerminate: (event, gestureState) => {}//手勢(shì)中斷
其中,
目標(biāo)效果- View隨著手拖動(dòng)而移動(dòng),手指離開會(huì)到原點(diǎn)
核心代碼
class Demo extends React.Component {
state:{
trans:AnimatedValueXY,
}
_panResponder:PanResponder;
constructor(props) {
super(props);
this.state = {
trans: new Animated.ValueXY(),
};
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true, //響應(yīng)手勢(shì)
onPanResponderMove: Animated.event(
[null, {dx: this.state.trans.x, dy:this.state.trans.y}] // 綁定動(dòng)畫值
),
onPanResponderRelease: ()=>{//手松開,回到原始位置
Animated.spring(this.state.trans,{toValue: {x: 0, y: 0}}
).start();
},
onPanResponderTerminate:()=>{//手勢(shì)中斷,回到原始位置
Animated.spring(this.state.trans,{toValue: {x: 0, y: 0}}
).start();
},
});
}
render() {
return (
<View style={styles.container}>
<Animated.View style={{width:100,
height:100,
borderRadius:50,
backgroundColor:'red',
transform:[
{translateY:this.state.trans.y},
{translateX:this.state.trans.x},
],
}}
{...this._panResponder.panHandlers}
>
</Animated.View>
</View>
);
}
}
LayoutAnimation在View由1個(gè)位置變化到另外一個(gè)位置的時(shí)候,在下1個(gè)Layout周期自動(dòng)創(chuàng)建動(dòng)畫。通常在setState前掉用LayoutAnimation.configureNext
代碼
class Demo extends React.Component {
state: {
marginBottom:number,
};
constructor(props) {
super(props);
this.state = {//設(shè)置初值
marginBottom:0
};
}
_textUp(){
LayoutAnimation.spring();
this.setState({marginBottom:this.state.marginBottom + 100})
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity onPress = {()=>this._textUp()}
style={{ width:120,
height:40,
alignItems:'center',
marginBottom:this.state.marginBottom,
justifyContent:'center',
backgroundColor:'#00ffff',
borderRadius:20}}>
<Text>Text UP</Text>
</TouchableOpacity>
</View>
);
}
}
其實(shí)代碼里只是調(diào)用了這1行LayoutAnimation.spring();
,布局修改的時(shí)候就顯得不那末僵硬了
//配置下1次切換的效果,其中config可配置的包括duration(時(shí)間),create(配置新的View),update(配置更新的View)
static configureNext(config, onAnimationDidEnd?)
//configureNext的方便方法
static create(duration, type, creationProp) #
對(duì)應(yīng)3種時(shí)間函數(shù)
easeInEaseOut: CallExpression #
linear: CallExpression #
spring: CallExpression #
我們先創(chuàng)建1個(gè)默許的Navigator轉(zhuǎn)場(chǎng)Demo
回拉的時(shí)候,前1個(gè)時(shí)圖的移動(dòng)距離要小于后1個(gè)視圖
這時(shí)候候的核心代碼以下,MainScreen和DetailScreen就是帶1個(gè)Button的視圖
class Demo extends React.Component{
render(){
return (
<Navigator
style = {styles.container}
initialRoute={{id:"main",}}
renderScene={this.renderNav}
configureScene={(route, routeStack) => Navigator.SceneConfigs.PushFromRight}
/>
);
}
renderNav(route,nav){
switch (route.id) {
case 'main':
return <MainScreen navigator={nav} title="Main"/ >;
case 'detail':
return (<DetailScreen navigator={nav} title="Detail"/ >);
}
}
}
Navigator的默許的轉(zhuǎn)場(chǎng)動(dòng)畫的實(shí)現(xiàn)都可以在這里找到NavigatorSceneConfigs.js。
So,我們有兩種方式來(lái)實(shí)現(xiàn)自定義的轉(zhuǎn)場(chǎng)動(dòng)畫
篇幅限制,本文只修改默許的轉(zhuǎn)場(chǎng)
比如,我想把默許的PushFromRight動(dòng)畫中,第1個(gè)視圖的移動(dòng)距離改成全屏幕。
var ToTheLeftCustom = {
transformTranslate: {
from: {x: 0, y: 0, z: 0},
to: {x: -SCREEN_WIDTH, y: 0, z: 0},//修改這1行
min: 0,
max: 1,
type: 'linear',
extrapolate: true,
round: PixelRatio.get(),
},
opacity: {
value: 1.0,
type: 'constant',
},
};
var baseInterpolators = Navigator.SceneConfigs.PushFromRight.animationInterpolators;
var customInterpolators = Object.assign({}, baseInterpolators, {
out: buildStyleInterpolator(ToTheLeftCustom),
});
var baseConfig = Navigator.SceneConfigs.PushFromRight;
var CustomPushfromRight = Object.assign({}, baseConfig, {
animationInterpolators: customInterpolators,
});
然后,修改Navigator的configScene
configureScene={(route, routeStack) => baseConfig}
這時(shí)候候的動(dòng)畫以下