今天整理文件的时候发现以前写的一个动画,感觉还不错,就和大家分享一下。
如何实现一个如下图的动画呢?

下面我们就来一步步的实现它。
动画拆解
做一个复杂的东西的时候,我们需要将它拆解成若干个容易实现的细节,之后在将这些细节有序的累加起来就可以实现我们最初的复杂的结构,没准当你完成的时候自己都会发出 It’s amazing 的感叹。
来看这个动画,我们发现有两个主要元素,一个是晃动水波线,一个是由于水深造成的颜色渐变。那我们得到了第一个容易实现的细节点:
1.水深的颜色渐变
晃动的水波线对我们来说似乎并不是一个简单的元素,那我们再来拆解,或许你注意到了这是一个正弦曲线的水平移动,只是这个正弦曲线稍微有点特殊,加入了一些浮动波峰和波谷的参数。那么我们得到了第二个细节点:
2.水波正弦曲线
同时也确定了第三个细节点:
3.正弦曲线模拟波浪参数方式
然后我们还有一个工作要做,就是以上元素以何种方式组织起来,这也是第四个点:
4.以上元素的组织方式
动画实现
要实现这个动画的话我们先要来谈谈第 4 点,因为你不能将你分解的元素有效的组织起来的话我们的动画就不能实现。其实在动画拆解的时候,我们心里就要对第 4 点心中有数了,这是一个慢慢积累经验的过程,当然我们可以参考别人。
针对于第 4 点,我们的实现方式是用第 2 点和第 3 点实现的曲线设置成一个 mask,去遮罩一个颜色有渐变的矩形区域(第 1 点),这样就得到了我们想要的波浪。想要波浪动起来,我们让波浪曲线随着时间发生变化即可。要做成效果当中的三条波浪,只需要将这些元素放置 3 份即可,只是设置参数不同罢了。
下面我们来分别实现上面拆解的细节点:
针对第 1 点,我们采用CAGradientLayer来实现,你不仅设置它从某个颜色渐变到另外一个颜色,而且还可以设置它的渐变方向等等。下面几行代码就可以实现我们想要的效果。
1 | _firstGradientLayer = [CAGradientLayer layer]; |
效果如下:

对于第 2 点,就要用到我们高中的知识了,对于屏幕上的每一个 x 值,计算出对应的 y 值,并将它们连接起来。其中FirstWaveSpeed*BEI6*_firstWaveTime是为了让波浪曲线随时间变化,产生向右平移运动的效果,_firstVariable这个属性是第 3 点当中要实现的更好的模拟波浪效果的参数,我们下面再进行介绍。
1 | - (CGMutablePathRef)getFirstCurrentWavePath |
下面我们来看 3 点,为了更好的模拟波浪的效果,我们需要一个曲线振幅的变化,我这里只做了简单的随时间在设定的范围内增大和变小的操作,如果想要更好的效果,可以添加更多参数来控制,当然这也需要一些物理学的知识。
1 | -(void)animateFirstWave |
以上基本元素都已经准备好了,那我们来做第 4 点,将上述元素整合起来。我们用 CAShapeLayer 来为第 1 点实现的渐变色提供提供遮罩。
1 | _firstWaveLayer = [CAShapeLayer layer]; |
这样我们就得到了一个静态的波浪

想要波浪动起来,我们要为它加入上面提到的时间变量_firstWaveTime,一般情况下我们加入时间变量都会采用NSTimer来进行计时,但是此处我们使用CADisplayLink来进行定时操作。为什么使用CADisplayLink呢,因为CADisplayLink的调用频率是和屏幕的刷新频率一致的,这样的话不会出现NSTimer那种由于和屏幕刷新时间冲突造成的渲染滞后画面卡顿的现象。
1 | - (void)initTimer |
这样我们就得到了第一个流动的波浪

下面只要在重复添加两条线就可以得到我们想要的效果了,不过,需要注意的是第三条波浪的颜色渐变是从左到右的哦,当然这是一个小问题。
动画调整
大部分情况下,我们写出来的复杂动画都会和设计师想要的样子有一定差距,当然也不排除能拿到精准参数的情况,但毕竟让一个设计师告诉你正弦曲线的起始位置不太现实,这个时候我们需要把能控制这个动画的一些参数单独写出来,不要用写死的值,其他的参数依赖于这些值,由这些值计算出来。这样我们就可以方便的调整动画的显示了,甚至可以让设计师自己动手调整出想要的样子。
1 | static float const FirstWaveCenterHeight = 162; |
源码:https://github.com/yangzq007/SHDemo->水波动画