在上篇动画入门文章中我们了解了在 React Native 中简单的动画的实现方式,本篇将作为上篇的延续,介绍如何使用 Animated 实现一些比较复杂的动画。

动画组合

在 Animated 中提供了一些有趣的API方法来轻松地按我们的需求实现组合动画,它们分别是 Animated.parallel、Animated.sequence、Animated.stagger、Animated.delay。

我们会分别介绍这些方法,并从中学习到一些其它有用的API。最后我们会得到一个有趣的DOGE动画 

1. Animated.parallel

并行执行一系列指定动画的方法,其格式如下:

Animated.parallel(Animates<Array>, [conf<Object>])

第一个参数接受一个元素为动画的数组,通过执行 start() 方法可以并行执行该数组中的所有方法。

如果数组中任意动画被中断的话,该数组内对应的全部动画会一起停止,不过我们可以通过第二个(可选)参数 conf 来取消这种牵连特性:

{stopTogether: false}

我们先看一个简单的、没有使用 Animated.parallel 的例子:

class AwesomeProject extends Component {
constructor(props) {
super(props);
this.state = {
grassTransY : new Animated.Value(Dimensions.get('window').height/2),
bigDogeTrans : new Animated.ValueXY({
x: 100,
y: 298
})
}
} componentDidMount() {
Animated.timing(this.state.grassTransY, {
toValue: 200,
duration: 1000,
easing: Easing.bezier(0.15, 0.73, 0.37, 1.2)
}).start(); Animated.timing(this.state.bigDogeTrans, {
toValue: {
x : Dimensions.get('window').width/2 - 139,
y : -200
},
duration: 2000,
delay: 1000
}).start();
} render() {
return (
<View style={styles.container}>
<Animated.View style={[styles.doges, {transform: this.state.bigDogeTrans.getTranslateTransform()}]} >
<Image source={require('./src/img/bdoge.png')}/>
</Animated.View> <Animated.View style={[styles.grass, {transform: [{translateY: this.state.grassTransY}]}]}></Animated.View> </View> );
}
} var styles = StyleSheet.create({
grass: {
position: 'absolute',
width: Dimensions.get('window').width,
backgroundColor: '#A3D900',
height: 240
},
doges: {
position: 'absolute'
},
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#73B9FF'
}
});

执行如下:

更改为 Animated.parallel 形式为(只需要修改 componentDidMount 代码块):

    componentDidMount() {
var timing = Animated.timing;
Animated.parallel([
timing(this.state.grassTransY, {
toValue: 200,
duration: 1000,
easing: Easing.bezier(0.15, 0.73, 0.37, 1.2)
}),
timing(this.state.bigDogeTrans, {
toValue: {
x : Dimensions.get('window').width/2 - 139,
y : -200
},
duration: 2000,
delay: 1000
})
]).start();
}

执行后效果是一致的。

不过对于上方的代码,这里提一下俩处API:

        new Animated.ValueXY({
x: 100,
y: 298
})

以及我们给 doge 设置的样式:

{transform: this.state.bigDogeTrans.getTranslateTransform()}

它们是一个语法糖,Animated.ValueXY 方法会生成一个 x 和 y 的映射对象,方便后续使用相关方法将该对象转换为需要的样式对象。

例如这里我们通过 .getTranslateTransform() 方法将该 ValueXY 对象转换为 translate 的样式值应用到组件上,即其初始样式等价于

{transform: [{
translateX: 100
},
{
translateY: 298
}]}

注意在 Animated.timing 中设置动画终值时需要以

{ x: XXX,
y: YYY
}

的形式来做修改:

            timing(this.state.bigDogeTrans, {
toValue: { //注意这里
x : Dimensions.get('window').width/2 - 139,
y : -200
},
duration: 2000,
delay: 1000
})

另外,除了能将 ValueXY 对象转为 translateX/Y 的 getTranslateTransform() 方法,我们还能通过 getLayout() 方法来将 ValueXY 对象转为 {left, top} 形式的样式对象:

 style={{
transform: this.state.anim.getTranslateTransform()
}}

不过这里就不举例了。

我们回到 Animated.parallel 方法的话题来。我们对开头的代码做小小的改动来学习一个新的API—— interpolate 插值函数:

