【React Native】文件翻譯閱讀紀錄 - 指南 - 動畫

by - 上午9:00





facebook open source - React Native

動畫

動畫對於創建出色的用戶體驗非常重要。固定物體在開始移動時必須克服慣性。運動中的物體具有動力,很少立即停止。動畫允許您在界面中傳達物理上可信的動作。

React Native提供了兩個互補的動畫系統:動畫用於特定值的粒度和交互式控制,以及用於動畫全局佈局事務的LayoutAnimation。

動畫 API


Animated API旨在使其能夠以非常高效的方式簡潔地表達各種有趣的動畫和交互模式。動畫關注於輸入和輸出之間的聲明關係,其間具有可配置的轉換,以及用於控制基於時間的動畫執行的簡單啟動/停止方法。

動畫導出四種可動畫組件類型:View,Text,Image 和 ScrollView,但您也可以使用 Animated.createAnimatedComponent() 創建自己的組件類型。


例如,掛載時淡入的容器視圖可能如下所示:


讓我們分解這裡發生的事情。在FadeInView構造函數中,一個名為fadeAnim的新Animated.Value被初始化為狀態的一部分。 View上的opacity屬性映射到此動畫值。在幕後,提取數值並用於設置不透明度。

當組件安裝時,不透明度設置為0.然後,在fadeAnim動畫值上啟動緩動動畫,該值將更新每個幀上的所有相關映射(在這種情況下,只是不透明度),因為值動畫為最終值為1。


這是以優化的方式完成的,比調用setState和重新渲染更快。

因為整個配置是聲明性的,所以我們將能夠實現進一步優化,以序列化配置並在高優先級線程上運行動畫。

配置動畫

動畫可配置很多。自定義和預定義的緩動函數,延遲,持續時間,衰減因子,彈簧常數等都可以根據動畫的類型進行調整。

動畫提供了幾種動畫類型,最常用的是Animated.timing()。它支持使用各種預定義的緩動函數之一隨時間設置值,或者您可以使用自己的。緩動功能通常用於動畫中以傳達物體的逐漸加速和減速。

默認情況下,時間將使用easeInOut曲線,該曲線將逐漸加速傳遞到全速,並通過逐漸減速到停止來結束。您可以通過傳遞緩動參數來指定不同的緩動函數。還支持自定義持續時間甚至動畫開始前的延遲。

例如,如果我們想要在移動到最終位置之前創建一個稍微備份的對象的2秒長動畫:
Animated.timing(this.state.xPosition, {
  toValue: 100,
  easing: Easing.back(),
  duration: 2000,
}).start();
請查看Animated API參考的配置動畫部分,以了解有關內置動畫支持的所有配置參數的更多信息。

撰寫動畫

動畫可以組合併依次或併行播放。順序動畫可以在上一個動畫結束後立即播放,也可以在指定的延遲後開始播放。 Animated API提供了幾種方法,例如sequence()和delay(),每種方法都只需要一組動畫來執行,並根據需要自動調用start()/ stop()。

例如,以下動畫慣性停止,然後在平行旋轉時彈回:
Animated.sequence([
  // decay, then spring to start and twirl
  Animated.decay(position, {
    // coast to a stop
    velocity: {x: gestureState.vx, y: gestureState.vy}, // velocity from gesture release
    deceleration: 0.997,
  }),
  Animated.parallel([
    // after decay, in parallel:
    Animated.spring(position, {
      toValue: {x: 0, y: 0}, // return to start
    }),
    Animated.timing(twirl, {
      // and twirl
      toValue: 360,
    }),
  ]),
]).start(); // start the sequence group
如果一個動畫停止或中斷,則組中的所有其他動畫也將停止。 Animated.parallel有一個stopTogether選項,可以將其設置為false以禁用此選項。

您可以在Animated API參考的Composing animations部分找到完整的合成方法列表。

撰寫動畫

您可以通過加法,乘法,除法或模來組合兩個動畫值,以創建新的動畫值。

在某些情況下,動畫值需要反轉另一個動畫值進行計算。一個例子是反轉比例(2x - > 0.5x):
const a = new Animated.Value(1);
const b = Animated.divide(1, a);

Animated.spring(a, {
  toValue: 2,
}).start();

插值

每個屬性都可以先通過插值運行。插值將輸入範圍映射到輸出範圍,通常使用線性插值,但也支持緩動功能。默認情況下,它會將​​曲線外推到超出給定的範圍,但您也可以將其限制為輸出值。

