另外,我在Github上建立了1個倉庫來搜集優秀的React Native庫和優秀的博客等
ReactNativeMaterials
目前,React Native的版本是0.28,主要的動畫分為兩大類
目前React native的release速度還是比較快的,每隔2周左右就release1次。
本文默許讀者已
react-native init Demo --verbose
初始化了1個Demo項目1個最基本的Animated創建進程以下
Animated.Value
,設置初始值,比如1個視圖的opacity
屬性,最開始設置Animated.Value(0)
,來表示動畫的開始時候,視圖是全透明的。Animated.timing
來創建自動的動畫,或使用Animated.event
來根據手勢,觸摸,Scroll的動態更新動畫的狀態(本文會側重講授Animated.timing
)Animated.timeing.start()
開始動畫基于上述的原理,我們來實現第1個動畫。
創建1個Demo工程的時候,運行后,摹擬器截圖應當是醬紫的。
然后,只保存第1行文字,然后我們給這個默許的視圖創建fade in動畫,效果以下
代碼
class Demo extends React.Component {
state: { //可以不寫,我這里寫是為了去除flow正告
fadeAnim: Object,
};
constructor(props) {
super(props);
this.state = {
fadeAnim: new Animated.Value(0), //設置初始值
};
}
componentDidMount() {
Animated.timing(
this.state.fadeAnim,//初始值
{toValue: 1}//結束值
).start();//開始
}
render() {
return (
<View style={styles.container}>
<Animated.Text style={{opacity: this.state.fadeAnim}}>//綁定到屬性
Welcome to React Native!
</Animated.Text>
</View>
);
}
}
所以說,簡單的動畫就是用Animated.Value
指定初始值,然后在Animated.timing
中設置結束值,其他的交給React native讓它自動創建,我們只需要調用start
開始動畫便可。
在當前版本0.27種,可動畫的視圖包括
static decay(value, config)
阻尼,將1個值根據阻尼系數動畫到 0static timing(value, config
根據時間函數來處理,常見的比如線性,加速開始減速結束等等,支持自定義時間函數static spring(value, config)
彈性動畫static add(a, b)
將兩個Animated.value
相加,返回1個新的static multiply(a, b)
將兩個Animated.value
相乘,返回1個新的static modulo(a, modulus)
,將a對modulus取余,類似操作符%static delay(time)
延遲1段時間static sequence(animations)
順次開始1組動畫,后1個在前1個結束后才會開始,如果其中1個動畫中途停止,則全部動畫組停止static parallel(animations, config?)
,同時開始1組動畫,默許1個動畫中途停止,則全都停止。可以通過設置stopTogether
來重寫這1特性static stagger(time, animations)
,1組動畫可以同時履行,但是會依照延遲順次開始static event(argMapping, config?)
,利用手勢,Scroll來手動控制動畫的狀態static createAnimatedComponent(Component)
,自定義的讓某1個Component支持動畫Value
,類型是AnimatedValue
,驅動基本動畫AnimatedValueXY
,類型是AnimatedValueXY
,驅動2維動畫1個AnimatedValue1次可以驅動多個可動畫屬性,但是1個AnimatedValue1次只能由1個機制驅動。比如,1個Value可以同時動畫View的透明度和位置,但是1個Value1次只能采取線性時間函數
constructor(value)
構造器setValue(value)
直接設置值,會致使動畫終止setOffset(offset)
設置當前的偏移量flattenOffset()
將偏移量合并到最初值中,并把偏移量設為0,addListener(callback) ,removeListener(id),removeAllListeners()
,增加1個異步的動畫監聽者stopAnimation(callback?)
終止動畫,并在動畫結束后履行callbackinterpolate(config)
插值,在更新可動畫屬性前用插值函數對當前值進行變換animate(animation, callback)
通常在React Native內部使用stopTracking(),track(tracking)
通常在React Native內部使用和AnimatedValue類似,用在2維動畫,使用起來和AnimatedValue類似,這里不在介紹,這里是文檔。
有了上文的知識支持,我們可以設計并實現1個更加復雜的動畫了。
效果
代碼(省略了import和style)
class Demo extends React.Component {
state: {
fadeAnim: Animated,
currentAlpha:number,
};
constructor(props) {
super(props);
this.state = {//設置初值
currentAlpha: 1.0,//標志位,記錄當前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, //透明度動畫
transform: [//transform動畫
{
translateY: this.state.fadeAnim.interpolate({
inputRange: [0, 1],
outputRange: [60, 0] //線性插值,0對應60,0.6對應30,1對應0
}),
},
{
scale:this.state.fadeAnim
},
],
}}>
Welcome to React Native!
</Animated.Text>
<TouchableOpacity onPress = {()=> this.startAnimation()} style={styles.button}>
<Text>Start Animation</Text>
</TouchableOpacity>
</View>
);
}
}
通過上文的講授,相信讀者已對如何用Animated創建動畫有了最基本的認識。而有些時候,我們需要根據Scroll或手勢來手動的控制動畫的進程。這就是我接下來要講的。
手動控制動畫的核心是Animated.event
,
這里的Aniamted.event的輸入是1個數組,用來做數據綁定
比如,
ScrollView中
onScroll={Animated.event(
[{nativeEvent: {contentOffset: {x: this.state.xOffset}}}]//把contentOffset.x綁定給this.state.xOffset
)}
Pan手勢中
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
])
目標效果 - 隨著ScrollView的相左滑動,最左側的1個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} //水平滑動
showsHorizontalScrollIndicator={false}
style={{width:deviceWidth,height:deviceHeight}}//設置大小
onScroll={Animated.event(
[{nativeEvent: {contentOffset: {x: this.state.xOffset}}}]//把contentOffset.x綁定給this.state.xOffset
)}
scrollEventThrottle={100}//onScroll回調間隔
>
<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最經常使用的手勢就是PanResponser,
由于本文側重講授動畫,所以不會特別詳細的介紹PanResponser,僅僅介紹用到的幾個屬性和回調方法
onStartShouldSetPanResponder: (event, gestureState) => {}//是不是相應pan手勢
onPanResponderMove: (event, gestureState) => {}//在pan移動的時候進行的回調
onPanResponderRelease: (event, gestureState) => {}//手離開屏幕
onPanResponderTerminate: (event, gestureState) => {}//手勢中斷
其中,
目標效果- View隨著手拖動而移動,手指離開會到原點
核心代碼
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, //響應手勢
onPanResponderMove: Animated.event(
[null, {dx: this.state.trans.x, dy:this.state.trans.y}] // 綁定動畫值
),
onPanResponderRelease: ()=>{//手松開,回到原始位置
Animated.spring(this.state.trans,{toValue: {x: 0, y: 0}}
).start();
},
onPanResponderTerminate:()=>{//手勢中斷,回到原始位置
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個位置變化到另外一個位置的時候,在下1個Layout周期自動創建動畫。通常在setState前掉用LayoutAnimation.configureNext
代碼
class Demo extends React.Component {
state: {
marginBottom:number,
};
constructor(props) {
super(props);
this.state = {//設置初值
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>
);
}
}
其實代碼里只是調用了這1行LayoutAnimation.spring();
,布局修改的時候就顯得不那末僵硬了
//配置下1次切換的效果,其中config可配置的包括duration(時間),create(配置新的View),update(配置更新的View)
static configureNext(config, onAnimationDidEnd?)
//configureNext的方便方法
static create(duration, type, creationProp) #
對應3種時間函數
easeInEaseOut: CallExpression #
linear: CallExpression #
spring: CallExpression #
我們先創建1個默許的Navigator轉場Demo
回拉的時候,前1個時圖的移動距離要小于后1個視圖
這時候候的核心代碼以下,MainScreen和DetailScreen就是帶1個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的默許的轉場動畫的實現都可以在這里找到NavigatorSceneConfigs.js。
So,我們有兩種方式來實現自定義的轉場動畫
篇幅限制,本文只修改默許的轉場
比如,我想把默許的PushFromRight動畫中,第1個視圖的移動距離改成全屏幕。
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}
這時候候的動畫以下
上一篇 BT是怎么下載的