雖然React Native內建的 Animated API 可以滿足基本的動畫需求。但是隨著我們對動畫質量和流暢性的要求日益增高,內建的 Animated API 有時已不能滿足所有的需求。
這正是 react-native-reanimated(簡稱 Reanimated)誕生的原因。Reanimated 是一個更高性能的動畫庫,它還提供了更多的彈性和功能,讓開發者能夠創建更複雜、更流暢的動畫效果。
react-native-reanimated的標語是React Native's Animated library reimplemented,意思就是重新實現 React Native的Animated,使其更具擴展性和效能。
React Native Animated 的侷限性
首先要先暸解React Native 有兩大線程:JavaScript 線程和UI 線程,這我們在創建運行篇也有提到。當 JavaScript 線程啟動一個動畫指令,這個指令傳送到 UI 線程。但因為因為這個過程是非同步的,所以 UI 線程可能無法馬上收到或處理這個動畫指令,導致動畫的啟動出現延遲。同時,JavaScript 線程要處理的事件很多,容易搶佔資源。
在上一篇文章中,我們有提到了 React Native Animated的 useNativeDriver
方法,它會將動畫配置訊息傳給UI線程處理,但這方法有其局限性。
opacity
和 transform
)。useNativeDriver
進行優化,動畫仍由 JavaScript 線程觸發,所以當JS線程繁忙阻塞時,動畫流暢性將受影響。Reanimated優勢
Reanimated 的突出之處在於,它將所有與動畫相關的 JavaScript 函式都轉移到 UI 線程中。你可能會疑惑,UI線程是原生的,怎麼能執行JS代碼?這就是 Reanimated 巧妙的地方了,Reanimated 在原生 UI 線程上創建了一個 JavaScript 虛擬機,專門來執行動畫相關的代碼。
從上圖中可已看到,JavaScript 線程包含了useSharedValue
、useAnimatedStyle
及 useAnimatedGestureHandler
這些是Reanimated定義好的動畫相關函式。
這些函式都有 worklet
標記。當 worklet
標記的函式被調用時,它就會在 Reanimated 建立的 JavaScript 虛擬機上執行。因為這個虛擬機運行在 UI 線程上,確保了高效、即時的操作。
Shared Values
共享值,類似Hooks的useState,透過訪問.value屬性來取得或更改這些共享變數的值。
const moveX = useSharedValue(0);
如上,我們為其指定了一個起始值0。要在之後的程式碼中操作或讀取moveX,需要使用moveX.value來訪問它。
const onPressHandler = () => {
moveX.value += 50;
};
useAnimatedStyle
這是一個專門為動畫樣式所設計的hooks函式。其返回的值即為動畫元件的樣式。所需改變的動畫樣式都可以透過useAnimatedStyle來定義。
withTiming
基於時間的動畫,使元素在指定的時間內從一個值過渡到另一個值。
用法:
const animationValue = useSharedValue(0);
const animatedStyle = useAnimatedStyle(() => {
return {
opacity: withTiming(animationValue.value, {
duration: 500, // 持續時間為 500 毫秒
}),
};
});
withSpring
彈簧動畫,可以模擬物件被彈出或彈回的效果。
用法:
Copy code
const animationValue = useSharedValue(0);
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [{ translateY: withSpring(animationValue.value) }],
};
});
withRepeat
用於重複某個動畫特定的次數或無限次。你可以指定動畫要重複的次數、是否要在每次重複之間逆轉動畫等。
用法:
Copy code
const animationValue = useSharedValue(0);
const animatedStyle = useAnimatedStyle(() => {
return {
opacity: withRepeat(
withTiming(animationValue.value, { duration: 500 }),
3, // 重複 3 次
true // 在每次重複之間逆轉動畫
),
};
});
npm install react-native-reanimated
module.exports = {
presets: [
... // don't add it here :)
],
plugins: [
...
'react-native-reanimated/plugin',
],
};
⚠️ react-native-reanimated/plugin 應放在 plugins 列表的最後。
要做babel
的設定是因為,Reanimated 標記的 worklets函式 需要通過 Babel 插件轉換,使它們可以在 UI 線程上運行。
我們將上一篇的Loading動畫改為 Reanimated 製作。
1. 建立一個 SharedValue
來存儲動畫的值。
設定動畫值(旋轉角度),創建一個 SharedValue
來代表這個值。
import {useSharedValue} from 'react-native-reanimated';
const rotationValue = useSharedValue(0);
2. 把這個值連接到動畫組件的一個或多個樣式上。
使用 useAnimatedStyle
來創建動畫樣式。當 rotationValue
的值改變時,它會直接將值轉換為相對應的 'deg' 旋轉角度。例如,當 rotationValue.value
為 1 時,該元件的旋轉角度為 '1deg'。
import { useAnimatedStyle } from 'react-native-reanimated';
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [
{
rotate: `${rotationValue.value * 360}deg`
}
]
};
});
3. 用 withTiming
控制並更新該動畫。
使用 withTiming 來定義動畫的具體行為。另外因為要做無限循環的動畫,所以還用到了 withRepeat ,讓動畫能持續地旋轉。
import {withRepeat, withTiming} from 'react-native-reanimated';
rotationValue.value = withRepeat(
withTiming(1, { duration: 1000, easing: Easing.linear }),
-1, // -1代表無限次執行
true
);
4. UI部分
使用 Animated.View
並傳入 animatedStyle
來實現旋轉效果。
import { Animated } from 'react-native-reanimated';
<Animated.View style={[customStyles.container, animatedStyle]}>
<View style={customStyles.backgroundCircle} />
<View style={customStyles.rotatingCircle} />
</Animated.View>
完整代碼
import React from 'react';
import { StyleSheet, View } from 'react-native';
import { useSharedValue, useAnimatedStyle, withRepeat, withTiming, Easing, Animated } from 'react-native-reanimated';
const primaryBorderColor = '#3498db';
const secondaryBorderColor = '#e0e0e0';
const customStyles = StyleSheet.create({
container: {
position: 'absolute',
left: '50%',
top: '50%',
width: 80,
height: 80,
marginLeft: -40,
marginTop: -40,
},
rotatingCircle: {
width: '100%',
height: '100%',
borderRadius: 40,
borderWidth: 10,
borderTopColor: primaryBorderColor,
borderRightColor: primaryBorderColor,
borderBottomColor: primaryBorderColor,
borderLeftColor: 'transparent',
},
backgroundCircle: {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
borderRadius: 40,
borderWidth: 10,
borderColor: secondaryBorderColor,
}
});
const RotatingComponent = () => {
const rotationValue = useSharedValue(0);
rotationValue.value = withRepeat(
withTiming(1, { duration: 1000, easing: Easing.linear }),
-1,
true
);
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [
{
rotate: `${rotationValue.value * 360}deg`
}
]
};
});
return (
<Animated.View style={[customStyles.container, animatedStyle]}>
<View style={customStyles.backgroundCircle} />
<View style={customStyles.rotatingCircle} />
</Animated.View>
);
};
export default RotatingComponent;
如果你只需要簡單的動畫效果,或許React Native 的原生 Animated API 就足夠了,但是如果有動畫的流暢性的需求,或是要做更複雜的動畫效果時,則 Reanimated 是一個很好的選擇。
若對深入了解 Reanimated 的更多特性和用法感興趣,可以看看它的官方文檔:React Native Reanimated 文檔。
https://www.jb51.net/article/277233.htm
https://juejin.cn/post/7068170378123673637