前言

iOS下实现水波动画,动画曲线使用的是正弦型函数解析式
效果图如下(图在最后有点卡顿的感觉,是因为gif重新开始播放了)

PS:gif图中下面的Water,在水波动画的基础上,使用遮罩实现了Water字体的蓝白颜色交替

image

正弦函数

正弦函数是高中学过的知识,这里不再做详细介绍,具体可以查看百科

我们主要使用到的是正弦函数的几个性质:

1
2
3
4
5
6
7
正弦型函数解析式:y = a * sin(ωx+φ)+ h

各常数值对函数图像的影响:
φ(初相位):决定波形与X轴位置关系或横向移动距离(左加右减)
ω:决定周期(最小正周期 T = 2π/|ω|)
A:决定峰值(即纵向拉伸压缩的倍数)
h:表示波形在Y轴的位置关系或纵向移动距离(上加下减)

动画解析

我们先来看上面方形的水波,主要就是利用正弦函数绘制出路径即可,如图:

image

我们知道,虽然我们肉眼看到的曲线是连续的,但是实际将曲线放大到一定程度,看到的是栅格的像素点。
所以我们只要计算出上图中的4个蓝色点,以及弧线上的所有点,再将之全部连成线,即可形成我们需要的水波形状。

我们从左上角的点开始,依次计算弧线上的点,以及之后的3个蓝色点即可,这里我们使用UIBezierPath来进行绘制(当然也可以使用CGMutablePathRef等),关键代码如下:

1
2
3
4
5
6
7
8
9
10
UIBezierPath *path = [UIBezierPath bezierPath];
path.lineWidth = 1;
[path moveToPoint:CGPointMake(0, leftUpPointY)];
for (CGFloat x = 0.0; x < waveWidth; x++) {
CGFloat y = 2 * a * sin(2.5 * M_PI / waveWidth * x + offset * M_PI / waveWidth) + leftUpPointY;
[path addLineToPoint:CGPointMake(x, y)];
}
[path addLineToPoint:CGPointMake(waveWidth, self.frame.size.height)];
[path addLineToPoint:CGPointMake(0, self.frame.size.height)];
[path closePath];

这里对使用到的正弦函数相关参数进行一下说明:

回顾一下正弦函数解析式:

1
y = a * sin(ωx+φ)+ h

各参数值说明:

下面的waveWidth为容器的宽度

1
2
3
4
5
6
7
`a`:峰值,值越大,峰越陡,我们可以通过调节该值,来实现水波波动的视觉效果;  

`ω`:周期,这里我们设定为`2.5`个周期(具体可以根据需要自行调整),则值为`2.5 * M_PI / waveWidth`;

`φ`:横向移动距离,调节该值,实现水波左右滑动的视觉效果(不是必须,设置为0也可以,根据需要自行调节即可);

`h`:纵向距离,即在容器中的一个位置,这里我们固定设置为左上角的`蓝色点`的`leftUpPointY `这个值即可;

经过多次调试后,我使用了以下参数:

1
CGFloat y = 2 * a * sin(2.5 * M_PI / waveWidth * x + offset * M_PI / waveWidth) + leftUpPointY;

以上就可以绘制出一段水波曲线了,不过还只是静态的,我们需要让水波连续地波动,就需要重复地进行绘制,并且在绘制过程中通过改变a值/φ值,来形成高低错落有致的视觉效果。

最开始我使用的是CADisplayLink来进行重复绘制,但是发现60帧/秒的频率,即1/60 = 0.017秒就重新进行一次绘制,速度有点快,导致CPU会上升得比较多。所以现在改用NSTimer,每0.05秒才进行一次绘制。

其中在NSTimer的每次回调里改变a值/φ值后再进行新的绘制:

1
2
3
4
a = (toAdd ? a + 0.01 : a - 0.01);
toAdd = (a <= 1 ? YES : (a >= 2.5 ? NO : toAdd));

offset = (offset < MAXFLOAT ? offset + _speed : offset - _speed);

蓝白颜色交替

之前无意中看到网上有人弄了水波动画(详见这里),就想着自己研究实现一下。

把水波的绘制动画研究出来后,发现那个demo里还有个颜色交替的效果,刚好以前弄过类似的,看了下源码,实现思路基本差不多:

使用2张图片,如图:

image

使用2个UIImageView分别放置2张图片,白色底的放下面,蓝色底的放上面,然后再在蓝色底upImageView.layer上加一层mask遮罩。

遮罩的路径也就是上面绘制水波的路径,只是不将路径绘制好后的CAShapeLayer加到容器的layer上去,而是设置为upImageView.layermask

1
self.upImgView.layer.mask = self.waveLayer;

总结

这里主要使用到的是正弦函数,正好复习了一下,能够应用到实际开发中去是挺有成就感的一件事。
这个动画的难点主要是弧线上的点的计算,不过只要多花点时间就可以得到一个满意的结果了。

代码已上传至github,需要可以去查看:水波动画源码


2016-08-04 01:42
Aevit
华师



摄影:Aevit 2016年7月 阳江闸坡 十里银滩