class AwesomeProject extends Component {
constructor(props) {
super(props);
this.state = {
//注意这里初始化value都为0
grassTransY : new Animated.Value(0),
bigDogeTransY : new Animated.Value(0)
}
} componentDidMount() {
var timing = Animated.timing;
Animated.parallel(['grassTransY', 'bigDogeTransY'].map((prop, i) => {
var _conf = {
toValue: 1, //注意这里设置最终value都为1
duration: 1000 + i * 1000
};
i || (_conf.easing = Easing.bezier(0.15, 0.73, 0.37, 1.2)); return timing(this.state[prop], _conf)
})).start();
} render() {
return (
<View style={styles.container}>
<Animated.View style={[styles.doges, {transform: [{
translateX: Dimensions.get('window').width/2 - 139
},
{
translateY: this.state.bigDogeTransY.interpolate({
inputRange: [0, 1], //动画value输入范围
outputRange: [298, -200] //对应的输出范围
})
}]}]}>
<Image source={require('./src/img/bdoge.png')}/>
</Animated.View> <Animated.View style={[styles.grass, {transform: [{
translateY: this.state.grassTransY.interpolate({
inputRange: [0, 1],
outputRange: [Dimensions.get('window').height/2, 200]
})
}]}]}></Animated.View> </View>
);
}
}

注意我们这里统一把动画属性初始值都设为0:

        this.state = {
//注意这里初始化value都为0
grassTransY : new Animated.Value(0),
bigDogeTransY : new Animated.Value(0)
}

然后又把动画属性的终值设为1:

            var _conf = {
toValue: 1, //注意这里设置最终value都为1
duration: 1000
}; return timing(this.state[prop], _conf)

然后通过 interpolate 插值函数将 value 映射为正确的值:

                    translateY: this.state.bigDogeTransY.interpolate({
inputRange: [0, 1], //动画value输入范围
outputRange: [298, -200] //对应的输出范围
})

这意味着当 value 的值为0时,interpolate 会将其转为 298 传给组件;当 value 的值为1时则转为 -200。

因此当value的值从0变化到1时,interpolate 会将其转为 (298 - 498 * value) 的值。

事实上 inputRange 和 outputRange 的取值非常灵活,我们看官网的例子:

value.interpolate({
inputRange: [-300, -100, 0, 100, 101],
outputRange: [300, 0, 1, 0, 0],
});

其映射为:

Input       Output
-400 450
-300 300
-200 150
-100 0
-50 0.5
0 1
50 0.5
100 0
101 0
200 0

2. Animated.sequence

Animated的动画是异步执行的,如果希望它们能以队列的形式一个个逐步执行,那么 Animated.sequence 会是一个最好的实现。其语法如下:

Animated.sequence(Animates<Array>)

事实上了解了开头的 parallel 方法,后面几个方法都是一样套路了。

来个例子,我们依旧直接修改上方代码即可:

    componentDidMount() {
var timing = Animated.timing;
Animated.sequence(['grassTransY', 'bigDogeTransY'].map((prop, i) => {
var _conf = {
toValue: 1
};
if(i==0){
_conf.easing = Easing.bezier(0.15, 0.73, 0.37, 1.2)
} return timing(this.state[prop], _conf)
})).start();
}

这样 doge 的动画会在 草地出来的动画结束后才开始执行:

3. Animated.stagger

该方法为 sequence 的变异版,支持传入一个时间参数来设置队列动画间的延迟,即让前一个动画结束后,隔一段指定时间才开始执行下一个动画。其语法如下:

Animated.stagger(delayTime<Number>, Animates<Array>)

其中 delayTime 为指定的延迟时间(毫秒),我们继续拿前面的代码来开刀(为了给力点我们再加入一个running doge的动画事件)

