5-旋转的小菊-旋转画布和定时器
Posted 颐和园
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了5-旋转的小菊-旋转画布和定时器相关的知识,希望对你有一定的参考价值。
这个例子完全模仿了苹果的 UIActivityIndicatorView 控件,它显示了一个旋转的小菊花。这个控件使用起来是非常简单的,可以完全不需要你编写一行代码(使用 IB),也不需要任何图片!如果让你自己用 Core Graphics 实现这个控件,你会怎么做呢?
绘制外壳
class MockIndicator: UIView {
var leafColor: UIColor = .white
var hudColor: UIColor = UIColor(red: 170/255, green: 169/255, blue: 169/255, alpha: 0.5)
override func draw(_ rect: CGRect) {
// 1
let shorterSide = min(rect.width, rect.height)
// 2
var frame = CGRect(x: (rect.width-shorterSide)/2, y:(rect.height-shorterSide)/2, width: shorterSide, height: shorterSide)
// 3
RectanglePainter.drawFillColor(frame, fillColor: hudColor, cornerRadius: 40/240*shorterSide)
}
}
- 计算较短边,因为我们想绘制一个正方形的外壳。
- 计算外壳的 frame。如果初始化时传入的 rect 是一个长方形,我们需要将正方形外壳绘制在视图中心。
- 调用 RectanglePainter 绘制一个浅灰色的正方形外壳。
绘制叶片
let context = UIGraphicsGetCurrentContext()
// 1
context?.saveGState()
// 2
context?.translateBy(x:rect.midX, y:rect.midY)
// 3
frame = CGRect(x: 0, y: -(0.25*shorterSide), width: 0.025*shorterSide, height: 0.1333*shorterSide)
// 4
RectanglePainter.drawFillColor(frame, fillColor: leafColor, cornerRadius: 0)
// 5
context?.restoreGState()
- 保存绘图状态。
- 我们需要围绕 hud 中心绘制一圈叶片(当然目前先绘制一片)。为了方便计算叶片的绘制位置,我们需要将 context 的坐标原点暂时移动到 hud 的中心。tranlateBy 方法用于移动坐标原点到指定位置。
- 计算叶片的 frame,注意此时坐标的计算方式已经变了,当前坐标的原点是视图中心。同时,叶片的坐标和宽高采用的都是相对数值,即都是按比例缩放的。
- 在制定位置绘制填充矩形充当菊花叶片。
绘制更多叶片
我们总共需要绘制 12 片叶片,叶片之间相差 30 度:
// 1
var h: CGFloat = 0
var s: CGFloat = 0
var b: CGFloat = 0
var a: CGFloat = 0
leafColor.getHue(&h, saturation: &s, brightness: &b, alpha: &a)
for i in 0...11 {
let context = UIGraphicsGetCurrentContext()
// 2
let angle = -CGFloat.pi/6*CGFloat(i)
let brightness:CGFloat = 1-0.1*CGFloat(i%12)
// 3
let color = UIColor(hue: h, saturation: s, brightness: brightness, alpha: a)
context?.saveGState()
// 4
context?.translateBy(x:rect.midX, y:rect.midY)
// 5
context?.rotate(by:angle)
// 6
frame = CGRect(x: 0, y: -(0.25*shorterSide), width: 0.025*shorterSide, height: 0.1333*shorterSide)
// 7
RectanglePainter.drawFillColor(frame, fillColor: color, cornerRadius: 0)
context?.restoreGState()
}
- 求取叶片颜色中的灰度、饱和度、亮度和透明度,因为我们准备对亮度进行运算。
- 角度计算,因为 12 个叶片的旋转角度是不同的,两两之间相差 30 度,转换为弧度。加上负号,是因为我们的绘制顺序是逆时针进行的。
- 改变叶片的颜色,将亮度值不断降低(逆时针)。
- 改变坐标原点。
- 逆时针旋转画布。每次循环都会在上一次的基础上多旋转 30 度。
- frame 保持不变,因为实际上画布在旋转了。如果画布不旋转,那我们就必须旋转图形了,但那个的计算要复杂许多。因此,我们采用旋转画布的方式。
- 绘制矩形。
旋转的花瓣
要让小菊花旋转,我们又要用定时器了。在 Indicator.h 中增加如下属性声明:
private var tick:Int = 0 // 1
private var timer: CADisplayLink? // 2
var isAnimating = false // 3
private var lastTime:CFTimeInterval = 0.0 // 4
var secondPerLoop: Double = 1 // 5
- tick 假设我们旋转 1 周需要 12 次动画,那么 tick 就记录了当前进行到第几次。
- timer 定时器。
- isAnimating 记录动画的启动/停止状态。
- lastTime 是 CADisplayLink 定时器,它和一般的 Timer 不同,它初始化时不能设定触发间隔(它没有系统时钟信号),因此我们需要一个变量来保存上次触发函数的时间。
- secondPerLoop 小菊花每转一圈的时间,这里我们设置为 1,即每秒转一圈。
开启/关闭定时器,这和上一节没有太多区别了:
func startAnimation() {
if isAnimating {
timer?.invalidate()
}
timer = CADisplayLink.init(target: self, selector: #selector(animate))
timer?.add(to: RunLoop.current, forMode: .default)
isAnimating = true
lastTime = CACurrentMediaTime()
}
func stopAnimation() {
if isAnimating {
timer?.invalidate()
isAnimating = false
timer = nil
}
}
唯一需要注意的就是时钟启动时需要在 lastTime 中记录当前时间。
最重要的还是定时器回调函数:
@objc private func animate() {
guard let timer = timer else {
return
}
// 1
let period = secondPerLoop/12
let currentTime = timer.timestamp
// 2
let elapsed = currentTime - lastTime
// 3
if elapsed > period {
// 4
tick += 1
// 5
if tick >= 12 {
tick = 0
}
// 6
setNeedsDisplay()
// 7
lastTime = currentTime
}
}
- 计算每转 1/12 圈(30 度)需要多少时间。
- 计算从上一次回调到现在过去了多少时间。
- 只有超过了 1/12 圈(30 度)需要的时间,我们才需要重新绘制。很显然,屏幕刷新一次的时间太短了,我们并不需要那么频繁地重绘图形。
- tick+1,将当前转到角度记录下来。
- 如果 tick 达到了满圈(360度),将角度归零,防止 tick 无限制累加下去,导致整数溢出。
- 重绘。这将导致 draw(_😃 方法被调用。
- 记录最新的 lastTime。
最终效果如图:
以上是关于5-旋转的小菊-旋转画布和定时器的主要内容,如果未能解决你的问题,请参考以下文章