ReactNative之從「拉皮條」來看RN中的Spring動畫

上篇博客咱們聊了RN中關於Timing的動畫,詳情請參見於《ReactNative之結合具體示例來看RN中的的Timing動畫》本篇博客咱們將從一個「拉皮條」的一個動畫提及,而後來看一下RN中Spring動畫的使用方式以及具體效果。Spring從名字中不難看出是彈性彈簧的意思,也就是咱們可使用Spring這個動畫來實現一些彈性的動畫效果。本部分咱們先經過一個「拉皮條」的示例來簡單的看一下Spring動畫的使用方式,而後在看一下Spring動畫中可配置的屬性以及每一個屬性的做用。html

 

1、從「拉皮條」談起

此拉皮條非彼「拉皮條」,此拉皮條是正經拉皮條,簡單的說,就是有一個皮條,咱們用勁拉他,而後再鬆開觀察皮條的運行軌跡。下方就是咱們「拉皮條」的示例,在這個「拉皮條」的示例中,咱們主要使用了Animation中的Spring動畫。下方這個Demo中這個灰色的帶子就是咱們要拉的皮條,一邊是黑色的固定皮條的東西,一端是能夠拉動的紅色方框,咱們往一邊拉動紅色方塊,這個皮條就會被拉伸,放手後皮條就會拉動咱們的方塊到原位置,固然這個拉動的過程當中是符合彈簧拉伸效果的。react

下方是調整方塊質量的操做區,從下方效果中不難看出,當質量越大時慣性就越大,方塊來回擺動的幅度就越大,這也是符合彈簧的特性的。spring

 

 

效果就是這麼個效果,接下來,咱們來看一下上述效果的具體代碼實現,代碼也不算太多,下方會把核心的代碼拿出來聊聊。首先來看一下上述示例中用到的State。在State中有三個值,以下所示:react-native

  • animationValue: 該值的類型爲  Animated.ValueXY,ValueXY存放的是{x, y}的對象,其中這個x的值就是拖動後皮條拉伸後的X值,這個y值咱們用來設置皮條的粗細度,也就是皮條的height。
  • mass: 而後就是這個mass(質量),咱們用他來存放方塊的質量的。
  • moveX: 該值用來存放手指移動時的X值的,用做在移動時實時更新皮條的拉伸度以及方塊位置。

  

 

看完上述的State,接下來咱們來看一下本Demo中涉及的手勢操做。下方的這個 DisplayView 就是整個皮條以及方塊所在的父View。下方是該View所涉及的手勢操做:ide

  • onStartShouldSetResponder: 首先經過該屬性開啓手勢相應者,在該屬性接收到方法中返回true來打開響應者。
  • onResponderMove: 該屬性所設置的方法就是是手指移動時所執行的回調,對應着iOS中的  touchMove 事件。經過該事件咱們能夠實時的拿到移動過程當中的相關座標。
  • onResponderRelease: 該屬性所對應的方法會在手指離開屏幕時觸發,咱們能夠在該事件中來打開 「皮條」 收縮的動畫。

而下方截圖中的這個 touchUp 事件就是手指離開屏幕時所觸發的動做。在該事件中,咱們更新了 State 中的moveX,咱們使用的是pageX,也就是相對應頁面的X值,這個MoveX咱們設置的是方塊的中心位置,根據具體的佈局,咱們須要作個 45 的糾正,這個糾正後的值就是方塊要移動的地方。簡單的說也就是手指移動的地方就是方塊的中心點。設置完 MoveX 後,咱們就開啓了Spring動畫,這個方塊就會隨着皮條的拉動往回走。佈局

而這個 MoveView 方法就是隨着手指的移動試試的更新State中的MoveX的值,而方塊的位置就是根據這個State中MoveX的值決定的。post

上述是咱們本次動畫中所涉及的幾個事件,固然還有其餘好多的手勢事件,之後有機會能夠在其餘博客中詳細的來介紹一下RN中經常使用的手勢操做,關於手勢在此就不作過多贅述了。flex

  

 

下方就是上述在 touchUp 方法中調用的啓動Spring動畫的相關方法,代碼比較簡單。就是設置了一下animation的目標值,及下方的animationValue, 以及設置了一下Spring動畫的配置對象,即下方的config對象,其中的 mass 就是本示例中方塊的質量。具體代碼以下所示:動畫

  

 

