參考文檔 :動(dòng)畫(huà)概述鼎兽、動(dòng)畫(huà)API答姥、動(dòng)畫(huà)緩動(dòng)函數(shù)、interactionmanager
文檔其實(shí)已經(jīng)比較詳細(xì)了谚咬,這里整理一下鹦付,做個(gè)筆記,也算加深一下自己的印象择卦。
定義
所謂動(dòng)畫(huà)敲长,就是 UI 上的變化,而在 RN 中秉继,UI 對(duì)應(yīng)的就是 style祈噪,那么動(dòng)畫(huà)就是要順滑的改變 style 某個(gè)屬性的值,就以 opacity 來(lái)作為解釋吧
如果想要實(shí)現(xiàn)透明度的不斷變化尚辑,比如可以用 state 中定義的屬性作為 style.opacity 的值辑鲤,后續(xù)通過(guò) requestAnimationFrame 來(lái)不停修改 state 值,可以做到杠茬,但性能低下月褥,這就是 RN Animated 存在的意義
const opacity = new Animated.Value(0);
<Animated.View style={{
opacity: opacity
}}/>
這就好了,opacity
的屬性值為一個(gè) Animated.Value
瓢喉,當(dāng)需要修改透明度的了宁赤,不需要改 state, 而是重置
Animated.Value
的值就行了栓票,他會(huì)實(shí)時(shí)傳導(dǎo)給 View 的 style.opacity
動(dòng)起來(lái)
如何修改 Animated.Value
的值
// 定義 opacity 的值如何變化
const an = Animated.timing(opacity, {
toValue: 1, // 最終變?yōu)?1
delay: 1000, // 延遲 1 秒后開(kāi)始變換(在此期間礁击,opacity 值仍為 0)
duration: 1000, // 用 1 秒的時(shí)間去變換(在 1 秒內(nèi),opacity 由 0 -> 1)
easing: Easing.back(), // 變換過(guò)程不一定是均勻變換逗载,可指明變換函數(shù)
useNativeDriver:false, // 是否啟用原生驅(qū)動(dòng)
isInteraction:true, // 是否在InteractionManager的隊(duì)列中注冊(cè)哆窿,后面會(huì)提到
})
// 開(kāi)始動(dòng)畫(huà) (可選:設(shè)置執(zhí)行完成的回調(diào))
an.start([callback]);
// 停下動(dòng)畫(huà)
an.stop()
說(shuō)下配置項(xiàng)中的三個(gè)參數(shù)
const a = new Animated.Value(0);
Animated.timing(opacity, {
toValue: a,
useNativeDriver:false,
isInteraction:true,
})
toValue
可以指定為其他 Animated.Value 動(dòng)態(tài)值,而不是固定值
useNativeDriver
啟用原生使用ui線程厉斟,可提供動(dòng)畫(huà)流暢度挚躯,但有限制
1)啟用了原生,那么和該動(dòng)畫(huà)相關(guān)的其他動(dòng)畫(huà)也必須是原生
2)不支持用在 style 的盒模型屬性上擦秽,啟用原生驅(qū)動(dòng)码荔,最安全的就是 opacity 和 transform
isInteraction
RN 有一個(gè) interactionmanager API,可執(zhí)行一些耗時(shí)任務(wù)感挥,這些任務(wù)會(huì)在動(dòng)畫(huà)執(zhí)行完畢后再啟動(dòng)缩搅,通過(guò)該屬性來(lái)指明是否支持該 API, 默認(rèn)為 true触幼,即動(dòng)畫(huà)完事再執(zhí)行任務(wù)硼瓣,設(shè)為 false 則不然
除了 Animated.timing,還有兩個(gè)已經(jīng)預(yù)置了 easing 效果的方法:Animated.decay、Animated.spring
合成
假設(shè)我們要做一個(gè)堂鲤,由透明逐漸顯示亿傅、且寬度從 0 變到 100 的 view,透明度就是上面的辦法了瘟栖,那么寬度呢葵擎,我們可以重新定義一個(gè)
const width = new Animated.Value(0);
const w = Animated.timing(width, {
toValue: 100,
...... // 其他配置,與 opacity 保持一致
})
w.start()
這樣搞半哟,配置就要寫(xiě)兩遍酬滤,當(dāng)然,也可以把配置提出來(lái)用一個(gè)變量來(lái)定義寓涨,這樣寫(xiě)一遍就好了敏晤,但這樣,要同時(shí)調(diào)用兩個(gè)動(dòng)畫(huà)的 start() 缅茉,兩個(gè)執(zhí)行可能不完全同步嘴脾,這就是合成的意義了,可以這樣寫(xiě)
const opacity = new Animated.Value(0);
const width = Animated.multiply(opacity, 100);
<View style={{
opacity: opacity,
width:width
}}/>
Animated.timing(opacity, {
...
}).start()
使用合成蔬墩,只需要啟動(dòng) opacity
就好了译打,width
的值會(huì)自動(dòng)跟著 opacity
變化,妙啊~
RN 提供了 Animated.add (加)拇颅、Animated.subtract (減)奏司、Animated.multiply(乘)、Animated.divide(除)樟插、Animated.modulo(取余) 五個(gè)合成方法
插值
上面的合成函數(shù)雖然方便韵洋,但實(shí)際操作中遠(yuǎn)不止加減乘除這么簡(jiǎn)單,那么能不能自定義合成函數(shù)呢黄锤?答案是不太能愚隧,因?yàn)樽远x函數(shù)意味著艾凯,動(dòng)畫(huà)跑起來(lái)之后,還要和 js 交互流强,來(lái)拿計(jì)算結(jié)果,在性能上是不能接受的打月,卡成幻燈片的動(dòng)畫(huà)還叫動(dòng)畫(huà)嗎队腐,也許未來(lái)某一天奏篙,RN 可以靜態(tài)編譯 js 無(wú)狀態(tài)純函數(shù)就可以了,那在那一天到之前,插值是當(dāng)前的選擇
const opacity = new Animated.Value(0);
const width = opacity.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [0, 20, 100]
});
<View style={{
opacity: opacity,
width:width
}}/>
Animated.timing(opacity, {
...
}).start()
插值通過(guò) interpolate
將輸入 inputRange
映射為 outputRange
悠就,可分段映射千绪,這樣靈活性大大提高
組合
產(chǎn)品改需求了充易,先逐漸顯示,然后再逐漸變寬盹靴。好簡(jiǎn)單稿静,在 opacity
的 start()
加回調(diào) -> 啟動(dòng) width
的 start()
;可以改备,但如果有 N 個(gè)連續(xù)動(dòng)畫(huà),回調(diào)恐怕會(huì)吐血盐捷,這就需要組合動(dòng)畫(huà)了
Animated.sequence
按順序執(zhí)行一系列動(dòng)畫(huà)
Animated.delay
在順序執(zhí)行動(dòng)畫(huà)時(shí)默勾,可以讓兩個(gè)動(dòng)畫(huà)銜接時(shí)母剥,延遲一會(huì)
Animated.sequence([
Animated.timing(opacity, {
...
})
Animated.delay(1000); // 有點(diǎn)像 sleep
Animated.timing(width, {
...
})
]).start()
假如希望 opacity
和 width
同時(shí)執(zhí)行,但動(dòng)畫(huà)過(guò)程不是一個(gè)步調(diào)环疼,使用不同的 easing 或 duraition
Animated.parallel
同時(shí)執(zhí)行一系列動(dòng)畫(huà)
Animated.stagger
延遲啟動(dòng)一系列動(dòng)畫(huà)炫隶,基本等同于 setTimeout(Animated.parallel)
Animated.parallel([
Animated.timing(opacity, {
...
})
Animated.timing(width, {
...
})
]).start()
Animated.stagger(1000, [
Animated.timing(opacity, {
...
})
Animated.timing(width, {
...
})
]).start()
Animated.loop
循環(huán)執(zhí)行動(dòng)畫(huà)
可以在 start 回調(diào)中再啟動(dòng)自身,這樣也能搞一個(gè)循環(huán)動(dòng)畫(huà)爸吮,但這樣的話望门,每次結(jié)束筹误,是 ui 線程調(diào)用 js, 再喚起 ui,周而復(fù)始友存,不如直接 loop 性能來(lái)的好
Animated.loop(Animated.timing(opacity, {
...
isInteraction:false
}), {
iterations:-1
})
- 函數(shù)的第二個(gè)參數(shù)是設(shè)置循環(huán)次數(shù)的陶衅,可省略
- 建議配置 isInteraction=false 是因?yàn)檫@是循環(huán)動(dòng)畫(huà),會(huì)導(dǎo)致其他任務(wù)永遠(yuǎn)無(wú)法執(zhí)行了
交互
以上都是提前設(shè)計(jì)好動(dòng)畫(huà)膨俐,然后執(zhí)行罩句,在實(shí)際使用中動(dòng)畫(huà)门烂,其實(shí)就是 style 可能是需要根據(jù)用戶操作來(lái)進(jìn)行變動(dòng)的,比如這樣的組件 react-native-parallax-scroll-view蔓姚,這就需要用到 Animated.event
函數(shù)了
// argMapping 是映射配置
// config 接受兩個(gè)參數(shù), js 異步回調(diào)函數(shù) 和 是否啟用原生驅(qū)動(dòng)
Animated.event(argMapping, {
listener:Function,
useNativeDriver:true
})
需要先強(qiáng)調(diào)一件事氓润,否則看 argMapping 恐怕會(huì)有點(diǎn)懵
Animated 的本質(zhì)是提供輸入值,比如上面通過(guò) 初始value
和 配置toValue
明確提供一個(gè)區(qū)間值作為輸入值挨措,亦或通過(guò) RN 提供的合成或插值將區(qū)間值進(jìn)行調(diào)整崩溪,說(shuō)白了,就是不希望 js 內(nèi)進(jìn)行任何計(jì)算觉既,要么提供明確的值乳幸,要么使用 RN 的計(jì)算手段粹断。
而交互呢,其實(shí)就是將 RN 提供的值瓶埋,再作為輸入值,還是不讓 js 參合曾撤,雖然提供了 listener 回調(diào)挤悉,但也只是讓你知道當(dāng)前執(zhí)行到哪了,是不能進(jìn)行修改的尖啡,并且這個(gè)回調(diào)的值可能還有延遲衅斩。argMapping 就是將 RN 提供的輸出值 映射 到一個(gè)動(dòng)畫(huà)上作為輸入值怠褐。
原生交互,也就是手勢(shì)交互奠涌,只有一個(gè) panresponder磷杏,還有就是 scrollview 二次封裝的 onScroll,當(dāng)然慈格,也可能有其他三方組件再次封裝遥金,但原理都是一樣的
以 panresponder
舉例稿械,綁定監(jiān)聽(tīng)函數(shù)會(huì)返回兩個(gè)參數(shù),具體看官網(wǎng)
onPanResponderMove: (nativeEvent, gestureState) => {}
gestureState = {
....
dx - 從觸摸操作開(kāi)始時(shí)的累計(jì)橫向路程
dy - 從觸摸操作開(kāi)始時(shí)的累計(jì)縱向路程
}
比如我們現(xiàn)在要將 dx 映射到某個(gè) Animated.value
const x = new Animated.Value(0);
onPanResponderMove: Animated.event([
null, { dx: x }
])
嗯页眯,這就完事了厢呵, event([])
中的 []
會(huì)傳遞回調(diào)函數(shù)參數(shù),第一個(gè)不用忿族,直接 null ,然后映射第二個(gè)错英,就是 gestureState.dx -> x
隆豹,當(dāng)然璃赡,你可以從 gestureState 找其他參數(shù)進(jìn)行映射
為加深理解,再看看 onScroll
碉考, 根據(jù)介紹侯谁,其函數(shù)原型是這樣的
onScroll : (event) => {}
event = {
nativeEvent: {
contentInset: { bottom, left, right, top },
contentOffset: { x, y },
contentSize: { height, width },
layoutMeasurement: { height, width },
zoomScale
}
}
比如要映射 Y 軸滾動(dòng)距離
const dy = new Animated.Value(0);
onScroll={Animated.event(
[{ nativeEvent: {
contentOffset: { y: dy }
}}]
)}
嗯,就是這樣热芹,參數(shù)對(duì)參數(shù)
響應(yīng)
輸入值 雖然無(wú)法自定義函數(shù)進(jìn)行計(jì)算惨撇,但好歹提供了 合成 和 插值的辦法魁衙,輸出值,就是當(dāng)前的 Animated.Value 實(shí)際值纺棺,有么有辦法監(jiān)聽(tīng)祷蝌?
RN 提供了一些方法,具體可參見(jiàn) animatedvalue米丘、animatedvaluexy糊啡;使用方法比較簡(jiǎn)單
const an = new Animated.Value(0);
an.addListener(v => {
});
// 也可直接設(shè)置值
an.setValue(2)
這種響應(yīng)是不建議直接參與動(dòng)畫(huà)的,適合作為旁觀者靜靜看著動(dòng)畫(huà)堕扶,當(dāng)達(dá)到某一個(gè)符合條件的值,觸發(fā)一些其他操作典尾。
收尾
最后再加上 layoutanimation 便是 RN動(dòng)畫(huà) 的全部?jī)?nèi)容了糊探,該 API 是做整體動(dòng)畫(huà)的科平,不是特別常用,就沒(méi)展開(kāi)髓考,需要的話可以自行查閱汞贸。
本篇文檔都是使用 RN 中文網(wǎng)的鏈接印机,該網(wǎng)站是第三方進(jìn)行翻譯的射赛,質(zhì)量不錯(cuò),但相對(duì)于 官網(wǎng) 還是有所不足楣责,比如上面說(shuō)的 animatedvalue
API秆麸,由于官網(wǎng)上沒(méi)有相關(guān)入口,所以中文網(wǎng)也沒(méi)有屯烦,但在官網(wǎng)頂部的 search 中是可以搜索到的房铭,中文網(wǎng)則搜索不到 :) 英文水平跟得上缸匪,直接看官方文檔,更新會(huì)比較及時(shí)
最后凌蔬,就是 github 上的 react-native-website ,也就是官網(wǎng)的的托管庫(kù)懈词,其實(shí)也是一個(gè)不錯(cuò)的尋找文檔的地方钦睡。
最最后,這里還有一個(gè)不錯(cuò)的學(xué)習(xí)資料:https://future-challenger.gitbooks.io/react-native-animation/content/
最最最后洒琢,官方推薦的導(dǎo)航組件 react-navigation 使用一個(gè)第三方動(dòng)畫(huà)庫(kù) react-native-reanimated褐桌,該庫(kù)更為強(qiáng)大荧嵌,這里就不再介紹了,有興趣的朋友可以研究一下