1. 程式人生 > >ReactNative之結合具體示例來看RN中的的Timing動畫

ReactNative之結合具體示例來看RN中的的Timing動畫

今天繼續更新RN相關的部落格。上篇部落格詳細的聊了RN中關於Flex佈局的相關東西,具體請參見《ReactNative之參照具體示例來看RN中的FlexBox佈局》。本篇部落格繼續更新RN的動畫部分,部落格中的內容依然是依託於具體的示例來進行的。

下方是官網對RN動畫的的一個綜述,意思就是說在RN的元件中View、Text、Image 和ScrollView是支援動畫的,不過你可以使用Animated.createAnimatedComponent()這個方法來建立一個支援動畫的元件稍後會介紹到。這些支援動畫的元件在使用動畫是都差不多,本篇部落格中的示例主要以View為主,也會有Text、Image的部分動畫。

Animated exports four animatable component types: ViewTextImage, and ScrollView, but you can also create your own using Animated.createAnimatedComponent().

 

一、一個簡單的Moving動畫

第一部分我們先從一個簡單的Moving動畫來入手。這個Moving動畫是非常簡單的,就是一個View,然後點選這個View,會從一個地方移動到另一個地方。然後再次點選會迴歸到原位。下方是效果實現的分析:

  • 首先我們會為View新增一個點選事件,點選View時,從一個位置移到另一個位置。
  • 再次點選時,會回到上次的一個位置。
  • 在移動時我們加了一個Bounce的一個動畫效果。

  

 

下方程式碼段是上述動畫效果的部分程式碼。程式碼比較簡單:

  • 首先在State中定義了一個型別為 Animated.Value 的動畫值,該值就負責來記錄動畫路徑的值。該值在元件的構造器中進行了初始化,其初始值為零。
  • 然後就是 pressView 方法了,該方法就是上述紅色View點選時所執行的方法。為了點選反覆移動,我們使用了 toValue來記錄下次要運動的重點值。這個toValue值 0 和 1稍後會詳細介紹。
  • 然後就是關鍵了,呼叫了Animated 的timing 方法,該方法是用來配置一些動畫效果的,比如設定動畫執行時間的duration(單位為ms)、設定目標值的 toValue屬性,以及指定緩動效果的熟悉 easing。關於這個easing下方會有詳細的Demo介紹到。
  • 設定完動畫所執行的引數後,就呼叫了start() 方法來執行這個動畫了。

  

 

上面這段程式碼是動畫設定的相關程式碼,下方這塊程式碼是動畫使用的相關程式碼片段。下方是對這段程式碼的解析:

  • 首先是從state中取出了動畫值,我們將該值付給了moveValue。
  • 然後我們是根據這個 moveValue 通過差值函式建立了一個 toValue的值,這個值就是我們動畫的目標值,也是我們真正要使用的值。
  • 這個 interpolate() 差值函式負責用來指定 toValue的生成規則, 該函式可以把這個

  

 完整程式碼片段:

 1 type States = {
 2   moveValue: Animated.Value  // 儲存動畫的值
 3 }
 4 
 5 class MoveViewTestComponent extends Component<null, States> {
 6   toValue = 0
 7   constructor (props) {
 8     super(props)
 9     this.state = {
10       moveValue: new Animated.Value(0)
11     }
12   }
13 
14   pressView = () => {
15     this.toValue = this.toValue === 0 ? 1 : 0
16     Animated.timing(
17       this.state.moveValue,  // 初始化從0開始
18       {
19         toValue: this.toValue, // 目標值
20         duration: 1000,         // 時間間隔
21         easing: Easing.bounce // 緩動函式
22       }
23     ).start()
24   }
25 
26   render () {
27     let { moveValue } = this.state
28 
29     let toValue = moveValue.interpolate({
30       inputRange: [0,1],
31       outputRange: ['10%', '60%']
32     })
33     return (
34       <TouchableOpacity onPress={this.pressView}>
35         <Animated.View                 // 使用專門的可動畫化的View元件
36           style={{
37             width: 100,
38             height: 50,
39             backgroundColor: 'red',
40             left: toValue,
41           }}
42         >
43           <Text style={{ color: '#fff' }}> Tap Me Move </Text>
44         </Animated.View>
45       </TouchableOpacity>
46     )
47   }
48 }
View Code

 

上面設設定的left屬性,我們還可以對上述程式碼進行修改,使用動畫來改變每個角的弧度,具體動畫效果如下所示:

 

