如何使用
import React, {Component} from 'react';
import {
StyleSheet,
View,
Text,
Animated,
Easing
} from 'react-native';
export default class AnimatedDocument extends Component{
constructor(props){
super(props);
this.state = {
fadeInOpacity: new Animated.Value(0)
};
}
componentDidMount() {
Animated.timing(this.state.fadeInOpacity, {
toValue: 1, // 目标值
duration: 2500, // 动画时间
easing: Easing.linear // 直线
}).start();
}
render(){
return(
<Animated.View style={[styles.demo, {
opacity: this.state.fadeInOpacity
}]}>
<Text style={styles.text}>悄悄的,我出现了</Text>
</Animated.View>
);
}
}
const styles = StyleSheet.create({
demo: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'white'
},
text: {
fontSize: 30
}
});
效果演示
步骤拆解
一个RN的动画可以按照如下的步骤进行:
- 使用基本的Animated组件,如Animated.View、Animated.Image、Animated.Text(目前RN只支持这三个组件,官网也提供了自定义Animated组件的方法,不过不推荐)
- 使用Animated.Value设定一个或多个初始值(透明度、位置等)
- 将初始化值绑定到动画目标的属性上(如Style)
- 通过Animatd.timing等函数设定动画参数
- 调用start启动动画
更复杂的例子
constructor(props){
super(props);
this.state = {
fadeInOpacity: new Animated.Value(0),
rotation: new Animated.Value(0),
fontSize: new Animated.Value(0)
};
}
componentDidMount() {
Animated.parallel(['fadeInOpacity', 'rotation', 'fontSize'].map(index => {
return Animated.timing(this.state[index], {
toValue: 1,
duration: 1000,
easing: Easing.linear
})
})).start();
}
render(){
return(
<Animated.View style={[styles.demo, {
opacity: this.state.fadeInOpacity,
transform: [{
rotateZ: this.state.rotation.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg']
})
}]
}]}>
<Animated.Text style={{
fontSize: this.state.fontSize.interpolate({
inputRange: [0, 1],
outputRange: [12, 26]
})
}}>我骑着七彩祥云出现了~~</Animated.Text>
</Animated.View>
);
}
注意到我们使用了Animated的一个新方法,parallel,它表示同时执行一组动画
强大的interpolate
上面的例子使用了interpolate函数,也就是插值函数。这个函数很强大,实现了数值大小、单位的映射转换,比如:
{
inputRange: [0, 1],
outputRange: [‘0deg’, '180deg']
}
当setValue(0.5)时,会自动映射成90deg。inputRange并不局限于[0, 1]区间,可以画出多段。interpolate一般用于多个动画公用一个Animated.Value,只需要在每个属性里面映射好对应的值,就可以用一个变量控制多个动画。事实上,上例中的Animated.Value可以用一个变量来声明,这里只是为了演示parallel的用法
流程控制
在刚才的例子中,我们使用了Parallel来实现多个动画并行渲染,其它用于流程控制的API还有:
- sequence接受一系列动画数组为参数,并依次执行
- stagger接受一系列动画数组和一个延迟时间,按照序列,每隔一个延迟时间后执行下一个动画(其实就是插入了delay的parrllel)
- delay生成一个延时时间(基于timing的delay参数生成)
constructor(props){
super(props);
this.state = {
anim: [1, 2, 3].map(() => new Animated.Value(0)) // 初始化3个值
};
}
componentDidMount() {
let timing = Animated.timing;
Animated.sequence([
Animated.stagger(200, this.state.anim.map(left => {
return timing(left, {
toValue: 1,
});
}).concat(
this.state.anim.map(left => {
return timing(left, {
toValue: 0,
});
})
)), // 三个view滚到右边再还原,每个动作间隔200ms
Animated.delay(400), // 延迟400ms,配合sequence使用
timing(this.state.anim[0], {
toValue: 1,
}),
timing(this.state.anim[1], {
toValue: -1,
}),
timing(this.state.anim[2], {
toValue: 0.5
}),
Animated.delay(400),
Animated.parallel(this.state.anim.map((anim) => {
timing(anim, {
toValue: 0,
})
})) // 同时回到原位
]).start();
}
render(){
let views = this.state.anim.map((value, i) => {
return (
<Animated.View key={i}
style={[styles.demo, styles['demo' + 1], {
left: value.interpolate({
inputRange: [0, 1],
outputRange: [0, 200]
})
}]}>
<Text style={styles.text}>我是第{i + 1}个View</Text>
</Animated.View>
);
});
return(
<View style={styles.container}>
<Text>sequence/delay/stagger/paraller演示</Text>
{views}
</View>
);
}
Spring/Decay/Timing
前面的几个动画都是基于时间实现的,事实上,在日常的手势操作中,基于时间的动画往往难以满足复杂的交互动画。对此,RN太提供了另外两种动画模式
- Spring 弹簧效果
- friction 摩擦系数,默认40
- tension 张力系数,默认7
- bounciness
- speed
- Decay 衰变效果
- velocity 初速率
- deceleration 衰减系数 默认0.997
Spring支持friction与tension或者bounciness与speed两种组合模式,这两种模式不能并存。
Track && Event
RN动画支持跟踪功能,这也是日常交互中很常见的需求,比如跟踪用户的手势变化,跟踪另一个动画。而跟踪的用法也很简单,只需要指定toValue到另一个Animated.Value就可以了。交互动画需要跟踪用户的手势操作,Animated也很贴心地提供了事件借口的封装,如下:
// Animated.event 封装手势事件等值映射到对应的ANimated.Value
onPanResponderMove: Animated.event(
[null, {dx: this.state.x, dy: this.state.y}]
)