需求
要求根据手机摇晃程度(加速计),来实现多个波浪线动画,要求两端低,中间高。
实现效果:
工作原理
正弦曲线公式:y=Asin(ωx+φ)+k
A :振幅,曲线最高位和最低位的距离
ω :角速度,用于控制周期大小,单位x中起伏的个数
K :偏距,曲线上下偏移量
φ :初相,曲线左右偏移量
曲线图如下:
现在有一根正弦曲线出来了,要产生动画只要实时改变φ,就会有左右滚动的效果,多个曲线就是原始φ不同,多个曲线峰值高低则改变A ,波浪线要中间高两边低其实也简单,只要再乘以半个周期的标准正弦曲线。
看了图片相信恍然大悟了吧
代码实现
import UIKit
import CoreMotion
class SportWaveLineView: UIView {
var lastAccelera: Double = 0.0
let path1 = UIBezierPath()
let path2 = UIBezierPath()
let path3 = UIBezierPath()
let path4 = UIBezierPath()
let layer1 = CAShapeLayer()
let layer2 = CAShapeLayer()
let layer3 = CAShapeLayer()
let layer4 = CAShapeLayer()
var offset: Double = 0.0
lazy var noMotionlabel: UILabel = {
let noMotionlabel = UILabel(frame: .zero)
noMotionlabel.text = """
鹰和鹰将利用手机传感器来记录你的跑步
请确认手机可以感受到迈步或者摆臂,以减少误差
"""
noMotionlabel.numberOfLines = 2
noMotionlabel.textAlignment = NSTextAlignment.center
noMotionlabel.font = UIFont.systemFont(ofSize: 12)
noMotionlabel.textColor = UIColor(white: 1.0, alpha: 0.3)
noMotionlabel.backgroundColor = UIColor(hexString: "#393E4C")
addSubview(noMotionlabel)
noMotionlabel.snp.makeConstraints({ (make) in
make.center.equalToSuperview()
make.left.equalToSuperview().offset(30)
make.right.equalToSuperview().offset(-30)
})
return noMotionlabel
}()
override init(frame: CGRect) {
super.init(frame: frame)
layer1.strokeColor = UIColor(white: 1.0, alpha: 0.3).cgColor
layer1.fillColor = UIColor.clear.cgColor
layer1.lineWidth = 0.5
layer.addSublayer(layer1)
layer2.strokeColor = UIColor(white: 1.0, alpha: 0.3).cgColor
layer2.fillColor = UIColor.clear.cgColor
layer2.lineWidth = 0.5
layer.addSublayer(layer2)
layer3.strokeColor = UIColor(white: 1.0, alpha: 0.3).cgColor
layer3.fillColor = UIColor.clear.cgColor
layer3.lineWidth = 0.5
layer.addSublayer(layer3)
layer4.strokeColor = UIColor(white: 1.0, alpha: 0.3).cgColor
layer4.fillColor = UIColor.clear.cgColor
layer4.lineWidth = 0.5
layer.addSublayer(layer4)
//判断加速计是否可用,不能就一根直线
if !SportTrackingManager.shareInstance.accelerometerManager.isAccelerometerAvailable {
path1.move(to: CGPoint(x: 0, y: self.bounds.height / 2))
path1.addLine(to: CGPoint(x: bounds.width, y: bounds.height / 2))
layer1.path = path1.cgPath
return
}
SportTrackingManager.shareInstance.accelerometerManager.accelerometerUpdateInterval = 1 / 15
SportTrackingManager.shareInstance.accelerometerManager.startAccelerometerUpdates(to: OperationQueue.main) { (accelerometerData, error) in
let acceleration = accelerometerData?.acceleration
// print(acceleration?.x, acceleration?.y, acceleration?.z)
let accelera = sqrt(pow(acceleration!.x, 2) + pow(acceleration!.y, 2) + pow(acceleration!.z, 2))
if accelera < 1.05 && accelera > 0.9 {
return
}
if accelera > self.lastAccelera {
self.lastAccelera = accelera
self.lastAccelera *= 1.4
}
if accelera <= 0.9 {
let lowTemp = 0.9 - accelera
let highTemp = self.lastAccelera - 1.05
if lowTemp > highTemp {
self.lastAccelera = 1.05 + lowTemp
self.lastAccelera *= 1.4
}
}
if self.lastAccelera > 6 {
self.lastAccelera = 6
}
}
//根据手机刷新频率刷新曲线
let display = CADisplayLink(target: self, selector: #selector(drawWaveLine))
display.add(to: RunLoop.current, forMode: .commonModes)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func drawWaveLine() {
path1.removeAllPoints()
path2.removeAllPoints()
path3.removeAllPoints()
path4.removeAllPoints()
path1.move(to: CGPoint(x: 0, y: self.bounds.height / 2))
path2.move(to: CGPoint(x: 0, y: self.bounds.height / 2))
path2.addLine(to: CGPoint(x: 5, y: self.bounds.height / 2))
path3.move(to: CGPoint(x: 0, y: self.bounds.height / 2))
path3.addLine(to: CGPoint(x: 10, y: self.bounds.height / 2))
path4.move(to: CGPoint(x: 0, y: self.bounds.height / 2))
path4.addLine(to: CGPoint(x: 15, y: self.bounds.height / 2))
for x in 0...Int(bounds.width) {
let y = 9 * self.lastAccelera * sin(Double.pi / Double(UIScreen.main.bounds.width / 6) * Double(x) + offset) * (sin(Double(x) * Double.pi / Double(UIScreen.main.bounds.width)))
path1.addLine(to: CGPoint(x: Double(x), y: y + Double(bounds.height / 2)))
if CGFloat(x) < bounds.width - 5 {
let y1 = 8 * self.lastAccelera * sin(Double.pi / Double(UIScreen.main.bounds.width / 6) * Double(x) + offset - 0.2) * (sin(Double(x) * Double.pi / Double(UIScreen.main.bounds.width - 5)))
path2.addLine(to: CGPoint(x: Double(x) + 5, y: y1 + Double(bounds.height / 2)))
}
if CGFloat(x) < bounds.width - 10 {
let y2 = 7 * self.lastAccelera * sin(Double.pi / Double(UIScreen.main.bounds.width / 6) * Double(x) + offset - 0.4) * (sin(Double(x) * Double.pi / Double(UIScreen.main.bounds.width - 10)))
path3.addLine(to: CGPoint(x: Double(x) + 10, y: y2 + Double(bounds.height / 2)))
}
if CGFloat(x) < bounds.width - 15 {
let y3 = 6 * self.lastAccelera * sin(Double.pi / Double(UIScreen.main.bounds.width / 6) * Double(x) + offset - 0.6) * (sin(Double(x) * Double.pi / Double(UIScreen.main.bounds.width - 15)))
path4.addLine(to: CGPoint(x: Double(x) + 15, y: y3 + Double(bounds.height / 2)))
}
}
path1.addLine(to: CGPoint(x: bounds.width, y: bounds.height / 2))
path2.addLine(to: CGPoint(x: bounds.width, y: bounds.height / 2))
path3.addLine(to: CGPoint(x: bounds.width, y: bounds.height / 2))
path4.addLine(to: CGPoint(x: bounds.width, y: bounds.height / 2))
layer1.path = path1.cgPath
layer2.path = path2.cgPath
layer3.path = path3.cgPath
layer4.path = path4.cgPath
//没有加速就显示文案,波浪线归0
if self.lastAccelera <= 0.1 {
self.lastAccelera = 0.0
self.noMotionlabel.isHidden = false
} else {
self.noMotionlabel.isHidden = true
self.lastAccelera -= 0.08
}
//波浪线移动
offset -= 0.18
if offset < -60 * Double.pi {
offset = 0
}
}
}