將0-1範圍轉換為0-100範圍的簡單映射將是:
value.interpolate({
  inputRange: [0, 1],
  outputRange: [0, 100],
});
例如,您可能想要將Animated.Value視為從0到1,但是將位置從150px設置為0px並將不透明度從0設置為1.這可以通過從上面的示例修改樣式來輕鬆完成,如此:
  style={{
    opacity: this.state.fadeAnim, // Binds directly
    transform: [{
      translateY: this.state.fadeAnim.interpolate({
        inputRange: [0, 1],
        outputRange: [150, 0]  // 0 : 150, 0.5 : 75, 1 : 0
      }),
    }],
  }}
interpolate()也支持多個範圍段,這對於定義死區和其他方便的技巧很方便。例如,要獲得-300處的否定關係,在-100處變為0,然後在0處返回到1,然後在100處返回到零,接著是死區,對於除此之外的所有內容,該死區保持為0,你可以這樣做:
value.interpolate({
  inputRange: [-300, -100, 0, 100, 101],
  outputRange: [300, 0, 1, 0, 0],
});
哪個會像這樣映射:
Input | Output
------|-------
  -400|    450
  -300|    300
  -200|    150
  -100|      0
   -50|    0.5
     0|      1
    50|    0.5
   100|      0
   101|      0
   200|      0
interpolate()還支持映射到字符串,允許您使用單位為顏色和值設置動畫。例如,如果您想要為旋轉設置動畫,則可以執行以下操作:
value.interpolate({
  inputRange: [0, 360],
  outputRange: ['0deg', '360deg'],
});
interpolate()還支持任意緩動函數,其中許多函數已在Easing模塊中實現。 interpolate()還具有可配置的行為,用於外推outputRange。您可以通過設置extrapolate,extrapolateLeft或extrapolateRight選項來設置外推。默認值為extend,但您可以使用clamp來防止輸出值超出outputRange。

跟踪動態值

動畫值還可以跟踪其他值。只需將動畫的toValue設置為另一個動畫值而不是普通數字。例如,類似於Android上的Messenger使用的“聊天頭”動畫可以使用固定在另一個動畫值上的spring(),或者使用timing()和持續時間0來實現剛性跟踪。它們也可以用插值組成:
Animated.spring(follower, {toValue: leader}).start();
Animated.timing(opacity, {
  toValue: pan.x.interpolate({
    inputRange: [0, 300],
    outputRange: [1, 0],
  }),
}).start();
使用Animated.ValueXY()實現領導者和跟隨者動畫值。 ValueXY是處理2D交互的便捷方式,例如平移或拖動。它是一個簡單的包裝器,基本上包含兩個Animated.Value實例和一些調用它們的輔助函數,使得ValueXY在很多情況下成為Value的替代品。它允許我們在上面的例子中跟踪x和y值。

跟踪手勢

手勢(如平移或滾動)和其他事件可以使用Animated.event直接映射到動畫值。這是通過結構化地圖語法完成的,以便可以從復雜事件對像中提取值。第一級是允許跨多個args映射的數組,該數組包含嵌套對象。

例如,在使用水平滾動手勢時,您將執行以下操作以將event.nativeEvent.contentOffset.x映射到scrollX(Animated.Value):
 onScroll={Animated.event(
   // scrollX = e.nativeEvent.contentOffset.x
   [{ nativeEvent: {
        contentOffset: {
          x: scrollX
        }
      }
    }]
 )}
使用PanResponder時,您可以使用以下代碼從gestureState.dx和gestureState.dy中提取x和y位置。我們在數組的第一個位置使用null,因為我們只對傳遞給PanResponder處理程序的第二個參數感興趣,後者是gestureState。
onPanResponderMove={Animated.event(
  [null, // ignore the native event
  // extract dx and dy from gestureState
  // like 'pan.x = gestureState.dx, pan.y = gestureState.dy'
  {dx: pan.x, dy: pan.y}
])}

響應當前動畫值

您可能會注意到在動畫製作時沒有明顯的方法來讀取當前值。這是因為由於優化,該值可能僅在本機運行時中已知。如果您需要運行JavaScript以響應當前值,則有兩種方法:
  • spring.stopAnimation(callback)將停止動畫並使用最終值調用回調。這在進行手勢轉換時很有用。
  • spring.addListener(callback)將在動畫運行時異步調用回調,提供最近的值。這對於觸發狀態更改非常有用,例如,當用戶將其拖近時,將bobble捕捉到新選項,因為與需要運行在60的平移等連續手勢相比,這些較大的狀態更改對幾幀延遲不太敏感FPS。
動畫設計為完全可序列化,以便動畫可以以高性能方式運行,獨立於普通的JavaScript事件循環。這確實會對API產生影響,因此與完全同步的系統相比,當做一些事情似乎有點棘手時要記住這一點。查看Animated.Value.addListener是解決其中一些限制的一種方法,但請謹慎使用它,因為它可能會影響將來的性能。

使用本機驅動程序