class AwesomeProject extends Component {
constructor(props) {
super(props);
this.state = {
grassTransY : new Animated.Value(0),
bigDogeTransY : new Animated.Value(0),
runningDogeTrans : new Animated.ValueXY({
x: Dimensions.get('window').width,
y: Dimensions.get('window').height/2 - 120
})
}
} componentDidMount() {
var timing = Animated.timing;
Animated.stagger(1500, ['grassTransY', 'bigDogeTransY', 'runningDogeTrans'].map((prop, i) => {
var _conf = {
toValue: 1
};
if(i==0){
_conf.easing = Easing.bezier(0.15, 0.73, 0.37, 1.2)
} if(i==2){ //running doge
_conf.toValue = {
x: Dimensions.get('window').width - 150,
y: Dimensions.get('window').height/2 - 120
}
} return timing(this.state[prop], _conf)
})).start();
} render() {
return (
<View style={styles.container}>
<Animated.View style={[styles.doges, {transform: [{
translateX: Dimensions.get('window').width/2 - 139
},
{
translateY: this.state.bigDogeTransY.interpolate({
inputRange: [0, 1],
outputRange: [298, -200]
})
}]}]}>
<Image source={require('./src/img/bdoge.png')}/>
</Animated.View> <Animated.View style={[styles.grass, {transform: [{
translateY: this.state.grassTransY.interpolate({
inputRange: [0, 1],
outputRange: [Dimensions.get('window').height/2, 200]
})
}]}]}></Animated.View> <Animated.View style={[styles.doges, {
transform: this.state.runningDogeTrans.getTranslateTransform()
}]}>
<Image source={require('./src/img/sdoge.gif')}/>
</Animated.View> </View>
);
}
}

我们把三个动画间隔时间设定为 2000 毫秒,执行效果如下:

4. Animated.delay

噢这个接口实在太简单了,就是设置一段动画的延迟时间,接收一个时间参数(毫秒)作为指定延迟时长:

Animated.delay(delayTime<Number>)

常规还是跟好基友 Animated.sequence 一同使用,我们继续修改前面的代码:

class AwesomeProject extends Component {
constructor(props) {
super(props);
this.state = {
grassTransY : new Animated.Value(0),
bigDogeTransY : new Animated.Value(0),
runningDogeTrans : new Animated.ValueXY({
x: Dimensions.get('window').width,
y: Dimensions.get('window').height/2 - 120
})
}
} componentDidMount() {
var timing = Animated.timing;
Animated.sequence([
Animated.delay(1000), //延迟1秒再开始执行动画 timing(this.state.grassTransY, {
toValue: 1,
duration: 1000,
easing: Easing.bezier(0.15, 0.73, 0.37, 1.2)
}), timing(this.state.bigDogeTransY, {
toValue: 1,
duration: 3000
}), Animated.delay(2000), //延迟2秒再执行running doge动画 timing(this.state.runningDogeTrans, {
toValue: {
x: Dimensions.get('window').width - 150,
y: Dimensions.get('window').height/2 - 120
},
duration: 2000
}), Animated.delay(1000), //1秒后跑到中间 timing(this.state.runningDogeTrans, {
toValue: {
x: Dimensions.get('window').width/2 - 59,
y: Dimensions.get('window').height/2 - 180
},
duration: 1000
})
] ).start();
} render() {
return (
<View style={styles.container}>
<Animated.View style={[styles.doges, {transform: [{
translateX: Dimensions.get('window').width/2 - 139
},
{
translateY: this.state.bigDogeTransY.interpolate({
inputRange: [0, 1], //动画value输入范围
outputRange: [298, -200] //对应的输出范围
})
}]}]}>
<Image source={require('./src/img/bdoge.png')}/>
</Animated.View> <Animated.View style={[styles.grass, {transform: [{
translateY: this.state.grassTransY.interpolate({
inputRange: [0, 1],
outputRange: [Dimensions.get('window').height/2, 200]
})
}]}]}></Animated.View> <Animated.View style={[styles.doges, {
transform: this.state.runningDogeTrans.getTranslateTransform()
}]}>
<Image source={require('./src/img/sdoge.gif')}/>
</Animated.View> </View>
);
}
}

执行如下:

到这里我们基本就掌握了 RN 动画的常用API了,对于本章的 Doge 动画我们再搞复杂一点——再加一只running doge,然后在动画结束后,让最大的Doge头一直不断地循环旋转:

