在上次隨筆(系列三)中,我試着用RN實現了一個Demo,感覺很不錯,當時遇到的問題這篇文章里基本都解決了,比如導航動畫問題,這篇文章里主要介紹RN的動畫,學會動畫以后,各種小創意都可以實現了^^
下面是我自己做的效果:
1、列表項滑動顯示操作按鈕
2、列表行滑出
3、K線頁面
4、轉場動畫
一、React Native動畫
RN的動畫實現類似Jquery和CSS3的動畫實現,設定某一屬性的輸入和輸出值,后台實現相關的動畫計算,簡化了動畫的開發,下面是一段代碼,可以直觀感受下
class Playground extends React.Component { constructor(props: any) { super(props); this.state = { bounceValue: new Animated.Value(0), //設定初始值 }; } render(): ReactElement { return ( <Animated.Image // Base: Image, Text, View,這三個標簽可以實現動畫,必須是Animate.開頭,普通的Image不能實現動畫。 source={{uri: 'http://i.imgur.com/XMKOH81.jpg'}} style={{ flex: 1, transform: [ // `transform` 類似CSS3 {scale: this.state.bounceValue}, // 設置 `bounceValue` 給 `scale`,注意,這里要使用上面再state里創建的對象,不能重新實例一個對象賦值。 ] }} /> ); } //UI渲染結束后執行該方法 componentDidMount() { this.state.bounceValue.setValue(1.5); // 設定初始大小,使用的依舊是在state中創建的對象。 Animated.spring( // Base: spring, decay, timing,這個三個方法是三種動畫,spring是類似彈簧的動畫 this.state.bounceValue, // Animate `bounceValue`初始輸入值 { toValue: 0.8, // Animate to smaller size,動畫目標值 friction: 1, // Bouncier spring,這個值類似設置彈簧的彈性。 } ).start(); // Start the animation,開始動畫,start的參數是一個動畫結束的callback。 } }
RN動畫的主要類庫是Animated庫,Animated庫包括Value和ValueXY兩個類型,Value是單值的動畫,比較簡單,就是某個屬性值的改變,ValueXY是向量的動畫,就是兩個值的改變,參數X,Y是元素的坐標,根據XY的變化計算運動方向。目前支持動畫的元素View
, Text
, and Image,用戶可以自己創建動畫元素,通過Animated.createAnimatedComponent來創建。
目前主要有三種動畫實現:spring, decay, timing,
spring
: 類似彈簧的實現,動畫結束時會有個漸弱的擺動,上面的第一個和第二個動畫就是用spring實現。friction
: 數值越小,彈的越厲害,默認是7。tension
: 動畫的速度,默認是40。
decay
: 動畫的速度逐漸變慢,最后停止,類似上面的轉場動畫。velocity
: 初始速度。deceleration
: 減速度,默認是 0.997.
timing
:線性動畫,在設定時間內移動到終點,中間的動畫可以設置,類似CSS3的animation-timing-functionduration
: 動畫持續時間 500.easing
:類似css3的animation-timing-function,具體的實現在Easing模塊里,是貝塞爾曲線(Bézier curve)的一個實現,這樣使用 Easing.bezier(0.42, 0, 1, 1),參數設置參考這個網址:http://cubic-bezier.com/,默認的實現Easing.in和Easing.out和Easing.inOut。delay
: 動畫延遲開始時間。
組合動畫:
如果需要同時執行多個動畫,或者按順序執行動畫,就需要將動畫組合執行,RN提供了parallel
, sequence
, stagger
, delay
, 這個四個方法組合動畫,這些方法的參數都是一個動畫的數組,使用起來很簡單,parallel是並行執行
, sequence是順序執行
, stagger是每個動畫延遲一段時間執行
, delay是延時
,下面是代碼示例:
Animated.sequence([ // spring to start and twirl after decay finishes Animated.decay(position, { // coast to a stop velocity: {x: gestureState.vx, y: gestureState.vy}, // velocity from gesture release deceleration: 0.997, }), Animated.parallel([ // after decay, in parallel: Animated.spring(position, { toValue: {x: 0, y: 0} // return to start }), Animated.timing(twirl, { // and twirl toValue: 360, }), ]), ]).start(); // start the sequence group
關於RN動畫更多內容參考:http://facebook.github.io/react-native/docs/animations.html#content
二、動畫demo
我做了一個動畫的demo,這里只是改變了元素的left屬性,大家體驗下:

/* @flow */ 'use strict'; var React = require('react-native'); var Easing = require('Easing'); var { TouchableWithoutFeedback, StyleSheet, View, Image, Text, Animated, Dimensions, InteractionManager } = React; var SCREEN_WIDTH = Dimensions.get('window').width; var Component = React.createClass({ getInitialState: function() { return { first:new Animated.Value(0), second:new Animated.Value(0), three:new Animated.Value(0) }; }, reset:function (argument) { this.state.first.setValue(0); this.state.second.setValue(0); this.state.three.setValue(0); }, getAnimations:function (argument) { return[ Animated.spring( // Base: spring, decay, timing this.state.first, // Animate `bounceValue` { toValue: SCREEN_WIDTH-50, // Animate to smaller size friction: 7, tension:40 // Bouncier spring } ), Animated.decay( // Base: spring, decay, timing this.state.second, // Animate `bounceValue` { //toValue: SCREEN_WIDTH-50, // Animate to smaller size velocity: 1, deceleration:0.997 // Bouncier spring } ), Animated.timing( // Base: spring, decay, timing this.state.three, // Animate `bounceValue` { toValue: SCREEN_WIDTH-50, // Animate to smaller size easing: Easing.inOut(Easing.ease), delay:0 // Bouncier spring } ) ]; }, onStagger:function (argument) { this.reset(); Animated.stagger(150,this.getAnimations()).start(); }, onParallel:function (argument) { this.reset(); Animated.parallel(this.getAnimations()).start(); }, onSequence:function (argument) { this.reset(); Animated.sequence(this.getAnimations()).start(); }, onAll:function (argument) { var me=this; this.reset(); Animated.sequence([ Animated.stagger(50,me.getAnimations()), Animated.sequence([ Animated.spring( // Base: spring, decay, timing this.state.first, // Animate `bounceValue` { toValue: 0, // Animate to smaller size friction: 7, tension:40 // Bouncier spring } ), Animated.decay( // Base: spring, decay, timing this.state.second, // Animate `bounceValue` { //toValue: SCREEN_WIDTH-50, // Animate to smaller size velocity: -1, deceleration:0.997 // Bouncier spring } ), Animated.timing( // Base: spring, decay, timing this.state.three, // Animate `bounceValue` { toValue: 0, // Animate to smaller size easing: Easing.bezier(.13,.93,.79,.07), delay:0 // Bouncier spring } ) ]), Animated.parallel(me.getAnimations()) ]).start(); }, render: function() { return ( <View style={styles.container}> <View style={{flex:1}}> <Animated.View style={[styles.view,{top:50,left:this.state.first}]}><Text>spring</Text></Animated.View> <Animated.View style={[styles.view,{top:150,left:this.state.second}]}><Text>decay</Text></Animated.View> <Animated.View style={[styles.view,{top:250,left:this.state.three}]}><Text>timing</Text></Animated.View> </View> <View style={{flexDirection:'row',height:80,justifyContent :'center',alignItems: 'center'}}> <TouchableWithoutFeedback onPress={this.onStagger}> <View style={styles.btn}><Text>stagger</Text></View> </TouchableWithoutFeedback> <TouchableWithoutFeedback onPress={this.onParallel}> <View style={styles.btn}><Text>parallel</Text></View> </TouchableWithoutFeedback> <TouchableWithoutFeedback onPress={this.onSequence}> <View style={styles.btn}><Text>sequence</Text></View> </TouchableWithoutFeedback> <TouchableWithoutFeedback onPress={this.onAll}> <View style={styles.btn}><Text>組合</Text></View> </TouchableWithoutFeedback> </View> </View> ); } }); var styles = StyleSheet.create({ container:{ flex:1 }, view:{ position:'absolute', backgroundColor:'red', width:50, height:50, justifyContent :'center', alignItems: 'center', }, btn:{ width:100, height:50, backgroundColor:'red', justifyContent :'center', alignItems: 'center', margin:5 } }); module.exports = Component;
三、性能優化
主要是在執行動畫時,避免執行其他工作量比較大的代碼,比如,最好不要一邊渲染,一邊執行動畫,而是先執行動畫,等動畫執行結束后在渲染,可以setTimeout來延時執行渲染,最好是用官方推薦的做法,利用InteractionManager,下面是代碼示例:
componentDidMount() { InteractionManager.runAfterInteractions(() => { this.setState({renderPlaceholderOnly: false}); }); }
InteractionManager.runAfterInteractions是在動畫或者操作結束后執行,還有其他兩種方法:
- requestAnimationFrame(): H5的標准,RN實現了它,下一幀渲染時執行,更好的利用瀏覽器的刷新頻率,避免丟幀。
- setImmediate/setTimeout(): 定時執行,有可能影響動畫的流暢度。
另外,這個項目里用了MPAndroidChart組件,我對MPAndroidChart做了橋接,有想用的用戶可以試試這個項目:
https://github.com/hongyin163/react-native-chart-android
如果有想體驗React Native的用戶,可以下載慢牛APP的APK體驗:
關注慢牛的公眾號:發送react,返回apk下載鏈接,apk大小8M,最好連接WiFi下載。
最后,歡迎園友提出好的想法,評論留名!謝謝!