学习和实践react已经有一段时间了,在经历了从最初的彷徨到解决痛点时的兴奋,再到不断实践后遭遇问题时的苦闷,确实被这一种新的思维方式和开发模式所折服,但react也不是万能的,在很多场景下滥用反而会适得其反,这里不展开讨论。
有了react的实践经验,结合之前自己的一点ios开发经验,决定继续冒险,开始react-native学习和实践,目前主要是从常规的native功能入手,逐步用react-native实现,基础知识如开发环境搭建、调试工具等官方文档有很清楚的指引,不再赘述,这里主要是想把实际学习实践中遇到的坑或者有意思的经历记录下来,为广大react-native初学者提供一点参考。O(∩_∩)O~
话不多说,进入正题,今天要实现的是一个加载动画,效果如下:
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应用中,还是需要结合实际情况不断优化。
这个动画应该还有更好更便捷的实现方式,这里抛砖引玉,希望大家能够在此基础上探索出性能更好的实现方式并分享出来。