# 实现波浪动画

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

### CAShapeLayer

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

### CADisplayLink

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

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



### 绘制波浪

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

```
y = A*sin(x+B)
```

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

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

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

### 小船的位置和旋转角度

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

```
y' = A * cos(x + B)
```

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

```
angle = atan(y')
```

用以上旋转角度，改变小船视图\(UIView\)的 transform，调用 CGAffineTransformRotate 方法，实现小船的旋转。

### CAGradientLayer

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

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

