學習和實踐react已經有一段時間了,在經歷了從最初的彷徨到解決痛點時的興奮,再到不斷實踐後遭遇問題時的苦悶,確實被這一種新的思惟方式和開發模式所折服,react不是萬能的,在不少場景下濫用反而會拔苗助長,這裏不展開討論。
有了react的實踐經驗,結合以前本身的一點ios開發經驗,決定繼續冒險,開始react-native學習和實踐,目前主要是從常規的native功能入手,逐步用react-native實現,基礎知識如開發環境搭建、調試工具等官方文檔有很清楚的指引,再也不贅述,這裏主要是想把實際學習實踐中遇到的坑或者有意思的經歷記錄下來,爲廣大react-native初學者提供一點參考。O(∩_∩)O~javascript
話很少說,進入正題,今天要實現的是一個加載動畫,效果以下:
很簡單一個動畫,不是麼?用native實現實在是小菜一碟,如今咱們試着用RN來實現它!
先將動畫的視圖結構搭建出來,這個比較簡單,就是4個會變形的View順序排列:html
<View style={styles.square}> <Animated.View style={[styles.line,{height:this.state.fV}]}></Animated.View> <Animated.View style={[styles.line,{height:this.state.sV}]}></Animated.View> <Animated.View style={[styles.line,{height:this.state.tV}]}></Animated.View> <Animated.View style={[styles.line,{height:this.state.foV}]}></Animated.View> </View>
這裏的視圖結構很普通,只不過在RN中,須要施加動畫的視圖,都不能是普通的View,而是Animated.View,包括施加動畫的圖片,也應該是Animated.Image,須要注意。
RN繼承了react的核心思想,基於虛擬DOM和數據驅動的模式,用state來管理視圖層,因此RN的動畫和react的動畫相似,都是經過改變state從而執行render進行視圖重繪,展示動畫。
毫無疑問,先從Animated庫下手,這是facebook官方提供的專門用於實現動畫的庫,它比較強大,集成了多種常見的動畫形式,正如官方文檔寫道:java
Animated focuses on declarative relationships between inputs and outputs, with configurable transforms in between, and simple start/stop methods to control time-based animation execution.react
它專一於輸入和輸出之間的對應關係,其間是能夠配置的各類變形,經過簡單的開始和中止方法來控制基於時間的動畫。
因此使用這個庫的時候,須要清楚知道動畫的輸入值,不過這並不表明須要知道每個時刻動畫的精確屬性值,由於這是一種插值動畫,Animated只須要知道初始值和結束值,它會將全部中間值動態計算出來運用到動畫中,這有點相似於CSS3中的關鍵幀動畫。它提供了spring、decay、timing三種動畫方式,其實這也就是三種不一樣的差值方式,指定相同的初始值和結束值,它們會以不一樣的函數計算中間值並運用到動畫中,最終輸出的就是三種不一樣的動畫,好比官方給出的示例:ios
class Playground extends React.Component { constructor(props: any) { super(props); this.state = { bounceValue: new Animated.Value(0),//這裏設定了動畫的輸入初始值,注意不是數字0 }; } render(): ReactElement { return ( <Animated.Image //這裏不是普通Image組件 source={{uri: 'http://i.imgur.com/XMKOH81.jpg'}} style={{ flex: 1, transform: [ //添加變換,transform的值是數組,包含一系列施加到對象上的變換 {scale: this.state.bounceValue}, // 變換是縮放,縮放值state裏的bounceValue,這個值是一個動態值,也是動畫的根源 ] }} /> ); } componentDidMount() { this.state.bounceValue.setValue(1.5); // 組件加載的時候設定bounceValue,所以圖片會被放大1.5倍 Animated.spring( //這裏運用的spring方法,它的差值方式不是線性的,會呈現彈性的效果 this.state.bounceValue, //spring方法的第一個參數,表示被動態插值的變量 { toValue: 0.8, //這裏就是輸入值的結束值 friction: 1, //這裏是spring方法接受的特定參數,表示彈性係數 } ).start();// 開始spring動畫 } }
能夠想象該動畫效果大體爲:圖片首先被放大1.5倍呈現出來,而後以彈性方式縮小到0.8倍。這裏的start方法還能夠接收一個參數,參數是一個回調函數,在動畫正常執行完畢以後,會調用這個回調函數。
Animated庫不只有spring/decay/timing三個方法提供三種動畫,還有sequence/decay/parallel等方法來控制動畫隊列的執行方式,好比多個動畫順序執行或者同時進行等。
介紹完了基礎知識,咱們開始探索這個實際動畫的開發,這個動畫須要動態插值的屬性其實很簡單,只有四個視圖的高度值,其次,也不須要特殊的彈性或者緩動效果。因此咱們只須要將每一個視圖的高度依次變化,就能夠了,so easy!
開始嘗試:git
Animated.timing( this.state.fV, { toValue: 100, duration:500, delay:500, } ).start(); Animated.timing( this.state.sV, { toValue: 100, duration:1000, delay:1000, } ).start(); Animated.timing( this.state.tV, { toValue: 100, duration:1000, delay:1500, } ).start(); Animated.timing( this.state.foV, { toValue: 100, duration:1000, delay:2000, } ).start();
WTF!
雖然動畫動起來了,可是這根本就是四根火柴在作廣播體操。。。
而且一個更嚴重的問題是,動畫運行完,就中止了。。。,而loading動畫應該是循環的,在查閱了文檔及Animated源碼以後,沒有找到相似loop這種控制循環的屬性,無奈之下,只能另闢蹊徑了。
上文提到過,Animated動畫的start方法能夠在動畫完成以後執行回調函數,若是動畫執行完畢以後再執行本身,就實現了循環,所以,將動畫封裝成函數,而後循環調用自己就能夠了,不過目前動畫還只把高度變矮了,沒有從新變高的部分,所以即便循環也不會有效果,動畫部分也須要修正:github
...//其餘部分代碼 loopAnimation(){ Animated.parallel([//最外層是一個並行動畫,四個視圖的動畫以不一樣延遲並行運行 Animated.sequence([//這裏是一個順序動畫,針對每一個視圖有兩個動畫:縮小和還原,他們依次進行 Animated.timing(//這裏是縮小動畫 this.state.fV, { toValue: Utils.getRealSize(100), duration:500, delay:0, } ), Animated.timing(//這裏是還原動畫 this.state.fV, { toValue: Utils.getRealSize(200), duration:500, delay:500,//注意這裏的delay恰好等於duration,也就是縮小以後,就開始還原 } ) ]), ...//後面三個數值的動畫相似,依次加大delay就能夠 ]).start(this.loopAnimation2.bind(this)); } ...
效果粗來了!
怎麼說呢,動畫是粗來了,基本實現了循環動畫,可是總以爲缺乏那麼點靈(sao)動(qi),仔細分析會發現,這是由於咱們的循環的實現是經過執行回調來實現的,當parallel執行完畢以後,會執行回調進行第二次動畫,也就是說parallel不執行完畢,第二遍是不會開始的,這就是爲何動畫會略顯僵硬,所以仔細觀察,第一個條塊在執行完本身的縮小放大動畫後,只有在等到第四個條也完成縮小放大動畫,整個並行隊列纔算執行完,回調纔會被執行,第二遍動畫纔開始。
So,回調能被提早執行嗎?
Nooooooooooooooooooooop!
多麼感人,眼角貌似有翔滑過。。。。。
可是,不哭站擼的程序猿是不會輕易折服的,在屢次查閱Animated文檔以後,無果,累覺不愛(或許咱們並不合適)~~~
好在facebook還提供了另外一個更基礎的requestAnimationFrame函數,熟悉canvas動畫的同窗對它應該不陌生,這是一個動畫重繪中常常遇到的方法,動畫的最基本原理就是重繪,經過在每次繪製的時候改變元素的位置或者其餘屬性使得元素在肉眼看起來動起來了,所以,在碰壁以後,咱們嘗試用它來實現咱們的動畫。
其實,用requestAnimationFrame來實現動畫,就至關於須要咱們本身來作插值,經過特定方式動態計算出中間值,將這些中間值賦值給元素的高度,就實現了動畫。
這四個動畫是徹底相同的,只是以必定延遲順序進行的,所以分解以後只要實現一個就能夠了,每一個動畫就是條塊的高度隨時間呈現規律變化:
大概就介麼個意思。這是一個分段函數,弄起來比較複雜,咱們能夠將其近似成至關接近的連續函數--餘弦函數,這樣就至關輕鬆了:web
let animationT=0;//定義一個全局變量來標示動畫時間 let animationN=50,//餘弦函數的極值倍數,即最大偏移值範圍爲正負50 animationM=150;//餘弦函數偏移值,使得極值在100-200之間 componentDidMount(){ animationT=0; requestAnimationFrame(this.loopAnimation.bind(this));//組件加載以後就執行loopAnimation動畫 } loopAnimation(){ var t0=animationT,t1=t0+0.5,t2=t1+0.5,t3=t2+timeDelay,t4=t3+0.5;//這裏分別是四個動畫的當前時間,依次加上了0.5的延遲 var v1=Number(Math.cos(t0).toFixed(2))*animationN+animationM;//將cos函數的小數值只精確到小數點2位,提升運算效率 var v2=Number(Math.cos(t1).toFixed(2))*animationN+animationM; var v3=Number(Math.cos(t2).toFixed(2))*animationN+animationM; var v4=Number(Math.cos(t3).toFixed(2))*animationN+animationM; this.setState({ fV:v1, sV:v2, tV:v3, foV:v4 }); animationT+=0.35;//增長時間值,每次增值越大動畫越快 requestAnimationFrame(this.loopAnimation.bind(this)); }
最終效果:
能夠看出,至關靈(sao)動(qi),由此也能夠一窺RN的性能,咱們知道,RN中的JS是運行在JavaScriptCore環境中的,對大多數React Native應用來講,業務邏輯是運行在JavaScript線程上的。這是React應用所在的線程,也是發生API調用,以及處理觸摸事件等操做的線程。更新數據到原生支持的視圖是批量進行的,而且在事件循環每進行一次的時候被髮送到原生端,這一步一般會在一幀時間結束以前處理完(若是一切順利的話)。能夠看出,咱們在每一幀都進行了運算並改變了state,這是在JavaScript線程上進行的,而後經過RN推送到native端實時渲染每一幀,說實話,最開始對動畫的性能仍是比較擔心的,如今看來還算不錯,不過這只是一個很簡單的動畫,須要繪製的東西不多,在實際app應用中,仍是須要結合實際狀況不斷優化。
這個動畫應該還有更好更便捷的實現方式,這裏拋磚引玉,但願你們可以在此基礎上探索出性能更好的實現方式並分享出來。
好了,此次動畫初探就到這裏,隨着學習和實踐的深刻,還會陸續推出一系列分享,敬請關注。spring