程式碼就比較簡單了,就是把動畫的值直接賦值給我們的 borderRadius 屬性即可。

  

 

 

二、使用Easing函式指定相關的動畫效果

在上面的示例中我們指定的移動動畫的Bounce效果,下方我們將通過一個示例來看一下Easing中所有的效果,具體動畫效果如下所示。從下方的示例中我們不難看出,每種效果的動畫運動軌跡都不同,我們在平時開發中可以根據自己的需要來選擇相關的效果。當然我們還可以通過矩陣來定義動畫的變換路徑,在此就不做過多贅述了。

  

 

接下來我們來看一下上述動畫實現的一個Demo的設計與實現。

首先我們定義了一個MoveView元件,也就是對應著上述Demo中的每個Button,上面可點選可移動的每個View就是一個MoveView。該View會從外部接收一個easing引數,該引數會被設定為該View的動畫效果。具體程式碼如下所示:

  

 

 然後我們建立了一個 TestMoveView 的一個元件,這個元件就是上述演示的內容。在 TestMoveView 中我們定義了兩個陣列,第一個陣列用來存放每個按鈕的Title, 第二個陣列則用來存放相關按鈕的動畫型別。稍後會用到下方的這兩個陣列。

  

 

下方就是兩個比較核心的方法了, 下方是對這兩個方法的介紹

  • 第一個 item 方法用來建立一個 MoveView,該View對應的就是上述一個按鈕。第一個引數Title就是按鈕上的title, 二後邊的easing則是動畫效果。
  • 在Item方法中我們給 MoveView 設定了一個ref的屬性,該屬性的Value值我們用的是按鈕上的Title。設定完這個ref值後,我們可以使用 this.refs 來獲取相關key值的物件。稍後我們會用到。 
  • 然後就是 createItem 方法了,該方法負責呼叫 上面我們事先建立好的陣列,從陣列中取出相關的值,然後呼叫 item 方法建立一系列的 MoveView 放到相關的數組裡並返回。
  • 在 Render 方法中我們就可以呼叫下方的這個 createItem 方法來建立相關的按鈕了。上的圖片中能動的按鈕都是通過這個 CreateItem 方法來建立的。

  

 

最後部分我們就來看一下 點選Tap Me 按鈕所執行的相關方法了,下方的Click就是該方法。 在Click方法中主要做的就是呼叫 this.refs 方法獲取所有可移動的MoveView的物件,然後呼叫該物件的pressView方法執行相關的動畫。所有點選 Tap Me 按鈕,下方所有的按鈕都會運動。

   

完整程式碼:

  1 import React,{ Component } from 'react'
  2 // import MoveView from './MoveView'
  3 import { Animated, Easing, Text, TouchableOpacity, View } from 'react-native'
  4 
  5 type EasingType = (value: number) => number
  6 
  7 export default class TestMoveView extends Component {
  8   animatedKey: string[] = [
  9     'linear',
 10     'quad',
 11     'cubic',
 12     'sin',
 13     'exp',
 14     'bounce',
 15     'poly-5',
 16     'elastic-2',
 17     'back-2',
 18     'bezier'
 19   ]
 20 
 21   animatedEasingType: EasingType[] = [
 22     Easing.linear,
 23     Easing.quad,
 24     Easing.cubic,
 25     Easing.sin,
 26     Easing.exp,
 27     Easing.bounce,
 28     Easing.poly(5),
 29     Easing.elastic(2),
 30     Easing.back(2),
 31     Easing.bezier(0,1.6, 1,-0.67)
 32   ]
 33 
 34   click = () => {
 35     for (let i = 0; i < this.animatedKey.length; i++) {
 36       this.refs[this.animatedKey[i]].pressView()
 37     }
 38   }
 39 
 40   item = (title: string, easing: EasingType) => {
 41     return (
 42       <MoveView
 43         easing= {easing}
 44         ref = {title}>
 45         <Text style={{ fontSize: 17, textAlign: 'center' }}>
 46           {title}
 47         </Text>
 48       </MoveView>
 49     )
 50   }
 51 
 52   createItems = () => {
 53     let items = []
 54     for (let i = 0; i < this.animatedKey.length; i++) {
 55       items.push(this.item(this.animatedKey[i], this.animatedEasingType[i]))
 56     }
 57     return items
 58   }
 59 
 60   render () {
 61     console.log('lizelu')
 62     return (
 63       <View style={{ flex: 1, justifyContent: 'center' }}>
 64         <TouchableOpacity onPress={this.click}>
 65           <Text>Tap Me</Text>
 66         </TouchableOpacity>
 67         { this.createItems() }
 68       </View>
 69     )
 70   }
 71 }
 72 
 73 // MoveView
 74 
 75 type MoveViewProps = {
 76   easing?: (value: number) => number
 77 }
 78 
 79 class MoveView extends Component<MoveViewProps> {
 80   toValue = 0
 81   state = {
 82     moveValue: new Animated.Value(0)
 83   }
 84 
 85   pressView = () => {
 86     this.toValue = this.toValue === 0 ? 1 : 0
 87     Animated.timing(
 88       this.state.moveValue,  // 初始化從0開始
 89       {
 90         toValue: this.toValue, // 目標值
 91         duration: 1000,         // 時間間隔
 92         easing: this.props.easing // 動畫效果
 93       }
 94     ).start()
 95   }
 96 
 97   render () {
 98     let { moveValue } = this.state
 99     let toValue = moveValue.interpolate({
100       inputRange: [0,1],
101       outputRange: ['10%', '70%']
102     })
103     return (
104       <TouchableOpacity onPress={this.pressView}>
105         <Animated.View                 // 使用專門的可動畫化的View元件
106           style={[{
107             width: 80,
108             height: 30,
109             backgroundColor: 'powderblue',
110             margin: 2,
111             left: toValue   // 動畫的目標值
112           }]}
113         >
114           {this.props.children}
115         </Animated.View>
116       </TouchableOpacity>
117     )
118   }
119 }
Easing

 

 

