react?native?reanimated實(shí)現(xiàn)動(dòng)畫(huà)示例詳解
背景
在一次 App 迭代中,UI 想要給按鈕添加一個(gè)動(dòng)畫(huà)效果,在對(duì)接的過(guò)程中,UI 表示直接用 .gif 就好,因?yàn)楦杏X(jué)開(kāi)發(fā)出來(lái)的效果應(yīng)該不會(huì)很好。
聽(tīng)到這里,一個(gè)技術(shù)人的自尊心仿佛被踩在了地上,我當(dāng)即表示想用 react-native-reanimated(下文簡(jiǎn)稱 Reanimated) 試一試。??
動(dòng)畫(huà)拆分
首先,從最外層來(lái)看,動(dòng)畫(huà)有一個(gè)抖動(dòng)效果:先向左,再向右??梢岳?rotate 旋轉(zhuǎn)屬性來(lái)實(shí)現(xiàn)。
其次,中間的文字部分有一個(gè)縮放動(dòng)畫(huà),可以通過(guò) scale 實(shí)現(xiàn)。
最后,當(dāng)文字最小化時(shí),會(huì)改變內(nèi)容,這個(gè)需要配合 JS 來(lái)實(shí)現(xiàn)。
實(shí)現(xiàn)抖動(dòng)
首先通過(guò) useSharedValue
定義一個(gè)共享值 rotation
,共享值和 useRef
類似,區(qū)別是共享值有一個(gè) value
屬性而不是 current
。
我們使用共享值改變的樣式通過(guò) useAnimatedStyle
包裝一下,再賦值給 Animated.View
,這和使用普通的 React Native 樣式有點(diǎn)區(qū)別:
import Animated from 'react-native-reanimated'; const rotation = useSharedValue(0); const shakeStyle = useAnimatedStyle(() => { return { transform: [ { rotateZ: `${rotation.value}deg`, }, ], }; }, []); <Animated.View style={[styles.btn, shakeStyle]}> ... </Animated.View>
定義動(dòng)畫(huà)
每個(gè)動(dòng)畫(huà)可以使用 withTiming
更新共享值,并設(shè)置動(dòng)畫(huà)的具體參數(shù)。它會(huì)啟動(dòng)基于時(shí)間的動(dòng)畫(huà)曲線,如執(zhí)行時(shí)間 duration,緩動(dòng)函數(shù) easing 等。
抖動(dòng)的過(guò)程有三個(gè)步驟:向左旋轉(zhuǎn),向右旋轉(zhuǎn),保持水平。我們使用 withSequence
來(lái)編排動(dòng)畫(huà)的順序。
最后,我們使用 withRepeat
讓以上三個(gè)步驟無(wú)限循環(huán)。它接受三個(gè)參數(shù):
- 第一參數(shù)是動(dòng)畫(huà)函數(shù);
- 第二個(gè)參數(shù)是執(zhí)行的次數(shù),-1 表示無(wú)限次;
- 第三個(gè)參數(shù)表示動(dòng)畫(huà)是否反向執(zhí)行。
注意,在恢復(fù)水平后,按鈕仍保持一段時(shí)間的靜止,我們可以用到 withDelay
來(lái)延遲執(zhí)行下一個(gè)動(dòng)作。
const SCOPE = 2; useEffect(() => { const turnL = withDelay(1400, withTiming(-SCOPE, { duration: 100, easing: Easing.linear })); // 向左 const turnR = withTiming(SCOPE, { duration: 100, easing: Easing.linear }); // 向右 const holden = withTiming(0, { duration: 100, easing: Easing.linear }); // 水平 const rotateAnimations = withSequence(turnL, turnR, holden); // 編排動(dòng)畫(huà)順序 rotation.value = withRepeat(rotateAnimations, -1); // 重復(fù)執(zhí)行動(dòng)畫(huà) return () => { cancelAnimation(rotation); }; }, []);
實(shí)現(xiàn)縮放動(dòng)畫(huà)
實(shí)現(xiàn)縮放動(dòng)畫(huà)的思路與上面基本相似。這里需要注意的是,需要根據(jù)實(shí)際需求,調(diào)整動(dòng)畫(huà)之間的節(jié)奏關(guān)系。比如縮放開(kāi)始,抖動(dòng)開(kāi)始;縮放結(jié)束,抖動(dòng)也就結(jié)束。
const scaleSize = useSharedValue(0.2); const scaleStyle = useAnimatedStyle(() => { return { transform: [ { scale: scaleSize.value, }, ], }; }, []); useEffect(() => { ... const zoomOut = withDelay(1600, withTiming(0.2, { duration: 100, easing: Easing.linear })); const restoreSize = withTiming(1, { duration: 100, easing: Easing.linear }); const scaleAnimations = withSequence(restoreSize, zoomOut); scaleSize.value = withRepeat(scaleAnimations, -1); return () => { ... cancelAnimation(scaleSize); }; }, []) <Animated.View style={[styles.textWrapper, scaleStyle]}> ... <Animated.View>
改變內(nèi)容
當(dāng)我們依賴共享值的變化,需要進(jìn)一步操作時(shí),可以使用 useAnimatedReaction
,它第一參數(shù)中定義依賴的值,第二個(gè)參數(shù)接受第一個(gè)參數(shù)的返回值,并進(jìn)行自定義的操作。
注意,共享值變化不會(huì)觸發(fā) JS 線程中的組件更新,改變文案的狀態(tài)需要用到 useState
,因?yàn)槲陌父淖兪窃?JS 線程中處理的,可以通過(guò) runOnJS
可以讓函數(shù)在 JS 線程中執(zhí)行。
import { useAnimatedReaction, runOnJS } from 'react-native-reanimation'; ... const [status, setStatus] = useState(true); const scaleSize = useSharedValue(0.2); const toggle = useCallback(() => { setStatus((s) => !s); }, []); useAnimatedReaction( () => { return scaleSize.value; }, (next) => { if (next <= 0.2) { runOnJS(toggle)(); } } ); ... <Text>{status ? '參與話題' : '賺點(diǎn)贊次數(shù)'}</Text>
Reanimated 原理淺析
在開(kāi)發(fā)過(guò)程中,我們的動(dòng)畫(huà)代碼和狀態(tài)代碼都是用 JavaScript 寫(xiě)在同一個(gè)文件中的,你可能會(huì)認(rèn)為你寫(xiě)的動(dòng)畫(huà)部分的 JavaScript 和狀態(tài)部分的 JavaScript 都是運(yùn)行在同一個(gè)線程中的, 但其實(shí)并不是這樣的。
React Native 有兩個(gè)常用的線程:一個(gè)是 React Native 的 JavaScript 線程,另一個(gè)是 UI 主線程。
一方面,JavaScript 線程和 UI 主線程是異步通信的,這也意味著,如果是由 JavaScript 線程發(fā)起動(dòng)畫(huà)的執(zhí)行,UI 線程并不能同步地收到該命令并且立刻執(zhí)行。
另一方面,JavaScript 線程處理的事件很多,包括所有的業(yè)務(wù)邏輯、React Diff、事件響應(yīng)等,容易搶占動(dòng)畫(huà)的執(zhí)行資源。
Reanimated 是如何優(yōu)化?答案就是:把動(dòng)畫(huà)代碼放到 UI 主線程來(lái)執(zhí)行性能更好、不易卡頓。
它把動(dòng)畫(huà)相關(guān)的 JavaScript 函數(shù)及其上下文傳給了 UI 主線程。由于 UI 主線程沒(méi)有能夠運(yùn)行 JavaScript 的環(huán)境,于是 Reanimated 又創(chuàng)建了一個(gè) JavaScript 虛擬機(jī)來(lái)運(yùn)行傳過(guò)來(lái)的 JavaScript 函數(shù)。
在 JavaScript 線程中包括了三個(gè)動(dòng)畫(huà)相關(guān)的函數(shù)或值, useSharedValue
、 useAnimatedStyle
和 useAnimatedGestureHandler
。
這三部分的代碼會(huì)在其底層,將相關(guān)的回調(diào)函數(shù)標(biāo)記為worklet
,被標(biāo)記的worklet
函數(shù)或值會(huì)被放在一個(gè)由 Reanimated 創(chuàng)建的 JavaScript 虛擬機(jī)中執(zhí)行。而這個(gè)由 Reanimated 創(chuàng)建的 JavaScript 虛擬機(jī),會(huì)在 UI 線程中執(zhí)行傳過(guò)來(lái)的worklet
函數(shù),并且執(zhí)行的函數(shù)還可以同步地操作 UI。
Reanimated 動(dòng)畫(huà)性能好的原因就在于:React Native 的 JavaScript 線程是性能瓶頸點(diǎn),而在真正執(zhí)行動(dòng)畫(huà)時(shí),已經(jīng)把所有與動(dòng)畫(huà)相關(guān) JavaScript 函數(shù)都放到了 UI 線程中獨(dú)立的 JavaScript 虛擬機(jī)中了,并不會(huì)和 JavaScript 線程搶占硬件資源。
總結(jié)
Reanimated 處理動(dòng)畫(huà)的方法非常巧妙,并且性能極佳,是目前 React Native 社區(qū)中主流的動(dòng)畫(huà)處理方案,很多開(kāi)源庫(kù)都在使用。雖然官方文檔有些缺陷,比如 API 沒(méi)有 demo 不夠直觀、目前只有英文文檔,對(duì)英文差的同學(xué)不夠友好。
未來(lái)我們將深入學(xué)習(xí)和使用 Reanimated,來(lái)提升用戶體驗(yàn),實(shí)現(xiàn)媲美原生的交互效果。
以上就是react native reanimated實(shí)現(xiàn)動(dòng)畫(huà)示例詳解的詳細(xì)內(nèi)容,更多關(guān)于react native reanimated 動(dòng)畫(huà)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
react?hooks?UI與業(yè)務(wù)邏輯分離必要性技術(shù)方案
這篇文章主要為大家介紹了react?hooks?UI與業(yè)務(wù)邏輯分離必要性技術(shù)方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11react項(xiàng)目中使用react-dnd實(shí)現(xiàn)列表的拖拽排序功能
這篇文章主要介紹了react項(xiàng)目中使用react-dnd實(shí)現(xiàn)列表的拖拽排序,本文結(jié)合實(shí)例代碼講解react-dnd是如何實(shí)現(xiàn),代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-02-02解析react?函數(shù)組件輸入卡頓問(wèn)題?usecallback?react.memo
useMemo是一個(gè)react hook,我們可以使用它在組件中包裝函數(shù)??梢允褂盟鼇?lái)確保該函數(shù)中的值僅在依賴項(xiàng)之一發(fā)生變化時(shí)才重新計(jì)算,這篇文章主要介紹了react?函數(shù)組件輸入卡頓問(wèn)題?usecallback?react.memo,需要的朋友可以參考下2022-07-07React使用Mobx6.x共享狀態(tài)問(wèn)題
這篇文章主要介紹了React使用Mobx6.x共享狀態(tài)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10react native實(shí)現(xiàn)往服務(wù)器上傳網(wǎng)絡(luò)圖片的實(shí)例
下面小編就為大家?guī)?lái)一篇react native實(shí)現(xiàn)往服務(wù)器上傳網(wǎng)絡(luò)圖片的實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08React?中?memo?useMemo?useCallback?到底該怎么用
在React函數(shù)組件中,當(dāng)組件中的props發(fā)生變化時(shí),默認(rèn)情況下整個(gè)組件都會(huì)重新渲染。換句話說(shuō),如果組件中的任何值更新,整個(gè)組件將重新渲染,包括沒(méi)有更改values/props的函數(shù)/組件。在react中,我們可以通過(guò)memo,useMemo以及useCallback來(lái)防止子組件的rerender2022-10-10