我们先看一下效果,现在很多app有这种交互,普通的下拉刷新有点low,这样感觉酷炫一点
水波动画的重点就是sin
、cos
函数
我们来复习一下不知道是初中还是高中的知识
正弦型函数解析式:y=Asin(ωx+φ)+h
φ(初相位):决定波形与X轴位置关系或横向移动距离(左加右减)
ω:决定周期(最小正周期T=2π/|ω|)
A:决定峰值(即纵向拉伸压缩的倍数)
h:表示波形在Y轴的位置关系或纵向移动距离(上加下减)
po出项目中的属性
1 | private struct BingoWaveRefreshViewData{ |
绘制曲线
如果要根据函数绘制曲线,我们需要设置各个值
初相位设置为0
周期设置为2π
波峰设置为1
位移设置为0
然后根据y = sin(x)计算出点的坐标然后使用CGMutablePath
对象的addLine
方法连线得出最后路径
由静态到动态
我们已经绘制了一条静态的曲线,我们通过什么方法来让它变成动态的呢?我们看上面各个值的定义,我们通过初相位来让曲线左右平移,我们需要通过定时器来让初相位根据时间进行线性的变化,我们设置单位时间的初相位的变化值为π/2
,因为函数的周期为2π
,4个单位时间为一个周期,我们可以通过改变这个变量来控制波浪平移的速度
顶部波浪的刷新方法,firstWaveLayer
已创建好1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17func configFirstWaveLayerPath() -> Void {
guard let scrollView = scrollView else {
return
}
var y = _offsetY
let path : CGMutablePath = CGMutablePath()
path.move(to: CGPoint(x: 0, y: y))
let waveWidth : CGFloat = scrollView.frame.size.width
for x in 0...Int(waveWidth) {
y = _amplitude * sin(_cycle * CGFloat(x) + _offsetX) + _offsetY
path.addLine(to: CGPoint(x: CGFloat(x), y: y))
}
path.addLine(to: CGPoint(x: waveWidth, y: self.frame.size.height))
path.addLine(to: CGPoint(x: 0, y: self.frame.size.height))
path.closeSubpath()
firstWaveLayer.path = path
}
顶部波浪的刷新方法,secondWaveLayer
已创建好1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18func configSecondWaveLayerPath() -> Void {
guard let scrollView = scrollView else {
return
}
var y = _offsetY
let path = CGMutablePath()
path.move(to: CGPoint(x: 0, y: y))
let forward = CGFloat(M_PI) / (_cycle * 4.0)
let waveWidth : CGFloat = scrollView.frame.size.width
for x in 0...Int(waveWidth) {
y = _amplitude * cos(_cycle * CGFloat(x) + _offsetX + forward) + _offsetY
path.addLine(to: CGPoint(x: CGFloat(x), y: y))
}
path.addLine(to: CGPoint(x: waveWidth, y: self.frame.size.height))
path.addLine(to: CGPoint(x: 0, y: self.frame.size.height))
path.closeSubpath()
secondWaveLayer.path = path
}
调整两个波浪的偏移量
我们其中一个是sin
和cos
函数,由上图可见两个曲线错位了1/8个周期,我们如果要让上下对称需要将cos
函数加1/4的个周期
1 | let forward = CGFloat(M_PI) / (_cycle * 4.0) |
刷新波峰
通过_increase属性交替来改变波峰的值,增长到最大时候开始减小,减小到最小的开始增大
1 | func configWaveAmplitude() -> Void { |
添加监听
监听scrollView.contentOffset
的变化
1 | override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { |