ReactNative学习实践--动画初探之加载动画
学习和实践react已经有一段时间了,在经历了从最初的彷徨到解决痛点时的兴奋,再到不断实践后遭遇问题时的苦闷,确实被这一种新的思维方式和开发模式所折服,react不是万能的,在很多场景下滥用反而会适得其反,这里不展开讨论。
有了react的实践经验,结合之前自己的一点ios开发经验,决定继续冒险,开始react-native学习和实践,目前主要是从常规的native功能入手,逐步用react-native实现,基础知识如开发环境搭建、调试工具等官方文档有很清楚的指引,不再赘述,这里主要是想把实际学习实践中遇到的坑或者有意思的经历记录下来,为广大react-native初学者提供一点参考。O(∩_∩)O~
话不多说,进入正题,今天要实现的是一个加载动画,效果如下:
很简单一个动画,不是么?用native实现实在是小菜一碟,现在我们试着用RN来实现它!
先将动画的视图结构搭建出来,这个比较简单,就是4个会变形的View顺序排列:
<View style={styles.square}>
<Animated.View style={[styles.line,{height:this.state.fV}]}></Animated.View>
<Animated.View style={[styles.line,{height:this.state.sV}]}></Animated.View>
<Animated.View style={[styles.line,{height:this.state.tV}]}></Animated.View>
<Animated.View style={[styles.line,{height:this.state.foV}]}></Animated.View>
</View>
这里的视图结构很普通,只不过在RN中,需要施加动画的视图,都不能是普通的View,而是Animated.View,包括施加动画的图片,也应该是Animated.Image,需要注意。
RN继承了react的核心思想,基于虚拟DOM和数据驱动的模式,用state来管理视图层,所以RN的动画和react的动画类似,都是通过改变state从而执行render进行视图重绘,展现动画。
毫无疑问,先从Animated库下手,这是facebook官方提供的专门用于实现动画的库,它比较强大,集成了多种常见的动画形式,正如官方文档写道:
Animated focuses on declarative relationships between inputs and outputs, with configurable transforms in between, and simple start/stop methods to control time-based animation execution.
它专注于输入和输出之间的对应关系,其间是可以配置的各种变形,通过简单的开始和停止方法来控制基于时间的动画。
所以使用这个库的时候,需要清楚知道动画的输入值,不过这并不代表需要知道每一个时刻动画的精确属性值,因为这是一种插值动画,Animated只需要知道初始值和结束值,它会将所有中间值动态计算出来运用到动画中,这有点类似于CSS3中的关键帧动画。它提供了spring、decay、timing三种动画方式,其实这也就是三种不同的差值方式,指定相同的初始值和结束值,它们会以不同的函数计算中间值并运用到动画中,最终输出的就是三种不同的动画,比如官方给出的示例:
class Playground extends React.Component {
constructor(props: any) {
super(props);
this.state = {
bounceValue: new Animated.Value(0),//这里设定了动画的输入初始值,注意不是数字0
};
}
render(): ReactElement {
return (
<Animated.Image //这里不是普通Image组件
source={{uri: 'http://i.imgur.com/XMKOH81.jpg'}}
style={{
flex: 1,
transform: [ //添加变换,transform的值是数组,包含一系列施加到对象上的变换
{scale: this.state.bounceValue}, // 变换是缩放,缩放值state里的bounceValue,这个值是一个动态值,也是动画的根源
]
}}
/>
);
}
componentDidMount() {
this.state.bounceValue.setValue(1.5); // 组件加载的时候设定bounceValue,因此图片会被放大1.5倍
Animated.spring( //这里运用的spring方法,它的差值方式不是线性的,会呈现弹性的效果
this.state.bounceValue, //spring方法的第一个参数,表示被动态插值的变量
{
toValue: 0.8, //这里就是输入值的结束值
friction: 1, //这里是spring方法接受的特定参数,表示弹性系数
}
).start();// 开始spring动画
}
}
可以想象该动画效果大致为:图片首先被放大1.5倍呈现出来,然后以弹性方式缩小到0.8倍。这里的start方法还可以接收一个参数,参数是一个回调函数,在动画正常执行完毕之后,会调用这个回调函数。
Animated库不仅有spring/decay/timing三个方法提供三种动画,还有sequence/decay/parallel等方法来控制动画队列的执行方式,比如多个动画顺序执行或者同时进行等。
介绍完了基础知识,我们开始探索这个实际动画的开发,这个动画需要动态插值的属性其实很简单,只有四个视图的高度值,其次,也不需要特殊的弹性或者缓动效果。所以我们只需要将每个视图的高度依次变化,就可以了,so easy!
开始尝试:
Animated.timing(
this.state.fV,
{
toValue: 100,
duration:500,
delay:500,
}
).start();
Animated.timing(
this.state.sV,
{
toValue: 100,
duration:1000,
delay:1000,
}
).start();
Animated.timing(
this.state.tV,
{
toValue: 100,
duration:1000,
delay:1500,
}
).start();
Animated.timing(
this.state.foV,
{
toValue: 100,
duration:1000,
delay:2000,
}
).start();
WTF!
虽然动画动起来了,但是这根本就是四根火柴在做广播体操。。。
并且一个更严重的问题是,动画运行完,就停止了。。。,而loading动画应该是循环的,在查阅了文档及Animated源码之后,没有找到类似loop这种控制循环的属性,无奈之下,只能另辟蹊径了。
上文提到过,Animated动画的start方法可以在动画完成之后执行回调函数,如果动画执行完毕之后再执行自己,就实现了循环,因此,将动画封装成函数,然后循环调用本身就可以了,不过目前动画还只把高度变矮了,没有重新变高的部分,因此即使循环也不会有效果,动画部分也需要修正:
...//其他部分代码
loopAnimation(){
Animated.parallel([//最外层是一个并行动画,四个视图的动画以不同延迟并行运行
Animated.sequence([//这里是一个顺序动画,针对每个视图有两个动画:缩小和还原,他们依次进行
Animated.timing(//这里是缩小动画
this.state.fV,
{
toValue: Utils.getRealSize(100),
duration:500,
delay:0,
}
),
Animated.timing(//这里是还原动画
this.state.fV,
{
toValue: Utils.getRealSize(200),
duration:500,
delay:500,//注意这里的delay刚好等于duration,也就是缩小之后,就开始还原
}
)
]),
...//后面三个数值的动画类似,依次加大delay就可以
]).start(this.loopAnimation2.bind(this));
}
...
效果粗来了!
怎么说呢,动画是粗来了,基本实现了循环动画,但是总觉得缺少那么点灵(sao)动(qi),仔细分析会发现,这是因为我们的循环的实现是通过执行回调来实现的,当parallel执行完毕之后,会执行回调进行第二次动画,也就是说parallel不执行完毕,第二遍是不会开始的,这就是为什么动画会略显僵硬,因此仔细观察,第一个条块在执行完自己的缩小放大动画后,只有在等到第四个条也完成缩小放大动画,整个并行队列才算执行完,回调才会被执行,第二遍动画才开始。
So,回调能被提前执行吗?
Nooooooooooooooooooooop!
多么感人,眼角貌似有翔滑过。。。。。
但是,不哭站撸的程序猿是不会轻易折服的,在多次查阅Animated文档之后,无果,累觉不爱(或许我们并不合适)~~~
好在facebook还提供了另一个更基础的requestAnimationFrame函数,熟悉canvas动画的同学对它应该不陌生,这是一个动画重绘中经常遇到的方法,动画的最基本原理就是重绘,通过在每次绘制的时候改变元素的位置或者其他属性使得元素在肉眼看起来动起来了,因此,在碰壁之后,我们尝试用它来实现我们的动画。
其实,用requestAnimationFrame来实现动画,就相当于需要我们自己来做插值,通过特定方式动态计算出中间值,将这些中间值赋值给元素的高度,就实现了动画。
这四个动画是完全相同的,只是以一定延迟顺序进行的,因此分解之后只要实现一个就可以了,每个动画就是条块的高度随时间呈现规律变化:
大概就介么个意思。这是一个分段函数,弄起来比较复杂,我们可以将其近似成相当接近的连续函数--余弦函数,这样就相当轻松了:
let animationT=0;//定义一个全局变量来标示动画时间
let animationN=50,//余弦函数的极值倍数,即最大偏移值范围为正负50
animationM=150;//余弦函数偏移值,使得极值在100-200之间
componentDidMount(){
animationT=0;
requestAnimationFrame(this.loopAnimation.bind(this));//组件加载之后就执行loopAnimation动画
}
loopAnimation(){
var t0=animationT,t1=t0+0.5,t2=t1+0.5,t3=t2+timeDelay,t4=t3+0.5;//这里分别是四个动画的当前时间,依次加上了0.5的延迟
var v1=Number(Math.cos(t0).toFixed(2))*animationN+animationM;//将cos函数的小数值只精确到小数点2位,提高运算效率
var v2=Number(Math.cos(t1).toFixed(2))*animationN+animationM;
var v3=Number(Math.cos(t2).toFixed(2))*animationN+animationM;
var v4=Number(Math.cos(t3).toFixed(2))*animationN+animationM;
this.setState({
fV:v1,
sV:v2,
tV:v3,
foV:v4
});
animationT+=0.35;//增加时间值,每次增值越大动画越快
requestAnimationFrame(this.loopAnimation.bind(this));
}
最终效果:
可以看出,相当灵(sao)动(qi),由此也可以一窥RN的性能,我们知道,RN中的JS是运行在JavaScriptCore环境中的,对大多数React Native应用来说,业务逻辑是运行在JavaScript线程上的。这是React应用所在的线程,也是发生API调用,以及处理触摸事件等操作的线程。更新数据到原生支持的视图是批量进行的,并且在事件循环每进行一次的时候被发送到原生端,这一步通常会在一帧时间结束之前处理完(如果一切顺利的话)。可以看出,我们在每一帧都进行了运算并改变了state,这是在JavaScript线程上进行的,然后通过RN推送到native端实时渲染每一帧,说实话,最开始对动画的性能还是比较担忧的,现在看来还算不错,不过这只是一个很简单的动画,需要绘制的东西很少,在实际app应用中,还是需要结合实际情况不断优化。
这个动画应该还有更好更便捷的实现方式,这里抛砖引玉,希望大家能够在此基础上探索出性能更好的实现方式并分享出来。
好了,这次动画初探就到这里,随着学习和实践的深入,还会陆续推出一系列分享,敬请关注。
ReactNative学习实践--动画初探之加载动画的更多相关文章
- layui数据表格分页加载动画,自己定义加载动画,"加载中..."
记录思路,仅供参考 在表格渲染完成后,在done回调函数中给分页动态加点击事件, 关闭"加载中..."动画也是在 done回调函数中关闭 这是我实现的思路,记录给大家参考. , d ...
- html5动画之等待加载动画
<div class="loading"> <p>100<span></span></p> </div> ; ...
- 网页图表Highcharts实践教程之标签组与加载动画
网页图表Highcharts实践教程之标签组与加载动画 Highcharts标签组 在图表的大部分元素都提供了标签功能.但非常多时候,我们须要额外说明一些信息.这个时候借助原有的图表元素的标签功能就 ...
- Blend学习之Loading加载动画
介绍: Blend for visual studio 与 visual studio 是有区别的 两者虽然是IDEA 但是专注的方向是不同的,前者是专注UI后者专注业务逻辑,当然你要用blend f ...
- ReactNative学习实践--Navigator实践
离上次写RN笔记有一段时间了,期间参与了一个新项目,只在最近的空余时间继续学习实践,因此进度比较缓慢,不过这并不代表没有新进展,其实这个小东西离上次发文时已经有了相当大的变化了,其中影响最大的变化就是 ...
- iOS开发——图形编程Swift篇&CAShapeLayer实现圆形图片加载动画
CAShapeLayer实现圆形图片加载动画 几个星期之前,Michael Villar在Motion试验中创建一个非常有趣的加载动画. 下面的GIF图片展示这个加载动画,它将一个圆形进度指示器和圆形 ...
- 使用CAShapeLayer来实现圆形图片加载动画[译]
原文链接 : How To Implement A Circular Image Loader Animation with CAShapeLayer 原文作者 : Rounak Jain 译文出自 ...
- QT自定义控件系列(二) --- Loading加载动画控件
本系列主要使用Qt painter来实现一些基础控件.主要是对平时自行编写的一些自定义控件的总结. 为了简洁.低耦合,我们尽量不使用图片,qrc,ui等文件,而只使用c++的.h和.cpp文件. 由于 ...
- webAPP制作框架Ionic--构建APP侧边栏 底部选项卡 轮播图 加载动画
超好用的移动框架--Ionic Ionic是一个轻量的手机UI库,具有速度快,界面现代化.美观等特点. 为了解决其他一些UI库在手机上运行缓慢的问题,它直接放弃了IOS6和Android4.1以下的版 ...
随机推荐
- js中判断是不是数字
使用isaNa来进行判断,这个函数的底层实现是:Nunmber,如果经过转化是数字则返回false,否则返回true Number('100px')返回的是ANA,parseInt('100px')返 ...
- 读取Assets中的文件数据
try { // 返回的字节流 InputStream is = getResources().getAssets().open("info.txt"); // 当读取时,属于文本 ...
- [转]Linux下which、whereis、locate、find 命令的区别
转自:http://312788172.iteye.com/blog/730280 我们经常在linux要查找某个文件,但不知道放在哪里了,可以使用下面的一些命令来搜索.这些是从网上找到的资料,因为有 ...
- Cocos2d-android (02) 添加一个精灵对象
什么是精灵: 1.精灵就是游戏当中的一个元素,通常用于代表画面当前中的一个事物,例如主人公,NPC和背景元素等: 2.一个精灵对象通常都与一张图片关联 3.精灵对象可以通过动作对象(CCAction) ...
- C#单元测试
简单来说,单元测试就是局部测试,即是对项目中的某个静态类测试.静态方法测试.类的实例化测试以及类的方法测试.当您有一个具体的项目时您可以通过运行查看结果的方式进行测试,但当您只有一个类而没有完整的项目 ...
- 说说Python 中的文件操作 和 目录操作
我们知道,文件名.目录名和链接名都是用一个字符串作为其标识符的,但是给我们一个标识符,我们该如何确定它所指的到底是常规文件文件名.目录名还是链接名呢?这时,我们可以使用os.path模块提供的isfi ...
- Epic - Spiral Matrix
Given aNXN matrix, starting from the upper right corner of the matrix start printingvalues in a coun ...
- 通过xshell 设置代理上网
前言: 前段时间,选修了一门并行计算,老师给我们每个人分配了一个linux登录账号,通过这个账号,可能登录学校的一台linux . 一次偶然的机会,了解到可以通过xshell , ssh服务器给本地开 ...
- SSO单点登录在web上的关键点 cookie跨域
概述 其实WEB单点登录的原理挺简单的,抛开那些复杂的概念,简单来讲讲如何实现一个最基本的单点登录 首先需要有两个程序 例如:http://www.site-a.com 我们简称A http://ww ...
- 《Java数据结构与算法》笔记-CH5-链表-2单链表,增加根据关键字查找和删除
/** * Link节点 有数据项和next指向下一个Link引用 */ class Link { private int iData;// 数据 private double dData;// 数据 ...