流暢、有意義的動畫對于移動應用使用者體驗來說是非常必要的。和react native的其他部分一樣,動畫api也還在積極開發中,不過我們已經可以聯合使用兩個互補的系統:用于全局的布局動畫<code>layoutanimation</code>,和用于建立更精細的互動控制的動畫<code>animated</code>。
<code>animated</code>庫使得開發者可以非常容易地實作各種各樣的動畫和互動方式,并且具備極高的性能。<code>animated</code>僅關注動畫的輸入與輸出聲明,在其中建立一個可配置的變化函數,然後使用簡單的<code>start/stop</code>方法來控制動畫按順序執行。下面是一個在加載時帶有簡單的彈跳動畫的元件示例:
<code>bouncevalue</code>在構造函數中初始化為<code>state</code>的一部分,然後和圖檔的縮放比例進行綁定。在動畫執行的背後,其數值會被不斷的計算并用于設定縮放比例。當元件剛剛挂載的時候,縮放比例被設定到1.5。然後緊跟着在<code>bouncevalue</code>上執行了一個彈跳動畫(spring),會逐幀重新整理數值,并同步更新所有依賴本數值的綁定(在這個例子裡,就是圖檔的縮放比例)。比起調用<code>setstate</code>然後重新渲染,這一運作過程要快得多。因為整個配置都是聲明式的,我們可以實作更進一步的優化,隻要序列化好配置,然後我們可以在一個高優先級的線程執行動畫。
大部分你需要的東西都來自<code>animated</code>子產品。它包括兩個值類型,<code>value</code>用于單個的值,而<code>valuexy</code>用于向量值;還包括三種動畫類型,<code>spring</code>,<code>decay</code>,還有<code>timing</code>,以及三種元件類型,<code>view</code>,<code>text</code>和<code>image</code>。你可以使用<code>animated.createanimatedcomponent</code>方法來對其它類型的元件建立動畫。
這三種動畫類型可以用來建立幾乎任何你需要的動畫曲線,因為它們每一個都可以被自定義:
<code>friction</code>: 摩擦力,預設為7.
<code>tension</code>: 張力,預設40。
<code>decay</code>: 以一個初始速度開始并且逐漸減慢停止。
<code>velocity</code>: 起始速度,必填參數。
<code>deceleration</code>: 速度衰減比例,預設為0.997。
<code>timing</code>: 從時間範圍映射到漸變的值。
<code>duration</code>: 動畫持續的時間(機關是毫秒),預設為500。
<code>easing</code>:一個用于定義曲線的漸變函數。閱讀<code>easing</code>子產品可以找到許多預定義的函數。ios預設為<code>easing.inout(easing.ease)</code>。
<code>delay</code>: 在一段時間之後開始動畫(機關是毫秒),預設為0。
動畫可以通過調用<code>start</code>方法來開始。<code>start</code>接受一個回調函數,當動畫結束的時候會調用此回調函數。如果動畫是因為正常播放完成而結束的,回調函數被調用時的參數為<code>{finished: true}</code>,但若動畫是在結束之前被調用了<code>stop</code>而結束(可能是被一個手勢或者其它的動畫打斷),它會收到參數<code>{finished: false}</code>。
多個動畫可以通過<code>parallel</code>(同時執行)、<code>sequence</code>(順序執行)、<code>stagger</code>和<code>delay</code>來組合使用。它們中的每一個都接受一個要執行的動畫數組,并且自動在适當的時候調用start/stop。舉個例子:
預設情況下,如果任何一個動畫被停止或中斷了,組内所有其它的動畫也會被停止。parallel有一個<code>stoptogether</code>屬性,如果設定為<code>false</code>,可以禁用自動停止。
<code>animated</code> api還有一個很強大的部分就是<code>interpolate</code>插值函數。它可以接受一個輸入區間,然後将其映射到另一個的輸出區間。下面是一個一個簡單的從0-1區間到0-100區間的映射示例:
<code>interpolate</code>還支援定義多個區間段落,常用來定義靜止區間等。舉個例子,要讓輸入在接近-300時取相反值,然後在輸入接近-100時到達0,然後在輸入接近0時又回到1,接着一直到輸入到100的過程中逐漸回到0,最後形成一個始終為0的靜止區間,對于任何大于100的輸入都傳回0。具體寫法如下:
它的最終映射結果如下:
輸入
輸出
-400
450
-300
300
-200
150
-100
-50
0.5
1
50
100
101
200
<code>interpolation</code>還支援任意的漸變函數,其中有很多已經在<code>easing</code>類中定義了,包括二次、指數、貝塞爾等曲線以及step、bounce等方法。<code>interpolation</code>還支援限制輸出區間<code>outputrange</code>。你可以通過設定<code>extrapolate</code>、<code>extrapolateleft</code>或<code>extrapolateright</code>屬性來限制輸出區間。預設值是<code>extend</code>(允許超出),不過你可以使用<code>clamp</code>選項來阻止輸出值超過<code>outputrange</code>。
動畫中所設的值還可以通過跟蹤别的值得到。你隻要把tovalue設定成另一個動态值而不是一個普通數字就行了。比如我們可以用彈跳動畫來實作聊天頭像的閃動,又比如通過<code>timing</code>設定<code>duration:0</code>來實作快速的跟随。他們還可以使用插值來進行組合:
<code>valuexy</code>是一個友善的處理2d互動的辦法,譬如旋轉或拖拽。它是一個簡單的包含了兩個<code>animated.value</code>執行個體的包裝,然後提供了一系列輔助函數,使得<code>valuexy</code>在許多時候可以替代<code>value</code>來使用。比如在上面的代碼片段中,<code>leader</code>和<code>follower</code>可以同時為<code>valuexy</code>類型,這樣x和y的值都會被跟蹤。
<code>animated.event</code>是animated api中與輸入有關的部分,允許手勢或其它事件直接綁定到動态值上。它通過一個結構化的映射文法來完成,使得複雜事件對象中的值可以被正确的解開。第一層是一個數組,允許同時映射多個值,然後數組的每一個元素是一個嵌套的對象。在下面的例子裡,你可以發現<code>scrollx</code>被映射到了<code>event.nativeevent.contentoffset.x</code>(<code>event</code>通常是回調函數的第一個參數),并且<code>pan.x</code>和<code>pan.y</code>分别映射到<code>gesturestate.dx</code>和<code>gesturestate.dy</code>(<code>gesturestate</code>是傳遞給<code>panresponder</code>回調函數的第二個參數)。
你可能會注意到這裡沒有一個明顯的方法來在動畫的過程中讀取目前的值——這是出于優化的角度考慮,有些值隻有在原生代碼運作階段中才知道。如果你需要在javascript中響應目前的值,有兩種可能的辦法:
<code>spring.stopanimation(callback)</code>會停止動畫并且把最終的值作為參數傳遞給回調函數<code>callback</code>——這在處理手勢動畫的時候非常有用。
<code>spring.addlistener(callback)</code> 會在動畫的執行過程中持續異步調用<code>callback</code>回調函數,提供一個最近的值作為參數。這在用于觸發狀态切換的時候非常有用,譬如當使用者拖拽一個東西靠近的時候彈出一個新的氣泡選項。不過這個狀态切換可能并不會十分靈敏,因為它不像許多連續手勢操作(如旋轉)那樣在60fps下運作。
如前面所述,我們計劃繼續優化animated,以進一步提升性能。我們還想嘗試一些聲明式的手勢響應和觸發動畫,譬如垂直或者水準的傾斜操作。
<code>layoutanimation</code>允許你在全局範圍内<code>建立</code>和<code>更新</code>動畫,這些動畫會在下一次渲染或布局周期運作。它常用來更新flexbox布局,因為它可以無需測量或者計算特定屬性就能直接産生動畫。尤其是當布局變化可能影響到父節點(譬如“檢視更多”展開動畫既增加父節點的尺寸又會将位于本行之下的所有行向下推動)時,如果不使用<code>layoutanimation</code>,可能就需要顯式聲明元件的坐标,才能使得所有受影響的元件能夠同步運作動畫。
注意盡管<code>layoutanimation</code>非常強大且有用,但它對動畫本身的控制沒有<code>animated</code>或者其它動畫庫那樣友善,是以如果你使用<code>layoutanimation</code>無法實作一個效果,那可能還是要考慮其他的方案。
另外,如果要在android上使用layoutanimation,那麼目前還需要在<code>uimanager</code>中啟用:
<a target="_blank" href="https://rnplay.org/apps/uaqrgq">運作這個例子</a>
<code>requestanimationframe</code>是一個對浏覽器标準api的相容實作,你可能已經熟悉它了。它接受一個函數作為唯一的參數,并且在下一次重繪之前調用此函數。一些基于javascript的動畫庫高度依賴于這一api。通常你不必直接調用它——那些動畫庫會替你管理好幀的更新。
“補間是在兩個圖像之間生成中間幀的過程,以使得第一個圖像能夠平滑的變化為第二個圖像”。補間幀是指在關鍵幀之間用于建立過渡假象的圖畫。”
這個庫并未随react native一起釋出——要在你的工程中使用它,則需要先在你的工程目錄下執行<code>npm i react-tween-state --save</code>來安裝。
<a target="_blank" href="https://rnplay.org/apps/4fuq-a">運作這個例子</a>
需要注意的是rebound動畫可以被中斷——如果你在按下動畫的過程中釋放手指,它會從目前狀态彈回初始值。
你還可以為彈跳值啟用邊界,這樣它們不會超出,而是會緩緩接近最終值。在上面的例子裡,我們可以添加<code>this._scrollspring.setovershootclampingenabled(true)</code>來啟用邊界。參見下面的gif動畫來看一個啟用了邊界的效果:
我們可以把這個用在rebound樣例中來更新縮放比例——如果我們要更新的元件有一個非常深的内嵌結構,并且沒有使用<code>shouldcomponentupdate</code>來優化,那麼使用<code>setnativeprops</code>就将大有裨益。
<a target="_blank" href="https://rnplay.org/apps/fuqjag">運作這個例子</a>
不過你沒辦法把<code>setnativeprops</code>和react-tween-state結合使用,因為更新的補間值會自動被庫設定到state上——rebound則不同,它通過<code>onsprintupdate</code>函數在每一幀中給我們提供一個更新後的值。
<a target="_blank" href="https://rnplay.org/apps/hpy6ua">運作這個例子</a>