Animated API旨在可序列化。通過使用本機驅動程序,我們在開始動畫之前將有關動畫的所有內容髮送到本機,允許本機代碼在UI線程上執行動畫,而無需在每個幀上通過橋接。動畫啟動後,可以阻止JS線程而不影響動畫。

使用本機驅動程序進行正常動畫非常簡單。只需在啟動時將useNativeDriver:true添加到動畫配置中即可。
Animated.timing(this.state.animatedValue, {
  toValue: 1,
  duration: 500,
  useNativeDriver: true, // <-- Add this
}).start();
動畫值僅與一個驅動程序兼容,因此如果在對值啟動動畫時使用本機驅動程序,請確保該值上的每個動畫也使用本機驅動程序。

本機驅動程序也適用於Animated.event。這對於跟隨滾動位置的動畫特別有用,因為沒有本機驅動程序,由於React Native的異步性質,動畫將始終在手勢後面運行一個幀。
<Animated.ScrollView // <-- Use the Animated ScrollView wrapper
  scrollEventThrottle={1} // <-- Use 1 here to make sure no events are ever missed
  onScroll={Animated.event(
    [
      {
        nativeEvent: {
          contentOffset: {y: this.state.animatedValue},
        },
      },
    ],
    {useNativeDriver: true} // <-- Add this
  )}>
  {content}
</Animated.ScrollView>
您可以通過運行RNTester應用程序,然後加載本機動畫示例來查看本機驅動程序。您還可以查看源代碼以了解如何生成這些示例。

注意事項

目前,本機驅動程序不支持您使用Animated可以執行的所有操作。主要限制是您只能為非佈局屬性設置動畫:轉換和不透明度等功能將起作用,但Flexbox和位置屬性不會。使用Animated.event時,它只適用於直接事件而不是冒泡事件。這意味著它不能與PanResponder一起使用,但可以使用ScrollView#onScroll之類的東西。

動畫運行時,它可以防止VirtualizedList組件呈現更多行。如果您需要在用戶滾動列表時運行長動畫或循環動畫,則可以在動畫的配置中使用isInteraction:false來防止此問題。

記住

使用轉換樣式(如rotateY,rotateX等)時,請確保轉換樣式透視圖已就位。此時,如果沒有它,某些動畫可能無法在Android上呈現。以下示例。
<Animated.View
  style={{
    transform: [
      {scale: this.state.scale},
      {rotateY: this.state.rotateY},
      {perspective: 1000}, // without this line this Animation will not render on Android while working fine on iOS
    ],
  }}
/>

其他例子

RNTester應用程序有各種使用動畫的示例:

LayoutAnimation API

LayoutAnimation允許您全局配置創建和更新動畫,這些動畫將用於下一個渲染/佈局循環中的所有視圖。這對於執行flexbox佈局更新非常有用,而無需測量或計算特定屬性以直接為它們設置動畫,並且在佈局更改可能影響祖先時尤其有用,例如“看到更多”擴展也會增加父級的大小並按下下面的行,否則需要在組件之間進行顯式協調,以便使它們全部同步動畫。

請注意,儘管LayoutAnimation功能非常強大並且非常有用,但它提供的控制遠遠少於動畫和其他動畫庫,因此如果無法使LayoutAnimation執行您想要的操作,則可能需要使用其他方法。

請注意,為了使其在Android上運行,您需要通過UIManager設置以下標誌:
UIManager.setLayoutAnimationEnabledExperimental &&
  UIManager.setLayoutAnimationEnabledExperimental(true);
此示例使用預設值,您可以根據需要自定義動畫,有關詳細信息,請參閱LayoutAnimation.js。

補充資料

 requestAnimationFrame

requestAnimationFrame是您可能熟悉的瀏覽器中的polyfill。它接受一個函數作為其唯一參數,並在下一次重繪之前調用該函數。它是動畫的基本構建塊,是所有基於JavaScript的動畫API的基礎。通常,您不需要自己調用它 - 動畫API將為您管理幀更新。

 setNativeProps

正如直接操作部分所述,setNativeProps允許我們直接修改本機支持的組件(本機視圖實際支持的組件,不像複合組件)的屬性,而無需setState和重新呈現組件層次結構。

我們可以在Rebound示例中使用它來更新比例 - 如果我們正在更新的組件是深度嵌套的並且尚未使用shouldComponentUpdate進行優化,這可能會有所幫助。

如果您發現您的動畫具有丟幀(執行低於每秒60幀),請查看使用setNativeProps或shouldComponentUpdate來優化它們。或者您可以使用useNativeDriver選項在UI線程而不是JavaScript線程上運行動畫。您可能還希望使用InteractionManager將任何計算密集型工作推遲到動畫完成之後。您可以使用應用程序開發人員菜單“FPS監視器”工具監視幀速率。



You May Also Like

0 意見