class AwesomeProject extends Component {
constructor(props) {
super(props);
this.state = {
grassTransY : new Animated.Value(0),
bigDogeTransY : new Animated.Value(0),
bigDogeRotate : new Animated.Value(0),
runningDogeTrans : new Animated.ValueXY({
x: Dimensions.get('window').width,
y: Dimensions.get('window').height/2 - 120
}),
runningDoge2Trans : new Animated.ValueXY({
x: Dimensions.get('window').width,
y: Dimensions.get('window').height/2 - 90
})
}
} componentDidMount() {
var timing = Animated.timing;
Animated.sequence([
Animated.delay(1000), //延迟1秒再开始执行动画 timing(this.state.grassTransY, {
toValue: 1,
duration: 1000,
easing: Easing.bezier(0.15, 0.73, 0.37, 1.2)
}), timing(this.state.bigDogeTransY, {
toValue: 1,
duration: 3000
}), Animated.parallel([
Animated.sequence([
timing(this.state.runningDogeTrans, {
toValue: {
x: Dimensions.get('window').width - 150,
y: Dimensions.get('window').height/2 - 120
},
duration: 2000
}), Animated.delay(1000), //1秒后跑到中间 timing(this.state.runningDogeTrans, {
toValue: {
x: Dimensions.get('window').width/2 - 99,
y: Dimensions.get('window').height/2 - 180
},
duration: 1000
})
]), Animated.sequence([
timing(this.state.runningDoge2Trans, {
toValue: {
x: Dimensions.get('window').width/2 + 90,
y: Dimensions.get('window').height/2 - 90
},
duration: 2000
}), Animated.delay(1000), timing(this.state.runningDoge2Trans, {
toValue: {
x: Dimensions.get('window').width/2 + 20,
y: Dimensions.get('window').height/2 - 110
},
duration: 1000
})
]) ])
] ).start(()=>{
this.bigDogeRotate()
});
} //大doge一直不断循环
bigDogeRotate(){
this.state.bigDogeRotate.setValue(0); //重置Rotate动画值为0
Animated.timing(this.state.bigDogeRotate, {
toValue: 1,
duration: 5000
}).start(() => this.bigDogeRotate())
} render() {
return (
<View style={styles.container}>
<Animated.View style={[styles.doges, {transform: [{
translateX: Dimensions.get('window').width/2 - 139
},
{
translateY: this.state.bigDogeTransY.interpolate({
inputRange: [0, 1], //动画value输入范围
outputRange: [298, -200] //对应的输出范围
})
},
{
rotateZ: this.state.bigDogeRotate.interpolate({
inputRange: [0, 1], //动画value输入范围
outputRange: ['0deg', '360deg'] //对应的输出范围
})
}]}]}>
<Image source={require('./src/img/bdoge.png')}/>
</Animated.View> <Animated.View style={[styles.grass, {transform: [{
translateY: this.state.grassTransY.interpolate({
inputRange: [0, 1],
outputRange: [Dimensions.get('window').height/2, 200]
})
}]}]}></Animated.View> <Animated.View style={[styles.doges, {
transform: this.state.runningDogeTrans.getTranslateTransform()
}]}>
<Image source={require('./src/img/sdoge.gif')}/>
</Animated.View> <Animated.View style={[styles.doges, {
transform: this.state.runningDoge2Trans.getTranslateTransform()
}]}>
<Image source={require('./src/img/sdoge.gif')}/>
</Animated.View> </View>
);
}
}

最终效果如下:

写完这篇文章都凌晨了,我也是蛮拼的

共勉~

