iT邦幫忙

2023 iThome 鐵人賽

DAY 14
0
Mobile Development

30天React Native之旅:從入門到活用系列 第 14

Day 14:高效React Native動畫:探索Reanimated

  • 分享至 

  • xImage
  •  

雖然React Native內建的 Animated API 可以滿足基本的動畫需求。但是隨著我們對動畫質量和流暢性的要求日益增高,內建的 Animated API 有時已不能滿足所有的需求。

這正是 react-native-reanimated(簡稱 Reanimated)誕生的原因。Reanimated 是一個更高性能的動畫庫,它還提供了更多的彈性和功能,讓開發者能夠創建更複雜、更流暢的動畫效果。

react-native-reanimated的標語是React Native's Animated library reimplemented,意思就是重新實現 React Native的Animated,使其更具擴展性和效能。

為何要用Reanimated

  • React Native Animated 的侷限性

    首先要先暸解React Native 有兩大線程:JavaScript 線程UI 線程,這我們在創建運行篇也有提到。當 JavaScript 線程啟動一個動畫指令,這個指令傳送到 UI 線程。但因為因為這個過程是非同步的,所以 UI 線程可能無法馬上收到或處理這個動畫指令,導致動畫的啟動出現延遲。同時,JavaScript 線程要處理的事件很多,容易搶佔資源。

    在上一篇文章中,我們有提到了 React Native Animated的 useNativeDriver 方法,它會將動畫配置訊息傳給UI線程處理,但這方法有其局限性。

    1. 有限的屬性支持:只支持部分動畫屬性(例如 opacitytransform)。
    2. 性能仍不夠好:即使透過 useNativeDriver 進行優化,動畫仍由 JavaScript 線程觸發,所以當JS線程繁忙阻塞時,動畫流暢性將受影響。
  • Reanimated優勢

    Reanimated 的突出之處在於,它將所有與動畫相關的 JavaScript 函式都轉移到 UI 線程中。你可能會疑惑,UI線程是原生的,怎麼能執行JS代碼?這就是 Reanimated 巧妙的地方了,Reanimated 在原生 UI 線程上創建了一個 JavaScript 虛擬機,專門來執行動畫相關的代碼。

    https://ithelp.ithome.com.tw/upload/images/20230929/20103365znsvCuWNef.png
    source

    從上圖中可已看到,JavaScript 線程包含了useSharedValueuseAnimatedStyleuseAnimatedGestureHandler 這些是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 // 在每次重複之間逆轉動畫
        ),
      };
    });
    

安裝

  1. 首先,安裝 react-native-reanimated:
npm install react-native-reanimated
  1. 接著,將 react-native-reanimated/plugin 加入到你的 babel.config.js 裡面:
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 文檔

Ref

https://www.jb51.net/article/277233.htm
https://juejin.cn/post/7068170378123673637


上一篇
Day 13:React Native動畫入門:從基礎到實作
下一篇
Day 15:使用WebView在React Native中展示網頁
系列文
30天React Native之旅:從入門到活用30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言