效果图

代码已上传 GitHub:htt0p s : / / g i t hu b . c o m/ S il e n ce - G i t H u b /C o r e A n im a t i o n D em o 

可以自定义波浪高度、宽度、速度、方向、渐变速度、水的深度等参数。

实现原理

波浪的形状绘制在 CAShapeLayer 上。通过 CADisplayLink 与屏幕刷新频率同步,每次刷新都绘制新的波浪,并改变小船的位置和角度。另外,水和天空的颜色是渐变的,由 CAGradientLayer 实现,其中,显示水的 CAGradientLayer 需要有波浪形状的 CAShapeLayer 的遮罩(mask)。

CAShapeLayer

CAShapeLayer 的属性 path (CGPath)就是图层要显示的形状。把波浪的形状绘制出来,赋值给此属性即可。

创建 CADisplayLink,相应的 target 实现屏幕刷新时要调用的方法。把 CADisplayLink 加入 RunLoop 中。通过 isPaused 属性控制 CADisplayLink 是否暂停(target 是否调用方法)

private var waveLink: CADisplayLink?
waveLink = CADisplayLink(target: self, selector: #selector(waveLinkRefresh))
waveLink?.isPaused = truewaveLink?.add(to: .current, forMode: .defaultRunLoopMode)

绘制波浪

波浪的形状关键是正弦函数曲线

y = A * sin(x + B)

参数 A 决定了波浪的高度;参数 B 决定了波浪在 x 轴的位置。

用一个属性 currentPhase 表示参数 B

private var currentPhase: CGFloat = 0

每次屏幕刷新的时候用 currentPhase 绘制,然后更新此属性,加上一个固定的数。这样波浪就会朝左或右匀速移动。

为了使波浪高度逐渐变化,用一个属性表示参数 A,然后每次绘制后更新此属性,加上一个固定的数,直到波浪高度达到目标值。

小船的位置和旋转角度

已知小船 x 轴坐标,通过正弦函数可以直接计算出小船的 y 轴坐标。此外,小船需要随着波浪旋转,旋转至船底与波浪表面相切。这就要对正弦函数进行求导

y' = A * cos(x + B)

用以上式子计算出小船所在位置的 y',表示正弦函数在此处的切线斜率,几何意义是切线与 x 轴的夹角的正切值。反正切即可求出切线与 x 轴的夹角,也就是小船需要旋转的角度

angle = atan(y')

用以上旋转角度,改变小船视图(UIView)的 transform,调用 CGAffineTransform 的 rotated(by:) 方法,实现小船的旋转。

CAGradientLayer

CAGradientLayer 默认的颜色渐变方向是由上至下。给 colors 属性赋值一个包含 CGColor 的数组,则图层颜色由上至下,从数组第一个值经中间值渐变至最后一个值。

显示水的 CAGradientLayer 需要呈现波浪形状,需要 CAShapeLayer 的遮罩。把绘制好波浪形状的 CAShapeLayer 赋值给 CAGradientLayer 的 mask 属性即可。

以上是动画效果的实现原理,具体见 Git H u b: h t t ps : / / gi t h u b. c o m / S i l e nc e - G i tH u b / C or e A n im a t i o n De m o 

转载请注明出处:ht tp :/ / w w w .c n b l og s .c o m /s i l en c e -c n b l og s /p / 6 97 9 4 1 8 .h t m l