Swift - 实现一个波浪动画效果(CADisplayLink定时器的使用样例)
作者:hangge | 2019-01-31 08:10
说到定时器我们常常会想到 Timer,我之前也写过不少相关文章。其实除了 Timer,还有 CADisplayLink 这个定时器。不同于 Timer 需要在初始化时设置一个定时周期,CADisplayLink 是一个同步屏幕刷新频率的定时器。
(2)WaveView.swift(自定义的波浪视图组件)
(3)UIColor+.swift(UIColor 扩展)
1,CADisplayLink 的介绍
(1)由于 iOS 设备的屏幕刷新频率是固定的 60Hz,因此 CADisplayLink 的 selector 默认调用周期也是是每秒 60 次(这个周期可以通过 frameInterval 属性设置)。
(2)不同于 Timer 因为阻塞问题会造成触发时间的延迟,CADisplayLink 在正常情况下会在每次刷新结束时被调用,因此精确度相当高。适合做 UI 的不停重绘,比如自定义动画引擎或者视频播放的渲染。
2,样例效果图
(1)下面使用 CADisplayLink 实现一个波浪效果。波浪会从右往左不断地移动,同时上方的文字也会随之上下浮动。
(2)波浪曲线其实是一条正弦曲线,整个动画的实现原理是在每次屏幕刷新时,改变曲线的偏移量再重新绘制,从而实现波浪起伏的效果。
3,样例代码
(1)ViewController.swift(主视图控制器)
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // 创建文本标签 let label = UILabel() label.text = "正在加载中......" label.textColor = .white label.textAlignment = .center label.frame = CGRect(x: (screenWidth() * 0.5 - 100), y: 0, width: 200, height: 50) // 创建波浪视图 let waveView = WaveView(frame: CGRect(x: 0, y: 0, width: screenWidth(), height: 130)) // 波浪动画回调 waveView.closure = {centerY in // 同步更新文本标签的y坐标 label.frame.origin.y = waveView.frame.height + centerY - 55 } // 添加两个视图 self.view.addSubview(waveView) self.view.addSubview(label) // 开始播放波浪动画 waveView.startWave() } // 返回当前屏幕宽度 func screenWidth() -> CGFloat { return UIScreen.main.bounds.size.width } }
(2)WaveView.swift(自定义的波浪视图组件)
import UIKit import QuartzCore // 波浪曲线动画视图 // 波浪曲线公式:y = h * sin(a * x + b); h: 波浪高度, a: 波浪宽度系数, b: 波的移动 class WaveView: UIView { // 波浪高度h var waveHeight: CGFloat = 7 // 波浪宽度系数a var waveRate: CGFloat = 0.01 // 波浪移动速度 var waveSpeed: CGFloat = 0.05 // 真实波浪颜色 var realWaveColor: UIColor = UIColor(55, 153, 249) // 阴影波浪颜色 var maskWaveColor: UIColor = UIColor(55, 153, 249, 0.3) // 波浪位置(默认是在下方) var waveOnBottom = true // 波浪移动回调 var closure: ((_ centerY: CGFloat)->())? // 定时器 private var displayLink: CADisplayLink? // 真实波浪 private var realWaveLayer: CAShapeLayer? // 阴影波浪 private var maskWaveLayer: CAShapeLayer? // 波浪的偏移量 private var offset: CGFloat = 0 // 视图初始化 override init(frame: CGRect) { super.init(frame: frame) initWaveParame() } // 视图初始化 required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) initWaveParame() } // 组件初始化 private func initWaveParame() { // 真实波浪配置 realWaveLayer = CAShapeLayer() var frame = bounds realWaveLayer?.frame.origin.y = frame.size.height - waveHeight frame.size.height = waveHeight realWaveLayer?.frame = frame // 阴影波浪配置 maskWaveLayer = CAShapeLayer() maskWaveLayer?.frame.origin.y = frame.size.height - waveHeight frame.size.height = waveHeight maskWaveLayer?.frame = frame layer.addSublayer(maskWaveLayer!) layer.addSublayer(realWaveLayer!) } // 开始播放动画 func startWave() { // 开启定时器 displayLink = CADisplayLink(target: self, selector: #selector(self.wave)) displayLink!.add(to: RunLoop.current, forMode: RunLoop.Mode.common) } // 停止播放动画 func endWave() { // 结束定时器 displayLink!.invalidate() displayLink = nil } // 定时器响应(每一帧都会调用一次) @objc func wave() { // 波浪移动的关键:按照指定的速度偏移 offset += waveSpeed // 起点y坐标(没有波浪的一侧) let startY = self.waveOnBottom ? 0 : frame.size.height let realPath = UIBezierPath() realPath.move(to: CGPoint(x: 0, y: startY)) let maskPath = UIBezierPath() maskPath.move(to: CGPoint(x: 0, y: startY)) var x = CGFloat(0) var y = CGFloat(0) while x <= bounds.size.width { // 波浪曲线 y = waveHeight * sin(x * waveRate + offset) // 如果是下波浪还要加上视图高度 let realY = y + (self.waveOnBottom ? frame.size.height : 0) let maskY = -y + (self.waveOnBottom ? frame.size.height : 0) realPath.addLine(to: CGPoint(x: x, y: realY)) maskPath.addLine(to: CGPoint(x: x, y: maskY)) // 增量越小,曲线越平滑 x += 0.1 } let midX = bounds.size.width * 0.5 let midY = waveHeight * sin(midX * waveRate + offset) if let closureBack = closure { closureBack(midY) } // 回到起点对侧 realPath.addLine(to: CGPoint(x: frame.size.width, y: startY)) maskPath.addLine(to: CGPoint(x: frame.size.width, y: startY)) // 闭合曲线 maskPath.close() // 把封闭图形的路径赋值给CAShapeLayer maskWaveLayer?.path = maskPath.cgPath maskWaveLayer?.fillColor = maskWaveColor.cgColor realPath.close() realWaveLayer?.path = realPath.cgPath realWaveLayer?.fillColor = realWaveColor.cgColor } }
(3)UIColor+.swift(UIColor 扩展)
import UIKit //UIColor扩展 extension UIColor { //使用rgb方式生成自定义颜色 convenience init(_ r : CGFloat, _ g : CGFloat, _ b : CGFloat) { let red = r / 255.0 let green = g / 255.0 let blue = b / 255.0 self.init(red: red, green: green, blue: blue, alpha: 1) } //使用rgba方式生成自定义颜色 convenience init(_ r : CGFloat, _ g : CGFloat, _ b : CGFloat, _ a : CGFloat) { let red = r / 255.0 let green = g / 255.0 let blue = b / 255.0 self.init(red: red, green: green, blue: blue, alpha: a) } }源码下载:hangge_2278.zip
附:上波浪样式
1,效果图
波浪视图组件(WaveView)还提供了个 waveOnBottom 属性,我们将其设置为 false 即可让波浪显示在组件视图的上侧。
2,样例代码
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // 创建文本标签 let label = UILabel() label.text = "正在加载中......" label.textColor = UIColor(55, 153, 249) label.textAlignment = .center label.frame = CGRect(x: (screenWidth() * 0.5 - 100), y: 0, width: 200, height: 50) // 创建波浪视图 let waveView = WaveView(frame: CGRect(x: 0, y: screenHeight() - 60, width: screenWidth(), height: 60)) // 波浪显示在上方 waveView.waveOnBottom = false // 波浪动画回调 waveView.closure = {centerY in // 同步更新文本标签的y坐标 label.frame.origin.y = waveView.frame.origin.y + centerY - 60 } // 添加两个视图 self.view.addSubview(waveView) self.view.addSubview(label) // 开始播放波浪动画 waveView.startWave() } // 返回当前屏幕宽度 func screenWidth() -> CGFloat { return UIScreen.main.bounds.size.width } // 返回当前屏幕高度 func screenHeight() -> CGFloat { return UIScreen.main.bounds.size.height } }
全部评论(0)