下方代碼就是對應的就是紅色方塊的代碼實現,在該代碼中,咱們爲方塊動態設置了 left。在手動滑動時,這個left的值隨着手指移動的位置變化而變化,而當開始動畫時,這個Left的值對應的就是 animationValue 中的x的值。具體以下所示:this

  

 

關於本次這個 「拉皮條」 的示例的介紹就先到這兒,畢竟篇幅有限,下方是上述示例的完整代碼:

「拉皮條」示例代碼

 

 

2、「拉皮條」 XS Max版本

Spring動畫有好多屬性,這些屬性對應着彈簧的各個物理特性,下方這個Demo 是上述「拉皮條」的一個升級版本,經過該Demo,咱們能夠很好的來觀察Spring動畫中各個屬性的做用,從而能夠判斷相關屬性的各個使用場景。下方是「拉皮條」 的XS Max 版本。

  

 

備註:在上面第一個gif的最後有一個報錯,下方是具體的報錯內容,該錯誤的緣由是咱們設置的Spring的動畫屬性中衝突了。根據提示咱們不難發現那些屬性會衝突。咱們能夠根據錯誤提示把屬性分爲三組,(bounciness、speed ), (tenson、friction)以及(stiffness、damping、mass)若是設置了其中一個組的任何一個屬性,那麼其餘兩組中的屬性都能再設置了,由於設置完後違反彈簧相關的物理定律,是不合規的,因此會報錯。

You can define one of bounciness/speed, tenson/friction, or stiffness/damping/mass, but not more than one。

   

下方是該Demo中所涉及的屬性:

一、friction - 摩擦力

摩擦摩擦,在光滑的地板上摩擦……」,關於什麼是摩擦力就很少說了,由於你們都知道穿着滑板鞋在光滑的地板上摩擦~摩擦~。該屬性對應的就是滑塊的摩擦力,根據物理常識摩擦力越大滑塊被皮條拉伸的也就越慢,當摩擦力達到必定程度時,滑塊就是勻速的運動了,而不是拉不動的狀況,下方是具體的表現效果:

  

 

二、tension - 張力

"張力,物理學名詞。物體受到拉力做用時,存在於其內部而垂直於兩鄰部分接觸面上的相互牽引力。", 額~上面就是張力的解釋,從物理字面量看,張力越大,方塊被拉回的速度也就越快。下方這個Demo就能體現出這一點。從下方的圖片中不難看出,隨着張力的逐漸增大,這個方塊被拉回的速度也就越快。

從上面的備註中咱們可知,張力是能夠和摩擦力一塊設置的,因此下方咱們設置tension的時候,也選中了friction。摩擦力大的話會使張力對滑塊的做用力減少,這也是符合物理規律的。

  

 

三、bounciness - 抖

一個字兒歸納就是「抖」,bounciness的值越大,這個滑塊被拉回來是抖的就越厲害。下方就是這個「抖」的具體示例,從下方不難看出這個抖的值越大,方塊回去時就越抖。

  

 

四、speed - 速度

速度及滑塊被「皮條」拉回的速度, 當這個 speed 的值越大時,滑塊就越容易被拉回,並且speed是能夠和上面的「抖」bounciness一塊設置的。下方就是Speed的相關效果。

  

 

五、stiffness - 剛度

剛度這個玩意兒也是個物理名詞,剛度指材料或結構在受力時抵抗彈性變形的能力。經過這個解釋咱們不難看出,剛度越大,說明彈簧越不容易變形,越不容易變形的狀況下,若是拉伸後就越快的恢復原形。對於這個剛度能夠簡單的理解爲彈簧的剛度越好,那麼這個彈簧的彈性就越好。下方就是剛度的表現:

  

 

六、damping - 阻尼

阻尼(damping) 的物理意義是力的衰減,或物體在運動中的能量耗散。通俗地講,就是阻止物體繼續運動。當物體受到外力做用而振動時,會產生一種使外力衰減的反力,稱爲阻尼力(或減震力) 。換句話說,阻尼就是「減震」,做用就是用來防止物體來回抖動的,這個與上面聊的那個「抖」 - bounciness 正好相反。阻尼越大,物體在運動過程當中就越不抖,越小就抖的厲害。

