使用CADisplayLink实现UILabel动画特效
在開發(fā)時(shí),我們有時(shí)候會遇到需要定時(shí)對UIView進(jìn)行重繪的需求,進(jìn)而讓view產(chǎn)生不同的動畫效果。
本文項(xiàng)目
效果圖
初探 CADisplayLink
定時(shí)對View進(jìn)行定時(shí)重繪可能會第一時(shí)間想到使用NSTimer,但是這樣的動畫實(shí)現(xiàn)起來是不流暢的,因?yàn)樵趖imer所處的runloop中要處理多種不同的輸入,導(dǎo)致timer的最小周期是在50到100毫秒之間,一秒鐘之內(nèi)最多只能跑20次左右。
但如果我們希望在屏幕上看到流暢的動畫,我們就要維持60幀的刷新頻率,也就意味著每一幀的間隔要在0.016秒左右,NSTimer是無法實(shí)現(xiàn)的。所以要用到Core Animation的另一個timer,CADisplayLink。
在CADisplayLink的頭文件中,我們可以看到它的使用方法跟NSTimer是十分類似的,其同樣也是需要注冊到RunLoop中,但不同于NSTimer的是,它在屏幕需要進(jìn)行重繪時(shí)就會讓RunLoop調(diào)用CADisplayLink指定的selector,用于準(zhǔn)備下一幀顯示的數(shù)據(jù)。而NSTimer是需要在上一次RunLoop整個完成之后才會調(diào)用制定的selector,所以在調(diào)用頻率與上比NSTimer要頻繁得多。
另外和NSTimer不同的是,NSTimer可以指定timeInterval,對應(yīng)的是selector調(diào)用的間隔,但如果NSTimer觸發(fā)的時(shí)間到了,而RunLoop處于阻塞狀態(tài),其觸發(fā)時(shí)間就會推遲到下一個RunLoop。而CADisplayLink的timer間隔是不能調(diào)整的,固定就是一秒鐘發(fā)生60次,不過可以通過設(shè)置其frameInterval屬性,設(shè)置調(diào)用一次selector之間的間隔幀數(shù)。另外需要注意的是如果selector執(zhí)行的代碼超過了frameInterval的持續(xù)時(shí)間,那么CADisplayLink就會直接忽略這一幀,在下一次的更新時(shí)候再接著運(yùn)行。
配置 RunLoop
在創(chuàng)建CADisplayLink的時(shí)候,我們需要指定一個RunLoop和RunLoopMode,通常RunLoop我們都是選擇使用主線程的RunLoop,因?yàn)樗蠻I更新的操作都必須放到主線程來完成,而在模式的選擇就可以用NSDefaultRunLoopMode,但是不能保證動畫平滑的運(yùn)行,所以就可以用NSRunLoopCommonModes來替代。但是要小心,因?yàn)槿绻麆赢嬙谝粋€高幀率情況下運(yùn)行,會導(dǎo)致一些別的類似于定時(shí)器的任務(wù)或者類似于滑動的其他iOS動畫會暫停,直到動畫結(jié)束。
private func setup() {_displayLink = CADisplayLink(target: self, selector: #selector(update))_displayLink?.isPaused = true_displayLink?.add(to: RunLoop.main, forMode: .commonModes) }復(fù)制代碼實(shí)現(xiàn)不同的字符變換動畫
在成功建立CADisplayLink計(jì)時(shí)器后,就可以著手對字符串進(jìn)行各類動畫操作了。在這里我們會使用NSAttributedString來實(shí)現(xiàn)效果
在setupAnimatedText(from labelText: String?)這個方法中,我們需要使用到兩個數(shù)組,一個是durationArray,一個是delayArray,通過配置這兩個數(shù)組中的數(shù)值,我們可以實(shí)現(xiàn)對字符串中各個字符的出現(xiàn)時(shí)間和出現(xiàn)時(shí)長的控制。
打字機(jī)效果的配置
- 每個字符出現(xiàn)所需時(shí)間相同
- 下一個字符等待上一個字符出現(xiàn)完成后再出現(xiàn)
- 通過修改NSAttributedStringKey.baselineOffset調(diào)整字符位置
閃爍效果的配置
- 每個字符出現(xiàn)所需時(shí)間隨機(jī)
- 確保所有字符能夠在duration內(nèi)均完成出現(xiàn)
- 修改NSAttributedStringKey.foregroundColor的透明度來實(shí)現(xiàn)字符的出現(xiàn)效果
漸現(xiàn)效果的配置
- 每個字符出現(xiàn)所需時(shí)間漸減
- 修改NSAttributedStringKey.foregroundColor的透明度來實(shí)現(xiàn)字符的出現(xiàn)效果
完善每一幀的字符串更新效果
接下來就需要完善剛才在CADisplayLink中配置的update方法了,在這個方法中我們會根據(jù)我們剛才配置的兩個數(shù)組中的相關(guān)數(shù)據(jù)對字符串進(jìn)行變換。
核心代碼
- 通過開始時(shí)間與當(dāng)前時(shí)間獲取動畫進(jìn)度
- 根據(jù)字符位置對應(yīng)duationArray與delayArray中的數(shù)據(jù)
- 根據(jù)durationArray與delayArray中的數(shù)據(jù)計(jì)算當(dāng)前字符的顯示進(jìn)度
隨后便可以將處理完的NSAttributedString返回給label進(jìn)行更新
番外:利用正弦函數(shù)實(shí)現(xiàn)波紋進(jìn)度
波紋路徑
首先介紹一下正弦函數(shù):y = A * sin(ax + b)
- 在 x 軸方向平移 b 個單位(左加右減)
- 橫坐標(biāo)伸長(0 < a < 1)或者縮短(a > 1) 1/a 倍
- 縱坐標(biāo)伸長(A > 1)或者縮短(0 < A < 1)A 倍
在簡單了解了這些知識后,我們回到wavePath()方法中,在這個方法我們使用正弦函數(shù)來繪制一段UIBezierPath:
let originY = (label.bounds.size.height + label.font.lineHeight) / 2 let path = UIBezierPath() path.move(to: CGPoint(x: 0, y: _waveHeight!)) var yPosition = 0.0 for xPosition in 0..<Int(label.bounds.size.width) {yPosition = _zoom! * sin(Double(xPosition) / 180.0 * Double.pi - 4 * _translate! / Double.pi) * 5 + _waveHeight!path.addLine(to: CGPoint(x: Double(xPosition), y: yPosition)) } path.addLine(to: CGPoint(x: label.bounds.size.width, y: originY)) path.addLine(to: CGPoint(x: 0, y: originY)) path.addLine(to: CGPoint(x: 0, y: _waveHeight!)) path.close() 復(fù)制代碼波紋高度與動畫的更新
- 隨著進(jìn)度高度不斷升高
- 隨著進(jìn)度波紋不斷波動
在CADisplayLink注冊的update的方法中,我們對承載了波紋路徑的Layer進(jìn)行更新
_waveHeight! -= duration / Double(label!.font.lineHeight) _translate! += 0.1 if !_reverse {_zoom! += 0.02if _zoom! >= 1.2 {_reverse = true} } else {_zoom! -= 0.02if _zoom! <= 1.0 {_reverse = false} } shapeLayer.path = wavePath() 復(fù)制代碼結(jié)語
以上就是我對CADisplayLink的一些運(yùn)用,其實(shí)它的使用方法還有很多,可以利用它實(shí)現(xiàn)更多更復(fù)雜而精美的動畫,同時(shí)希望各位如果有更好的改進(jìn)也能與我分享。
如果你喜歡這個項(xiàng)目,歡迎到GitHub上給我一個star。
參考
- RQShineLabel
- Apple Developer Document - CADisplayLink
- iOS核心動畫高級技巧
總結(jié)
以上是生活随笔為你收集整理的使用CADisplayLink实现UILabel动画特效的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: POJ - 2187 Beauty Co
- 下一篇: 怎样用原生js配合css的transit