大多数状况下,在 React Native 中建立动画是推荐使用 Animated API 的,其提供了三个主要的方法用于建立动画:html
Animated.timing() -- 推进一个值按照一个过渡曲线而随时间变化。Easing
模块定义了不少缓冲曲线函数。react
Animated.decay() -- 推进一个值以一个初始的速度和一个衰减系数逐渐变为0。android
Animated.spring() -- 产生一个基于 Rebound 和 Origami 实现的Spring动画。它会在 toValue
值更新的同时跟踪当前的速度状态,以确保动画连贯。ios
译者注:React Native(0.37) 目前只支持Animated.Text/Animated.View/Animated.Imagegit
以个人经验来看,Animated.timing() 和 Animated.spring() 在建立动画方面是很是有效的。github
除了这三个建立动画的方法,对于每一个独立的方法都有三种调用该动画的方式:spring
Animated.parallel() -- 同时开始一个动画数组里的所有动画。默认状况下,若是有任何一个动画中止了,其他的也会被中止。你能够经过stopTogether
选项来改变这个效果。react-native
Animated.sequence() -- 按顺序执行一个动画数组里的动画,等待一个完成后再执行下一个。若是当前的动画被停止,后面的动画则不会继续执行。api
Animated.stagger() -- 一个动画数组,里面的动画有可能会同时执行(重叠),不过会以指定的延迟来开始。数组
第一个要建立的动画是使用 Animated.timing
建立的旋转动画。
// Example implementation: Animated.timing( someValue, { toValue: number, duration: number, easing: easingFunction, delay: number } )
这种方式经常使用于建立须要loading指示的动画,在我使用React Native的项目中,这也是建立动画最有效的方式。这个理念也能够用于其它诸如按比例放大和缩小类型的指示动画。
开始以前,咱们须要建立一个新的React Native 项目或者一个空的React Native项目。建立新项目以前,须要输入 react-native init
来初始化一个项目,并切换到该项目目录:
react-native init animations cd animations
而后打开 index.android.js
和 index.ios.js
。
如今已经建立了一个新项目,则第一件事是在已经引入的 View 以后从 react native
中引入 Animated,Image 和 Easing:
import { AppRegistry, StyleSheet, Text, View, Animated, Image, Easing } from 'react-native'
Animated 是咱们将用于建立动画的库,和React Native交互的载体。
Image 用于在UI中显示图片。
Easing 也是用React Native建立动画的载体,它容许咱们使用已经定义好的各类缓冲函数,例如:linear, ease, quad, cubic, sin, elastic, bounce, back, bezier, in, out, inout 。因为有直线运动,咱们将使用 linear。在这节(阅读)完成以后,对于实现直线运动的动画,你或许会有更好的实现方式。
接下来,须要在构造函数中初始化一个带动画属性的值用于旋转动画的初始值:
constructor () { super() this.spinValue = new Animated.Value(0) }
咱们使用 Animated.Value 声明了一个 spinValue 变量,并传了一个 0 做为初始值。
而后建立了一个名为 spin
的方法,并在 componentDidMount
中调用它,目的是在 app 加载以后运行动画:
componentDidMount () { this.spin() } spin () { this.spinValue.setValue(0) Animated.timing( this.spinValue, { toValue: 1, duration: 4000, easing: Easing.linear } ).start(() => this.spin()) }
spin()
方法的做用以下:
将 this.spinValue 重置成 0
调用 Animated.timing ,并驱动 this.spinValue
的值以 Easing.linear
的动画方式在 4000 毫秒从 0 变成 1。Animated.timing 须要两个参数,一个要变化的值(本文中是 this.spinValue) 和一个可配置对象。这个配置对象有四个属性:toValue(终值)、duration(一次动画的持续时间)、easing(缓存函数)和delay(延迟执行的时间)
调用 start(),并将 this.spin 做为回调传递给 start
,它将在(一次)动画完成以后调用,这也是建立无穷动画的一种基本方式。start()
须要一个完成回调,该回调在动画正常的运行完成以后会被调用,并有一个参数是 {finished: true}
,但若是动画是在它正常运行完成以前而被中止了(如:被手势动做或者其它动画中断),则回调函数的参数变为 {finished: false}
。
译者注:若是在回调中将动画的初始值设置成其终值,该动画就不会再执行。如将 this.spinValue.setValue(0) 改成 this.spinValue.setValue(1),spin动画不会执行了
如今方法已经建立好了,接下来就是在UI中渲染动画了。为了渲染动画,须要更新 render
方法:
render () { const spin = this.spinValue.interpolate({ inputRange: [0, 1], outputRange: ['0deg', '360deg'] }) return ( <View style={styles.container}> <Animated.Image style={{ width: 227, height: 200, transform: [{rotate: spin}] }} source={{uri: 'https://s3.amazonaws.com/media-p.slid.es/uploads/alexanderfarennikov/images/1198519/reactjs.png'}} /> </View> ) }
在 render
方法中,建立了一个 spin 变量,并调用了 this.spinValue 的 interpolate 方法。interpolate 方法能够在任何一个 Animated.Value 返回的实例上调用,该方法会在属性更新以前插入一个新值,如将 0~1 映射到 1~10。在咱们的demo中,利用 interpolate 方法将数值 0~1 映射到了 0deg~360deg。咱们传递了 inputRange
和 outputRange
参数给interpolate 方法,并分别赋值为 [0,1] 和 &[‘0deg’, ‘360deg’]。
咱们返回了一个带 container
样式值的 View和 带 height, width和 transform 属性的Animated.Image,并将 spin 的值赋给 transform
的 rotate 属性,这也是动画发生的地方:
transform: [{rotate: spin}]
最后,在 container
样式中,使全部元素都居中:
const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center' } })
这个示例动画的最终代码在这里。
这是 Easing
模块的源码连接,从源码中能够看到每个 easing 方法。
我建立了另一个示例项目,里面包含了大部分 easing 动画的实现,能够供你参考,连接在这里。(项目的运行截图)依据在下面:
该项目实现的 easing 动画在 RNPlay 的地址在这里。
上文已经说过了 Animated.timing 的基础知识,这一节会例举更多使用 Animated.timing 与 interpolate 结合实现的动画示例。
下一个示例中,会声明一个单一的动画值, this.animatedValue
,而后将该值和 interpolate
一块儿使用来驱动下列属性值的变化来建立复杂动画:
marginLeft
opacity
fontSize
rotateX
在开始以前,能够建立一个新分支或者清除上一个项目的旧代码。
第一件事是在构造函数中初始化一个须要用到的动画属性值:
constructor () { super() this.animatedValue = new Animated.Value(0) }
接下来,建立一个名为animate
的方法,并在 componentDidMount() 中调用该方法:
componentDidMount () { this.animate() } animate () { this.animatedValue.setValue(0) Animated.timing( this.animatedValue, { toValue: 1, duration: 2000, easing: Easing.linear } ).start(() => this.animate()) }
在 render
方法中,咱们建立 5 个不一样的插值变量:
render () { const marginLeft = this.animatedValue.interpolate({ inputRange: [0, 1], outputRange: [0, 300] }) const opacity = this.animatedValue.interpolate({ inputRange: [0, 0.5, 1], outputRange: [0, 1, 0] }) const movingMargin = this.animatedValue.interpolate({ inputRange: [0, 0.5, 1], outputRange: [0, 300, 0] }) const textSize = this.animatedValue.interpolate({ inputRange: [0, 0.5, 1], outputRange: [18, 32, 18] }) const rotateX = this.animatedValue.interpolate({ inputRange: [0, 0.5, 1], outputRange: ['0deg', '180deg', '0deg'] }) ... }
interpolate 是一个很强大的方法,容许咱们用多种方式来使用单一的动画属性值:this.animatedValue。由于 this.animatedValue
只是简单的从0变到1,于是咱们能将这个值插入到 opacity、margins、text sizes 和 rotation 等样式属性中。
最后,返回实现了上述变量的 Animated.View 和 Animated.Text 组件:
return ( <View style={styles.container}> <Animated.View style={{ marginLeft, height: 30, width: 40, backgroundColor: 'red'}} /> <Animated.View style={{ opacity, marginTop: 10, height: 30, width: 40, backgroundColor: 'blue'}} /> <Animated.View style={{ marginLeft: movingMargin, marginTop: 10, height: 30, width: 40, backgroundColor: 'orange'}} /> <Animated.Text style={{ fontSize: textSize, marginTop: 10, color: 'green'}} > Animated Text! </Animated.Text> <Animated.View style={{ transform: [{rotateX}], marginTop: 50, height: 30, width: 40, backgroundColor: 'black'}}> <Text style={{color: 'white'}}>Hello from TransformX</Text> </Animated.View> </View> )
固然,也须要更新下 container
样式:
const styles = StyleSheet.create({ container: { flex: 1, paddingTop: 150 } })
这个示例动画的最终代码在这里。
接下来,咱们将会使用 Animated.spring() 方法建立动画。
// Example implementation: Animated.spring( someValue, { toValue: number, friction: number } )
咱们继续使用上一个项目,并只须要更新少许代码就行。在构造函数中,建立一个 springValue 变量,初始化其值为0.3:
constructor () { super() this.springValue = new Animated.Value(0.3) }
而后,删除 animated
方法和componentDidMount
方法,建立一个新的 spring
方法:
spring () { this.springValue.setValue(0.3) Animated.spring( this.springValue, { toValue: 1, friction: 1 } ).start() }
将 springValue
的值重置为 0.3
调用 Animated.spring
方法,并传递两个参数:一个要变化的值和一个可配置对象。可配置对象的属性能够是下列的任何值:toValue (number), overshootClamping (boolean), restDisplacementThreshold (number), restSpeedThreshold (number), velocity (number), bounciness (number), speed (number), tension(number), 和 friction (number)。除了 toValue 是必须的,其余值都是可选的,但 friction 和 tension 能帮你更好地控制 spring 动画。
调用 start()
启动动画
动画已经设置好了,咱们将其放在 View 的click事件中,动画元素依然是以前使用过的 React logo 图片:
<View style={styles.container}> <Text style={{marginBottom: 100}} onPress={this.spring.bind(this)}>Spring</Text> <Animated.Image style={{ width: 227, height: 200, transform: [{scale: this.springValue}] }} source={{uri: 'https://s3.amazonaws.com/media-p.slid.es/uploads/alexanderfarennikov/images/1198519/reactjs.png'}}/> </View>
咱们返回一个Text组件,并将 spring() 添加到组件的onPress事件中
咱们返回一个 Animated.Image
,并为其 scale
属性添加 this.springValue
这个示例动画的最终代码在这里。
Animated.parallel() 会同时开始一个动画数组里的所有动画。
先看一下这个api是怎么调用的:
// API Animated.parallel(arrayOfAnimations) // In use: Animated.parallel([ Animated.spring( animatedValue, { //config options } ), Animated.timing( animatedValue2, { //config options } ) ])
开始以前,咱们先直接建立三个咱们须要的动画属性值:
constructor () { super() this.animatedValue1 = new Animated.Value(0) this.animatedValue2 = new Animated.Value(0) this.animatedValue3 = new Animated.Value(0) }
而后,建立一个 animate
方法并在 componendDidMount() 中调用它:
componentDidMount () { this.animate() } animate () { this.animatedValue1.setValue(0) this.animatedValue2.setValue(0) this.animatedValue3.setValue(0) const createAnimation = function (value, duration, easing, delay = 0) { return Animated.timing( value, { toValue: 1, duration, easing, delay } ) } Animated.parallel([ createAnimation(this.animatedValue1, 2000, Easing.ease), createAnimation(this.animatedValue2, 1000, Easing.ease, 1000), createAnimation(this.animatedValue3, 1000, Easing.ease, 2000) ]).start() }
在 animate
方法中,咱们将三个动画属性值重置为0。此外,还建立了一个 createAnimation 方法,该方法接受四个参数:value, duration, easing, delay(默认值是0),返回一个新的动画。
而后,调用 Animated.parallel()
,并将三个使用 createAnimation
建立的动画做为参数传递给它。
在 render
方法中,咱们须要设置插值:
render () { const scaleText = this.animatedValue1.interpolate({ inputRange: [0, 1], outputRange: [0.5, 2] }) const spinText = this.animatedValue2.interpolate({ inputRange: [0, 1], outputRange: ['0deg', '720deg'] }) const introButton = this.animatedValue3.interpolate({ inputRange: [0, 1], outputRange: [-100, 400] }) ... }
scaleText -- 插值的输出范围是从0.5到2,咱们会用这个值对文本按0.5到2的比例进行缩放
spinText -- 插值的输出范围是 0 degrees 到 720 degrees,即将元素旋转两周
introButton -- 插值的输出范围是 -100 到 400,该值会用于 View 的 margin 属性
最后,咱们用一个主 View 包裹三个 Animated.Views:
<View style={[styles.container]}> <Animated.View style={{ transform: [{scale: scaleText}] }}> <Text>Welcome</Text> </Animated.View> <Animated.View style={{ marginTop: 20, transform: [{rotate: spinText}] }}> <Text style={{fontSize: 20}}> to the App! </Text> </Animated.View> <Animated.View style={{top: introButton, position: 'absolute'}}> <TouchableHighlight onPress={this.animate.bind(this)} style={styles.button}> <Text style={{color: 'white', fontSize: 20}}> Click Here To Start </Text> </TouchableHighlight> </Animated.View> </View>
当 animate()
被调用时,三个动画会同时执行。
这个示例动画的最终代码在这里。
先看一下这个api是怎么调用的:
// API Animated.sequence(arrayOfAnimations) // In use Animated.sequence([ Animated.timing( animatedValue, { //config options } ), Animated.spring( animatedValue2, { //config options } ) ])
和 Animated.parallel() 同样, Animated.sequence() 接受一个动画数组。但不一样的是,Animated.sequence() 是按顺序执行一个动画数组里的动画,等待一个完成后再执行下一个。
import React, { Component } from 'react'; import { AppRegistry, StyleSheet, Text, View, Animated } from 'react-native' const arr = [] for (var i = 0; i < 500; i++) { arr.push(i) } class animations extends Component { constructor () { super() this.animatedValue = [] arr.forEach((value) => { this.animatedValue[value] = new Animated.Value(0) }) } componentDidMount () { this.animate() } animate () { const animations = arr.map((item) => { return Animated.timing( this.animatedValue[item], { toValue: 1, duration: 50 } ) }) Animated.sequence(animations).start() } render () { const animations = arr.map((a, i) => { return <Animated.View key={i} style={{opacity: this.animatedValue[a], height: 20, width: 20, backgroundColor: 'red', marginLeft: 3, marginTop: 3}} /> }) return ( <View style={styles.container}> {animations} </View> ) } } const styles = StyleSheet.create({ container: { flex: 1, flexDirection: 'row', flexWrap: 'wrap' } }) AppRegistry.registerComponent('animations', () => animations);
因为 Animated.sequence()
和 Animated.parallel()
很类似,于是对 Animated.sequence()
就很少做介绍了。主要不一样的一点是咱们是使用循环建立 Animated.Values。
这个示例动画的最终代码在这里。
(图片太大,上传不了。gif动态图)
先看一下这个api是怎么调用的:
// API Animated.stagger(delay, arrayOfAnimations) // In use: Animated.stagger(1000, [ Animated.timing( animatedValue, { //config options } ), Animated.spring( animatedValue2, { //config options } ) ])
和 Animated.parallel() 和 Animated.sequence() 同样, Animated.Stagger 接受一个动画数组。但不一样的是,Animated.Stagger 里面的动画有可能会同时执行(重叠),不过会以指定的延迟来开始。
与上述两个动画主要的不一样点是 Animated.Stagger 的第一个参数,delay
会被应用到每个动画:
import React, { Component } from 'react'; import { AppRegistry, StyleSheet, Text, View, Animated } from 'react-native' const arr = [] for (var i = 0; i < 500; i++) { arr.push(i) } class animations extends Component { constructor () { super() this.animatedValue = [] arr.forEach((value) => { this.animatedValue[value] = new Animated.Value(0) }) } componentDidMount () { this.animate() } animate () { const animations = arr.map((item) => { return Animated.timing( this.animatedValue[item], { toValue: 1, duration: 4000 } ) }) Animated.stagger(10, animations).start() } render () { const animations = arr.map((a, i) => { return <Animated.View key={i} style={{opacity: this.animatedValue[a], height: 20, width: 20, backgroundColor: 'red', marginLeft: 3, marginTop: 3}} /> }) return ( <View style={styles.container}> {animations} </View> ) } } const styles = StyleSheet.create({ container: { flex: 1, flexDirection: 'row', flexWrap: 'wrap' } }) AppRegistry.registerComponent('SampleApp', () => animations);
这个示例动画的最终代码在这里。
文中使用的demo repo: react native animations