在移動(dòng)開發(fā)中,動(dòng)畫能有效的提高用戶體驗(yàn)。在 React Native 中,也有相應(yīng)的 API 供我們做動(dòng)畫。這里著重說一下 Animated 動(dòng)畫庫,它可以讓我們非常簡便的去實(shí)現(xiàn)各式各樣的動(dòng)畫和交互方式,而且具備很高的性能。Animated 目前只封裝了四個(gè)可以動(dòng)畫化的組件:View、Text、Image、ScrollView,不過你也可以用 Animated.createAnimatedComponent() 來封裝你自己的組件。
話不多說,我們來舉個(gè)栗子:
步驟拆解
1、創(chuàng)建 Animated.Value,設(shè)置初始值,比如一個(gè) View 組件的透明度,最開始設(shè)置 fadeAnim:Animated.Value(0) 來表示動(dòng)畫開始的時(shí)候,是透明的。
2、把創(chuàng)建的這個(gè) Animated.Value 綁定到 Style 的動(dòng)畫屬性,例:
3、使用 Animated.timing (還有其他的)來創(chuàng)建自動(dòng)的動(dòng)畫,或者使用 Animated.event 來根據(jù)手勢,觸摸,Scroll 的動(dòng)態(tài)更新動(dòng)畫的狀態(tài)。
4、調(diào)用 Animated.timing.start() 開始動(dòng)畫, start 接受一個(gè)回調(diào)函數(shù)作為參數(shù),將會在執(zhí)行了動(dòng)畫之后執(zhí)行回調(diào)函數(shù)。
動(dòng)畫模式
如果只是 timing ,肯定是無法滿足我們復(fù)雜的交互效果的需求的,所以 RN 還給我們提供了另外兩種動(dòng)畫模式。
1、spring 彈簧效果
? ? ? friction 摩擦系數(shù),默認(rèn)為40
? ? ? tension 張力系數(shù),默認(rèn)為7
? ? ? bounciness
? ? ? speed
2、decay 衰變效果
? ? ? velocity 出速率
? ? ? deceleration 衰減系數(shù),默認(rèn)為0.997
spring 支持 friction 與 tension 或者 bounciness 與 speed 兩種組合模式,這兩種模式不能并存。其中 friction 與 tension 模型來源于 Origami ,一款F家自制的動(dòng)畫原型設(shè)計(jì)工具,而 bounciness 與 speed 則是傳統(tǒng)的彈簧模型參數(shù)。
栗子不夠復(fù)雜?
看來一個(gè)簡單的淡入是無法滿足大家的好奇心的,我們整個(gè)大點(diǎn)的。
在上面的例子里面,我們實(shí)現(xiàn)的是三個(gè)動(dòng)畫效果同時(shí)進(jìn)行,因?yàn)槲覀兘o文字區(qū)域加上了字體增大的動(dòng)畫效果,相應(yīng)地,也要修改 Text 為 Animated.Text
強(qiáng)大的插值 interpolate
相信大家都已經(jīng)注意到了,我們上面用到了 interpolate 這個(gè)函數(shù),也就是插值函數(shù)。這個(gè)函數(shù)很強(qiáng)大,當(dāng)我們動(dòng)畫的值與要改變的屬性值不是同一單位的時(shí)候,就可以使用 interpolate 來幫我們進(jìn)行一個(gè)單位的映射轉(zhuǎn)換,比如
當(dāng) rotation 這個(gè)動(dòng)畫狀態(tài)的值為0時(shí),那么輸出的Z軸上的旋轉(zhuǎn)角度會自動(dòng)映射成0deg。當(dāng) rotation 這個(gè)動(dòng)畫狀態(tài)的值為0.5時(shí),那么輸出的Z軸上的旋轉(zhuǎn)角度會自動(dòng)映射成180deg。以此類推。 InputRange 并不局限于 [0,1] 區(qū)間,這個(gè)主要取決于你定義的動(dòng)畫的初始值,和想要變化以后的值,并且其間還可以存在多段映射。插值的好處在于我們可以聲明一個(gè)動(dòng)畫變量來控制多個(gè)并行動(dòng)畫,簡單易控制。
Interpolate 支持多段區(qū)間映射, [0,1] 區(qū)間和 [1,2] 區(qū)間之間沒有什么必然聯(lián)系,當(dāng) rotation 趨近于1時(shí),動(dòng)畫旋轉(zhuǎn)趨近于360deg,當(dāng) rotation 趨近于2時(shí),動(dòng)畫也可以旋轉(zhuǎn)回來趨近于200deg,唯一要注意的就是 inputRange 的每一個(gè)值都必須有一個(gè) outputRange 里面的值與其對應(yīng)。
流程控制
在剛才的栗子中,我們使用了 parallel 來實(shí)現(xiàn)多個(gè)動(dòng)畫的并行渲染,其他用于流程控制的 API 還有:
1、sequence 接受一系列動(dòng)畫數(shù)組為參數(shù),并依次執(zhí)行
2、stagger 接受一系列動(dòng)畫數(shù)組和一個(gè)延遲時(shí)間,按照序列,每隔一個(gè)延遲時(shí)間后執(zhí)行下一個(gè)動(dòng)畫(其實(shí)就是插入了 delay 的 parallel )
3、delay 生成一個(gè)延遲時(shí)間(默認(rèn)值為0,單位為毫秒)
第二個(gè)栗子稍微修改一下,就可以根據(jù)業(yè)務(wù)邏輯去控制自己的動(dòng)畫流程,在上面的栗子里面,我們讓動(dòng)畫首先出現(xiàn),出現(xiàn)了之后,再同時(shí)進(jìn)行字體變大和旋轉(zhuǎn)兩個(gè)動(dòng)畫,雖然他們持續(xù)的時(shí)間和到達(dá)的值不一樣,但是他們是在 opacity 變?yōu)?以后同時(shí)開始的。
需要注意的點(diǎn)
可以看到我們上面的動(dòng)畫都是以毫秒級的頻率來執(zhí)行的,也就相當(dāng)于我們會以毫秒級的頻率去調(diào)用 setState,而每次調(diào)用 setState 都會重新調(diào)用 render 方法遍歷子元素進(jìn)行渲染,就算有 dom diff 幫我們算,他也會累的(負(fù)擔(dān)不起這么大的計(jì)算量和 UI 渲染量)。這里淺談幾個(gè)優(yōu)化方案,具體收益就只有大家在實(shí)際項(xiàng)目中自己體會了。
使用原生驅(qū)動(dòng)
這算是官方給出的一個(gè)比較簡單的解決方案了,在動(dòng)畫中啟用原生驅(qū)動(dòng)非常簡單。只需在開始動(dòng)畫之前,在動(dòng)畫配置中加入一行 useNativeDriver:true 。
ShouldComponentUpdate
大家都知道,ShouldComponentUpdate 是性能優(yōu)化利器,只需要在子組件的 ShouldComponentUpdate 返回 false,就可以省去很多多余渲染花費(fèi)。這樣做也有一個(gè)弊端,畢竟我們的子元素并不是一成不變的,這樣粗暴的直接返回 false 的話,會讓子元素變成一灘死水,所以使用的時(shí)候請權(quán)衡利弊。
SetNativeProps (局部刷新)
可能這都不算是動(dòng)畫的的優(yōu)化方案,但是卻可以直接改動(dòng)組件并觸發(fā)局部的刷新。使用這個(gè)方法修改 View、Text 等 RN 自帶的組件,就不會觸發(fā)組件的 componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate 等組件生命周期中的方法。
LayoutAnimation
這也是官方給的一個(gè)比較簡單的動(dòng)畫解決方案,它允許我們在全局范圍內(nèi)創(chuàng)建和更新動(dòng)畫,這些動(dòng)畫會在下一次渲染或布局周期運(yùn)行。