三、動畫的插值函式及transform

1、插值函式

接下來我們通過一個Loading中經常使用的旋轉動畫,來看一下RN動畫中的插值函式。下方的Loading動畫本質上就是一張圖片在不停的轉圈,不過在轉圈的時候我們設定了一個Circle的動畫效果。

  

需要實現的效果就是上面這個效果,然後我們看一下程式碼實現以及插值函式的使用。首先我們來看一下上述動畫啟動時的相關程式碼:

  • 首先在 ComponentDidMount 方法中呼叫了啟動方法的函式 startAnimation
  • startAnimation 函式中,我們通過 Animation的 loop 方法來執行迴圈動畫動畫的值從 0 到 1
  • 並且我們設定了動畫效果為 circle
  • 最後就是呼叫start方法啟動動畫了。

  

 

然後就是Render方法中獲取動畫值,給相關的元件設定動畫了,具體程式碼如下所示:

  • 首先我們從state中獲取到相關的動畫值 animationValue
  • 然後呼叫該動畫值的插值函式 interpolate,將動畫值中的 0~1的範圍對映成角度 0deg ~ 360deg
  • 最後就是將這個插值函式生成的值 rotateZValue設定給 Image的transform即可。

  

 

2、Transform

經過上述步驟我們的圖片就轉起來了。插值函式在動畫中還是比較常用的,上面是把 0 ~ 1對映成角度,我們還可以將該值對映成透明度、顏色等等,總之插值函式是RN動畫中比較重要的角色。而且我們可以給一個RN元素設定多個插值動畫,這樣這個元素就會有多個動畫效果。

下方這個動畫就由多個插值函式結合著多種變換方式組合而成的,分別是移動、旋轉、放大這三種變換同時作用到一個View上所展示的效果,具體效果如下所示:

  

上述效果是在第一個轉圈的動畫中豐富了一下而形成的,具體程式碼如下:

  • 前兩個負責生成移動和縮放效果使用的值的插值函式和上面那個轉圈的比較一致,只不過對映的值不同。
  • 然後看第三個旋轉使用的插值函式就稍微有點不同了,該插值函式可以將 0 ~ 1 不同的區間的值對映成不同範圍的值, 從這個旋轉的插值函式的對映關係不難看出,上述View的旋轉路徑是先快後慢的,這一點在插值函式中也是比較常用的。
  • 最後就是將這三個插值函式所生成的結果設定在View的 transform 的各個key中就可以了。

  

 

上面是縮放、移動、旋轉的變換。下面我們來看一下斜切的變換。下方第一個是X方向上的斜切,第二個是Y軸方向上的斜切。

 

    

程式碼也比較簡單,下方是設定斜切的相關程式碼:

  

  

天不早了,今天部落格就先到這兒,下篇部落格繼續RN動畫的相關內容。下篇部落格我們會通過一系列的“拉皮條”操作來看一下RN中的Spring動畫。下篇的“拉皮條”的示例還是比較有意思的。稍後會更新。