今天繼續更新RN相關的博客。上篇博客詳細的聊了RN中關於Flex布局的相關東西,具體請參見《ReactNative之參照具體示例來看RN中的FlexBox布局》。本篇博客繼續更新RN的動畫部分,博客中的內容依然是依托於具體的示例來進行的。
下方是官網對RN動畫的的一個綜述,意思就是說在RN的組件中View、Text、Image 和ScrollView是支持動畫的,不過你可以使用Animated.createAnimatedComponent()這個方法來創建一個支持動畫的組件稍后會介紹到。這些支持動畫的組件在使用動畫是都差不多,本篇博客中的示例主要以View為主,也會有Text、Image的部分動畫。
Animated
exports four animatable component types:View
,Text
,Image
, andScrollView
, but you can also create your own usingAnimated.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 }
上面設設置的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 }
三、動畫的插值函數及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動畫。下篇的“拉皮條”的示例還是比較有意思的。稍后會更新。