返回 导航

Swift

hangge.com

Swift - 实现一个波浪动画效果(CADisplayLink定时器的使用样例)

作者:hangge | 2019-01-31 08:10
    说到定时器我们常常会想到 Timer,我之前也写过不少相关文章。其实除了 Timer,还有 CADisplayLink 这个定时器。不同于 Timer 需要在初始化时设置一个定时周期,CADisplayLink 是一个同步屏幕刷新频率的定时器。

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+.swiftUIColor 扩展)
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)

回到顶部