http://reactnative.cn/docs/0.50/animated.html#timing
http://reactnative.cn/docs/0.50/animations.html#content
Animated
Animated库使得开发者可以非常容易地实现各种各样的动画和交互方式,并且具备极高的性能。Animated旨在以声明的形式来定义动画的输入与输出,在其中建立一个可配置的变化函数,然后使用简单的start/stop方法来控制动画按顺序执行。 Animated仅封装了四个可以动画化的组件:View、Text、Image和ScrollView,不过你也可以使用Animated.createAnimatedComponent()来封装你自己的组件。 下面是一个在加载时带有淡入动画效果的视图:
简单的例子:
// FadeInView.js import React,{ Component } from 'react'; import { Animated,//导入 } from 'react-native'; export default class FadeInView extends Component { constructor(props) { super(props); this.state = { fadeAnim: new Animated.Value(0),// 动画的初始值设为0,这里是要实现透明度,所以初始透明度为0 }; } componentDidMount() { //执行动画函数 Animated.timing( // 随时间变化而执行的动画类型 this.state.fadeAnim,// 动画中的变量值 { toValue: 1,// 透明度最终变为1,即完全不透明 duration: 500 //动画执行时间 } ).start(); // 开始执行动画 } render() { return ( <Animated.View // 可动画化的视图组件 style={{ ...this.props.style,opacity: this.state.fadeAnim,// 将透明度指定为动画变量值 }} > {this.props.children} </Animated.View> ); } }
然后你就可以在组件中像使用View那样去使用FadeInView了,比如像下面这样:
render() { return ( <FadeInView style={{width: 250,height: 50,backgroundColor: 'powderblue'}}> <Text style={{fontSize: 28,textAlign: 'center',margin: 10}}>Fading in</Text> </FadeInView> ) }
概览
Animated提供了两种类型的值:
- Animated.Value()用于单个值
- Animated.ValueXY()用于矢量值
Animated.Value可以绑定到样式或是其他属性上,也可以进行插值运算。单个Animated.Value可以用在任意多个属性上。
ValueXY
是一个方便的处理2D交互的办法,譬如旋转或拖拽。它是一个简单的包含了两个Animated.Value
实例的包装,然后提供了一系列辅助函数,使得ValueXY
在许多时候可以替代Value
来使用
配置动画
动画拥有非常灵活的配置项。自定义的或预定义的easing函数、延迟、持续时间、衰减系数、Animations are heavily configurable. Custom and predefined easing functions,delays,durations,decay factors,spring constants,and more can all be tweaked depending on the type of animation.
Animated
提供了多种动画类型,其中最常用的要属Animated.timing()
。 It supports animating a value over time using one of varIoUs predefined easing functions,or you can use your own. Easing functions are typically used in animation to convey gradual acceleration and deceleration of objects.
By default,timing
will use a easeInOut curve that conveys gradual acceleration to full speed and concludes by gradually decelerating to a stop. You can specify a different easing function by passing aeasing
parameter. Customduration
or even adelay
before the animation starts is also supported.
For example,if we want to create a 2-second long animation of an object that slightly backs up before moving to its final position:
Animated
提供了三种动画类型。每种动画类型都提供了特定的函数曲线,用于控制动画值从初始值变化到最终值的变化过程:
Animated.decay()
以指定的初始速度开始变化,然后变化速度越来越慢直至停下。 starts with an initial velocity and gradually slows to a stop.Animated.spring()
基础的单次弹跳物理模型Animated.timing()
animates a value over time usingeasing函数..时间和变量线性变化
easing:http://reactnative.cn/docs/0.50/easing.html
Animated.timing( this.state.xPosition,{ toValue: 100,easing: Easing.back,// 线性的渐变函数 duration: 2000,} ).start();
组合动画
Animations can also be combined in complex ways using composition functions:
- Animated.delay() starts an animation after a given delay.
- Animated.parallel() starts a number of animations at the same time.
- Animated.sequence() starts the animations in order,waiting for each to complete before starting the next.
- Animated.stagger() starts animations in order and in parallel,but with successive delays.
多个动画可以通过parallel(同时执行)、sequence(顺序执行)、stagger和delay来组合使用。它们中的每一个都接受一个要执行的动画数组,并且自动在适当的时候调用start/stop。举个例子:
Animated.sequence([ // 首先执行decay动画,结束后同时执行spring和twirl动画 Animated.decay(position,{ // 滑行一段距离后停止 velocity: {x: gestureState.vx,y: gestureState.vy},// 根据用户的手势设置速度,velocity是起始速度,必填参数。 deceleration: 0.997,// 速度衰减比例,默认为0.997。 }),Animated.parallel([ // 在decay之后并行执行: Animated.spring(position,{ toValue: {x: 0,y: 0} // 返回到起始点开始 }),Animated.timing(twirl,{ // 同时开始旋转 toValue: 360,}),]),]).start(); // 执行这一整套动画序列
合成动画值
Api:
- Animated.add()
- Animated.divide()
- Animated.modulo()
- Animated.multiply()
你可以使用加减乘除以及取余等运算来把两个动画值合成为一个新的动画值。
const a = Animated.Value(1); const b = Animated.divide(1,a); Animated.spring(a,{ toValue: 2,}).start();
插值
interpolate()
Animated API还有一个很强大的部分就是interpolate插值函数。它可以接受一个输入区间,然后将其映射到另一个的输出区间。下面是一个一个简单的从0-1区间到0-100区间的映射示例:
value.interpolate({ inputRange: [0,1],outputRange: [0,100],});
interpolate
还支持定义多个区间段落,常用来定义静止区间等。举个例子,要让输入在接近-300时取相反值,然后在输入接近-100时到达0,然后在输入接近0时又回到1,接着一直到输入到100的过程中逐步回到0,最后形成一个始终为0的静止区间,对于任何大于100的输入都返回0。具体写法如下:
value.interpolate({ inputRange: [-300,-100,100,101],outputRange: [300,1,0],});
它的最终映射结果如下:
interpolate
还支持到字符串的映射,从而可以实现颜色以及带有单位的值的动画变换。例如你可以像下面这样实现一个旋转动画:
value.interpolate({ inputRange: [0,360],outputRange: ['0deg','360deg'] })
interpolation还支持任意的渐变函数,其中有很多已经在Easing类中定义了,包括二次、指数、贝塞尔等曲线以及step、bounce等方法。interpolation还支持限制输出区间outputRange。你可以通过设置extrapolate、extrapolateLeft或extrapolateRight属性来限制输出区间。默认值是extend(允许超出),不过你可以使用clamp选项来阻止输出值超过outputRange。
跟踪动态值
动画中所设的值还可以通过跟踪别的值得到。你只要把toValue设置成另一个动态值而不是一个普通数字就行了。比如我们可以用弹跳动画来实现聊天头像的闪动,又比如通过timing
设置duration:0
来实现快速的跟随。他们还可以使用插值来进行组合:
Animated.spring(follower,{toValue: leader}).start(); Animated.timing(opacity,{ toValue: pan.x.interpolate({ inputRange: [0,300],outputRange: [1,}).start();
ValueXY
是一个方便的处理2D交互的办法,譬如旋转或拖拽。它是一个简单的包含了两个Animated.Value
实例的包装,然后提供了一系列辅助函数,使得ValueXY
在许多时候可以替代Value
来使用。比如在上面的代码片段中,leader
和follower
可以同时为valueXY
类型,这样x和y的值都会被跟踪。
输入事件
Animated.event
是Animated API中与输入有关的部分,允许手势或其它事件直接绑定到动态值上。它通过一个结构化的映射语法来完成,使得复杂事件对象中的值可以被正确的解开。第一层是一个数组,允许同时映射多个值,然后数组的每一个元素是一个嵌套的对象。在下面的例子里,你可以发现scrollX
被映射到了event.nativeEvent.contentOffset.x
(event
通常是回调函数的第一个参数),并且pan.x
和pan.y
分别映射到gestureState.dx
和gestureState.dy
(gestureState
是传递给PanResponder
回调函数的第二个参数)。
onScroll={Animated.event( [{nativeEvent: {contentOffset: {x: scrollX}}}] // scrollX = e.nativeEvent.contentOffset.x )} onPanResponderMove={Animated.event([ null,// 忽略原生事件 {dx: pan.x,dy: pan.y} // 从gestureState中解析出dx和dy的值 ]);
响应当前的动画值
你可能会注意到这里没有一个明显的方法来在动画的过程中读取当前的值——这是出于优化的角度考虑,有些值只有在原生代码运行阶段中才知道。如果你需要在JavaScript中响应当前的值,有两种可能的办法:
spring.stopAnimation(callback)
会停止动画并且把最终的值作为参数传递给回调函数callback
——这在处理手势动画的时候非常有用。spring.addListener(callback)
会在动画的执行过程中持续异步调用callback
回调函数,提供一个最近的值作为参数。这在用于触发状态切换的时候非常有用,譬如当用户拖拽一个东西靠近的时候弹出一个新的气泡选项。不过这个状态切换可能并不会十分灵敏,因为它不像许多连续手势操作(如旋转)那样在60fps下运行。
Animated
is designed to be fully serializable so that animations can be run in a high performance way,independent of the normal JavaScript event loop. This does influence the API,so keep that in mind when it seems a little trickier to do something compared to a fully synchronous system. Check outAnimated.Value.addListener
as a way to work around some of these limitations,but use it sparingly since it might have performance implications in the future.
使用原生动画驱动
Animated
的API是可序列化的(即可转化为字符串表达以便通信或存储)。
通过启用原生驱动,我们在启动动画前就把其所有配置信息都发送到原生端,利用原生代码在UI线程执行动画,而不用每一帧都在两端间来回沟通。 如此一来,动画一开始就完全脱离了JS线程,因此此时即便JS线程被卡住,也不会影响到动画了。
在动画中启用原生驱动非常简单。 只需在开始动画之前,在动画配置中加入一行useNativeDriver: true
,如下所示:
<Animated.ScrollView // <-- 使用Animated ScrollView wrapper scrollEventThrottle={1} // <-- 设为1以确保滚动事件的触发频率足够密集 onScroll={Animated.event( [{ nativeEvent: { contentOffset: { y: this.state.animatedValue } } }],{ useNativeDriver: true } // <-- 加上这一行 )} > {content} </Animated.ScrollView>
循环执行动画
start
方法可以接受一个函数,通过监听动画结束,再调用动画方法可以重复执行动画,例如:
startAnimation() { this.state.translateValue.setValue({x:0,y:0});//因为要重复执行动画,所有每次都要设置初始值,这里设置初始值要用setState,直接设置它的值。这个会停止任何正在进行的动画,然后更新所有绑定的属性。 Animated.decay( // 以一个初始速度开始并且逐渐减慢停止。 S=vt-(at^2)/2 v=v - at this.state.translateValue,{ velocity: 10,// 起始速度,必填参数。 deceleration: 0.8,// 速度衰减比例,默认为0.997。 } ).start(() => this.startAnimation()); }
监听当前的动画值:
- addListener(callback):动画执行过程中的值
- stopAnimation(callback):动画执行结束时的值
监听AnimatedValueXY类型translateValue
的值变化:
this.state.translateValue.addListener((value) => { console.log("translateValue=>x:" + value.x + " y:" + value.y); }); this.state.translateValue.stopAnimation((value) => { console.log("translateValue=>x:" + value.x + " y:" + value.y); });
监听AnimatedValue类型rotateValue
的值变化:
this.state.rotateValue.addListener((state) => { console.log("rotateValue=>" + state.value); }); this.state.rotateValue.stopAnimation((state) => { console.log("rotateValue=>" + state.value); })@H_404_323@
其他要注意的地方
requestAnimationFrame
requestAnimationFrame
是一个对浏览器标准API的兼容实现,你可能已经熟悉它了。它接受一个函数作为唯一的参数,并且在下一次重绘之前调用此函数。一些基于JavaScript的动画库高度依赖于这一API。通常你不必直接调用它——那些动画库会替你管理好帧的更新。
setNativeProps
正如直接操作文档所说,setNativeProps
方法可以使我们直接修改基于原生视图的组件的属性,而不需要使用setState
来重新渲染整个组件树。如果我们要更新的组件有一个非常深的内嵌结构,并且没有使用shouldComponentUpdate
来优化,那么使用setNativeProps
就将大有裨益。
如果你发现你的动画丢帧(低于60帧每秒),可以尝试使用setNativeProps
或者shouldComponentUpdate
来优化它们。Or you could run the animations on the UI thread rather than the JavaScript threadwith the useNativeDriver option。你还可以考虑将部分计算工作放在动画完成之后进行,这时可以使用InteractionManager。你还可以使用应用内的开发者菜单中的“FPS Monitor”工具来监控应用的帧率。
spring实现的动画例子
import loadingImage from '../../assets/0.gif' ... class PlaygroundContainer extends Component { constructor(props) { super(props); this.state = { left1: new Animated.Value(0),} } componentDidMount() { Animated.spring(this.state.left1,{ toValue: 100,//属性目标值 friction: 1,//摩擦力 (越小 振幅越大) tension: 100,//拉力 }).start(); //执行动画 } render(){ return ( ... <Animated.Image style={[styles.image,{left: this.state.left1}]} source={ loadingImage }/> ... ) } }
用timing实现的翻转动画效果例子
import loadingImage from '../../assets/0.gif' ... class PlaygroundContainer extends Component { constructor(props) { super(props); this.state = { rotation2: new Animated.Value(0),} } componentDidMount() { Animated.timing(this.state.rotation2,{ toValue: 1,//属性目标值 duration: 3000 //动画执行时间 }).start(); //执行动画 } render(){ return ( ... <Animated.Image style={[styles.image,{ transform:[ { rotateX: this.state.rotation2.interpolate({ inputRange:[0,outputRange:['0deg','360deg'] }) } ] }]} source={ loadingImage }/> ... ) } }
用decay实现衰减动画例子
import loadingImage from '../../assets/0.gif' ... class PlaygroundContainer extends Component { constructor(props) { super(props); this.state = { decayLeft4: new Animated.Value(0),} } componentDidMount() { Animated.decay(this.state.decayLeft4,{ velocity: 2,// 起始速度,必填参数。 deceleration:0.992 //速度衰减比例,默认为0.997。 }).start(); } render(){ return ( ... <Animated.Image style={[styles.image,{ left: this.state.decayLeft4 }]} source={ loadingImage }/> ... ) } }
实现组合动画例子
import loadingImage from '../../assets/0.gif' ... class PlaygroundContainer extends Component { constructor(props) { super(props); this.state = { left3: new Animated.Value(0),rotation3: new Animated.Value(0),scale3: new Animated.Value(0.5),} } componentDidMount() { //串行执行 Animated.sequence([ // 并行执行(滚动,同时旋转) Animated.parallel([ Animated.timing(this.state.left3,{ toValue: 1,duration: 3000,Animated.timing(this.state.rotation3,duration: 1000,// 滚动、旋转结束 执行缩放 Animated.timing(this.state.scale3,{ toValue: 1,duration: 500,}) ]).start() //执行动画 } render(){ return ( ... <Animated.Image style={[styles.image,{ left: this.state.left3.interpolate({ inputRange:[0,outputRange:[0,width - 100] }),transform:[ {rotateZ: this.state.rotation3.interpolate({ inputRange:[0,'360deg']}) },{rotateX: this.state.rotation3.interpolate({ inputRange:[0,{scale: this.state.scale3} ] }]} source={ loadingImage }/> ... ) } }
用delay做延时动画例子
Animated.delay(1000)
延时1000ms
把delay
动画放进sequence
执行串行的动画数组中
import loadingImage from '../../assets/0.gif' ... class PlaygroundContainer extends Component { constructor(props) { super(props); this.state = { left5: new Animated.Value(0),} } componentDidMount() { Animated.sequence([ // 1000 ms后执行 Animated.delay(1000),Animated.timing(this.state.left5,{ toValue: 100,// 起始速度,必填参数。 duration: 1000 }) ]).start() } render(){ return ( ... <Animated.Image style={[styles.image,{ left: this.state.left5 }]} source={ loadingImage }/> ... ) } }原文链接:https://www.f2er.com/react/302269.html