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以下的版 ...
随机推荐
- android学习——MeasureSpec介绍及使用
一.MeasureSpc类说明 SDK的介绍:MeasureSpc类封装了父View传递给子View的布局(layout)要求.每个MeasureSpc实例代表宽度或者高度 它有三种模式:①.UNSP ...
- selenium python (三)鼠标事件
# -*- coding: utf-8 -*-#鼠标事件 #ActionChains类中包括: # context_click() 右击: ...
- PermGen space Eclipse 终极解决方案
1.选中项目右键 run or debug configurations... 2.在 VM arguments 加入 -Xms128m -Xmx512m -XX:PermSize=64M -XX: ...
- bzoj 3594 [Scoi2014]方伯伯的玉米田(DP+二维BIT)
[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=3594 [题意] 给定一个n个数的序列,有K次将一个区间内的数加1的机会,问最长不下降子 ...
- Linux学习--第二波
虽然安装的centos感觉不能上网,权限也不知道怎么设置. 偶然的机会发现了一个好东西,博客:http://www.cnblogs.com/xiaoluo501395377/tag/CentOS/.有 ...
- c++中获取字符cin,getchar,get,getline的区别
http://www.imeee.cn/News/GouWu/20090801/221298.html cin.get()与getchar()函数有什么区别? 详细点..C++中几个输入函数的用法和区 ...
- PostgreSQL的 fdw 跨库使用
create extension postgres_fdw; ',dbname 'postgres'); create user mapping for android_market server s ...
- jszs 对象引用
<!doctype html> <html> <head> <meta charset="utf-8"> <title> ...
- 无线网WEP的安全测试及防范
650) this.width=650;" border="0" alt="" src="http://img1.51cto.com/att ...
- 事件委托(event delegation)
事件委托给我带来的第一印象是,如果可以的话请尝试得经常使用它,性能好! 通过字符串拼接后,并进行DOM插入,不会复制事件,此时需要进行事件委托了!!! 优点 事件委托对于web应用程序的性能有如下几个 ...