阻尼的值必須大於零,並且阻尼能夠與上面的剛度- stiffness 一塊設置。兩個阻尼相同,剛度越大抖的越厲害。

  

 

七、 mass - 質量

上面第一部分咱們就聊質量了,物體的質量越大,慣性越大。一樣一根彈簧,質量越大就抖的越厲害。在Spring動畫中,stiffness(剛度)、damping(阻尼)和mass(質量)這三者是能夠一塊設置的。具體效果以下所示:

  

 

八、delay - 延遲

這個就比較好理解了,就是在滑塊被皮條拉回去時的一個延遲,單位是毫秒。下方就是關於delay的演示。

  

 

上述就是RN中Spring中經常使用的配置參數了,能夠根據不一樣的效果來具體設置不一樣的值。這些參數在不設置時也是有值的,下方是上述各個參數的默認值。

  

 

在本Demo中還用到了動畫的一個知識點,那就是同步執行動畫,一個是負責滑塊的動畫,一個負責皮條的動畫。

  

 

下方是該部分Demo的所有代碼,代碼很少也就200行左右。

  1 import {
  2   Animated,
  3   TouchableOpacity,
  4   View,
  5   Text,
  6   StyleSheet,
  7   GestureResponderEvent
  8 } from 'react-native'
  9 import { Component } from 'react'
 10 import React from 'react'
 11 
 12 type States = {
 13   animationValue: Animated.Value
 14   heightValue: Animated.Value
 15   configValue: any
 16   configLineValue: any
 17   moveX: number
 18 }
 19 
 20 // BorderView
 21 export default class SpringAnimationView extends Component<null, States> {
 22   isStartAnimation = false
 23   configKey = [
 24     'friction',  // 摩擦力
 25     'tension',   // 張力
 26     'bounciness', // 彈性
 27     'speed',      // 速度
 28     'stiffness',  // 剛度
 29     'damping',    // 阻尼
 30     'mass',       // 質量
 31     'delay'  // 延遲
 32   ]
 33 
 34   // 各個參數的默認值
 35   defaultValue = {
 36     friction: 7,
 37     tension: 40,
 38     bounciness: 8,
 39     speed: 12,
 40     stiffness: 100,
 41     damping: 10,
 42     mass: 1,
 43     delay: 0
 44   }
 45 
 46   constructor (props) {
 47     super(props)
 48     this.state = {
 49       animationValue: new Animated.Value(0),
 50       heightValue: new Animated.Value(0),
 51       configValue: { },
 52       configLineValue: { },
 53       moveX: 30
 54     }
 55   }
 56 
 57   // 拖動擡起時執行的回調方法
 58   touchUp = (evt) => {
 59     this.isStartAnimation = true
 60     this.setState({ moveX: evt.nativeEvent.pageX - 45 })
 61     this.startAnimation()
 62   }
 63 
 64   // 移動View執行的方法
 65   moveView = (evt: GestureResponderEvent) => {
 66     this.isStartAnimation = false
 67     this.setState({ moveX: evt.nativeEvent.pageX - 45 })
 68   }
 69 
 70   // 開始動畫
 71   startAnimation = () => {
 72     this.state.animationValue.setValue(this.state.moveX)
 73     this.state.heightValue.setValue(300 / this.state.moveX)
 74     Animated.parallel([
 75       Animated.spring(this.state.animationValue, this.getConfigValue(30)),
 76       Animated.spring(this.state.heightValue, this.getSecondConfigValue(10))
 77     ]).start()
 78   }
 79 
 80   // 獲取動畫執行的配置項
 81   getConfigValue = (toValue: number) => {
 82     let config = this.state.configValue
 83     config.toValue = toValue
 84     return config
 85   }
 86 
 87   // 獲取動畫執行的配置項
 88   getSecondConfigValue = (toValue: number) => {
 89     let config = this.state.configLineValue
 90     config.toValue = toValue
 91     return config
 92   }
 93 
 94   // 點擊配置項所執行的事件
 95   clickConfigPress = (key: string) => () => {
 96     let config = this.state.configValue
 97     if (config[key] === undefined) {
 98       config[key] = this.defaultValue[key]
 99     } else {
100       config[key] = undefined
101     }
102     this.setState({ configValue: config, configLineValue: { ...config } })
103   }
104 
105   add = (key: string) => () => {
106     this.defaultValue[key] += 5
107     this.updateStateValue(key)
108   }
109 
110   desc = (key: string) => () => {
111     this.defaultValue[key] -= 5
112     if (this.defaultValue[key] < 0) {
113       this.defaultValue[key] = 0
114     }
115     this.updateStateValue(key)
116   }
117 
118   updateStateValue = (key: string) => {
119     let config = this.state.configValue
120     if (config[key] !== undefined) {
121       config[key] = this.defaultValue[key]
122     }
123     this.setState({ configValue: config })
124   }
125 
126   addOrDescView = (title: string, presse: () => void) => {
127     return (
128       <TouchableOpacity onPress={presse}>
129         <View style={style.textView}>
130           <Text style={ style.textStyle}> {title} </Text>
131         </View>
132       </TouchableOpacity>
133     )
134   }
135 
136   configView = (key: string, index: number) => {
137     const {
138       configValue
139     } = this.state
140     let backgroundColor = '#000'
141     if (configValue[key] !== undefined) {
142       backgroundColor = '#f00'
143     }
144     return (
145       <View key={index} style={{ flex: 1, flexDirection: 'row', height: 60 }}>
146         <TouchableOpacity onPress={this.clickConfigPress(key)}>
147           <View style={[style.textView, { backgroundColor: backgroundColor }]}>
148             <Text style={ style.textStyle}> {key} </Text>
149           </View>
150         </TouchableOpacity>
151 
152         {this.addOrDescView('-', this.desc(key))}
153 
154         <View style={[style.textView, { backgroundColor: '#fff' }]}>
155           <Text style={ [style.textStyle, { color: '#000' }]}> {this.defaultValue[key]} </Text>
156         </View>
157         {this.addOrDescView('+', this.add(key))}
158       </View>
159     )
160   }
161 
162   animatedView = () => {
163     let left: any = this.state.moveX
164     if (this.isStartAnimation) {
165       left = this.state.animationValue
166     }
167     return (
168       <Animated.View
169         style={{
170           height: 50,
171           width: 50,
172           left: left,
173           backgroundColor: '#f00',
174           position: 'absolute',
175           borderRadius: 10
176         }}/>
177     )
178   }
179 
180   displayView = () => {
181     let width: any = this.state.moveX
182     let height: any = 300 / this.state.moveX
183     if (this.isStartAnimation) {
184       width = this.state.animationValue
185       height = this.state.heightValue
186     }
187     return (
188       <View style={style.displayView}
189             onStartShouldSetResponder={() => { return true }}
190             onResponderRelease={this.touchUp}
191             onResponderMove={this.moveView}>
192         <Animated.View style={{ height: height, width: width, backgroundColor: '#fff' }}/>
193         {this.animatedView()}
194       </View>
195     )
196   }
197 
198   render () {
199     return (
200       <View style={{ flex: 1 , margin: 10 }}>
201         {/*拖動的View*/}
202         {this.displayView()}
203 
204         {/*操做配置項的View*/}
205         {
206           this.configKey.map((key, index) => {
207             return this.configView(key, index)
208           })
209         }
210       </View>
211     )
212   }
213 }
214 
215 const style = StyleSheet.create({
216   textView: {
217     justifyContent: 'center',
218     alignItems: 'center',
219     height: 50,
220     backgroundColor: '#000',
221     margin: 10,
222     padding: 10,
223     borderRadius: 10,
224     borderWidth: 1
225   },
226   textStyle: {
227     textAlignVertical: 'center',
228     color: '#fff'
229   },
230   displayView: {
231     width: '100%',
232     height: 50,
233     backgroundColor: '#ccc',
234     borderLeftColor: '#000',
235     borderLeftWidth: 3,
236     borderBottomColor: '#000',
237     borderBottomWidth: 1,
238     flexDirection: 'row',
239     alignItems: 'center'
240   }
241 })
「拉皮條」升級版代碼

 

本篇的「拉皮條」的動畫就到這兒吧。

相關文章
相關標籤/搜索