ReactNative入门 —— 动画篇(下)的更多相关文章

  1. ReactNative入门 —— 动画篇(上)

    在不使用任何RN动画相关API的时候,我们会想到一种非常粗暴的方式来实现我们希望的动画效果——通过修改state来不断得改变视图上的样式. 我们来个简单的示例: var AwesomeProject ...

  2. ReactNative入门(安卓)——API(上)

    Alert - 弹窗 通过 Alert.alert() 方法调用唤起原生弹窗,点击会触发 onPress 回调(参考下方代码)并清除弹窗. import React, { AppRegistry, C ...

  3. React-Native入门指导之iOS篇 —— 一、准备工作

    React-Native 入门指导系列教程目录 一.准备工作 (已完成) 二.项目介绍与调试 三.CSS样式与Flex布局 四.常用UI控件的使用 五.JSX在React-Native中的应用 六.事 ...

  4. [转]Apache Maven 入门篇(下)

    原文地址: Apache Maven 入门篇(下) 作者:George Ma 第一篇文章大概的介绍了一下Apache Maven以及它的下载和安装,并且运行了一个简单的示例.那么在对maven有了一点 ...

  5. React-Native入门指导之iOS篇

    React-Native 入门指导系列教程目录 一.准备工作 (已完成) 二.项目介绍与调试 三.CSS样式与Flex布局 四.常用UI控件的使用 五.JSX在React-Native中的应用 六.事 ...

  6. ElasticSearch入门 第一篇:Windows下安装ElasticSearch

    这是ElasticSearch 2.4 版本系列的第一篇: ElasticSearch入门 第一篇:Windows下安装ElasticSearch ElasticSearch入门 第二篇:集群配置 E ...

  7. jQuery基础(动画篇 animate,显示隐藏,淡入淡出,下拉切换)

    1.jQuery中隐藏元素的hide方法   让页面上的元素不可见,一般可以通过设置css的display为none属性.但是通过css直接修改是静态的布局,如果在代码执行的时候,一般是通过js控制元 ...

  8. iOS React-Native入门指南之HelloWorld

    React-native 作为facebook开源项目,最近是火的一塌糊涂,它采用node.js能够写ios和android的native界面代码,简直是太酷了.支持动态更新,而且appstore 提 ...

  9. [转] ReactNative Animated动画详解

    http://web.jobbole.com/84962/     首页 所有文章 JavaScript HTML5 CSS 基础技术 前端职场 工具资源 更多频道▼ - 导航条 - 首页 所有文章 ...

随机推荐

  1. js复杂对象和简单对象的简单转化

    var course = { teacher :{ teacherId:001, teacherName:"王" }, course : { courseId : 120, cou ...

  2. 80 端口被占用 pid=4

    80端口被pid=4的系统进程给占用的解决方法: 一般开发的时候我们都会安装sqlserver ,也会把Sql server Reporting Services 安装上去.原因就是这个服务占用了80 ...

  3. Webpack 配置摘要

    open-browser-webpack-plugin 自动打开浏览器 html-webpack-plugin 通过 JS 生成 HTML webpack.optimize.UglifyJsPlugi ...

  4. 不懂CSS的后端难道就不是好程序猿?

    由于H5在移动端的发展如日中天,现在大部分公司对高级前端需求也是到处挖墙角,前端薪资也随之水涨船高,那公司没有配备专用的前端怎么办呢? 作为老板眼中的“程序猿” 前端都不会是非常无能的表现,那作为后端 ...

  5. Android—ListView条目背景为图片时,条目间距问题解决

    ListView是android开发中使用最普遍的控件了,可有的listView条目的内容颇为丰富,甚至为了美观,背景用指定图片,如下图:

  6. vim环境变量配置、背景色配置

    我们使用vi或者vim的时候,如果想要显示行号,可能会这样做:切换到命令模式,然后输入set nu,再按回车键就显示了:还有就是咱们在编写程序的时候,有的时候会希望按下回车键后,光标不是每次都在行首, ...

  7. 鱼眼模式(Fisheye projection)的软件实现

    简单实现 鱼眼模式(Fisheye)和普通的透视投影(Perspective projection),一个很大的区别就是鱼眼的投影算法是非线性的(non-linear),实际照相机的情况是在镜头外面包 ...

  8. Apache2.4:AH01630 client denied by server configuration

    问题说明:Apache服务总共有4个,是为了防止单点故障和负载均衡,负载均衡控制由局方的F5提供. 访问的内容在NAS存储上,现象是直接访问每个apache的服务内容都是没有问题,但是从负载地址过来的 ...

  9. 我的MYSQL学习心得(十二) 触发器

    我的MYSQL学习心得(十二) 触发器 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数 ...

  10. 一步步开发自己的博客 .NET版(3、注册登录功能)

    前言 这次开发的博客主要功能或特点:    第一:可以兼容各终端,特别是手机端.    第二:到时会用到大量html5,炫啊.    第三:导入博客园的精华文章,并做分类.(不要封我)    第四:做 ...