什么是CADisplayLink?
蘋(píng)果官方文檔中對(duì)CADisplayLink的描述如下:
A CADisplayLink object is a timer object that allows your application to synchronize its drawing to the refresh rate of the display.
由此我們可以知道, CADisplaylink本質(zhì)上就是一個(gè)能讓我們,以和屏幕刷新率相同的頻率,將內(nèi)容畫(huà)到屏幕上的定時(shí)器。也就是說(shuō),系統(tǒng)在每次刷新屏幕時(shí)都會(huì)觸發(fā)CADisplayLink事件。
開(kāi)發(fā)過(guò)程中常用的類似定時(shí)器功能又三個(gè):GCD,Timer,CADisplayLink,此處不再擴(kuò)展,有興趣可以查看相關(guān)文章學(xué)習(xí)一下,或者留言討論。
CADisplayLink的基本使用
mkDisplayLink = CADisplayLink.init(target: self, selector: #selector(MKLabel.updateLabelDisplay))
mkDisplayLink?.add(to: .current, forMode: .commonModes)
mkDisplayLink?.isPaused = true
通過(guò)以上代碼可以知道,CADisplayLink的實(shí)現(xiàn)需要添加一個(gè)target和selector,target就是要響應(yīng)的實(shí)例對(duì)象,selector則是要響應(yīng)的具體方法。然后將CADisplayLink實(shí)例對(duì)象添加到Runloop中,這樣屏幕刷新時(shí)就會(huì)回調(diào)selector方法。你也可以通過(guò)isPaused等于true或false控制對(duì)selector的調(diào)用。另外CADisplayLink還有屬性duration、frameInterval 和 timestamp分別用來(lái)獲取當(dāng)前屏幕的刷新時(shí)間間隔,兩次調(diào)用selector之間屏幕刷新次數(shù)間隔,以及上一次執(zhí)行selector的時(shí)間戳。
如果不再需要CADisplayLink時(shí),可以通過(guò)invalidate進(jìn)行釋放(類似Timer)。
mkDisplayLink?.invalidate()
重點(diǎn)來(lái)了,如何利用CADisplayLink的這些特性,實(shí)現(xiàn)文字的動(dòng)態(tài)顯示效果呢?
先看效果

同時(shí)可以響應(yīng)點(diǎn)擊事件以及自定義高亮話題等功能

直接上代碼:
首先,對(duì)動(dòng)畫(huà)進(jìn)行一些基本數(shù)據(jù)的設(shè)置,開(kāi)始時(shí)間,結(jié)束時(shí)間,顯示的類型(庫(kù)支持的擴(kuò)展效果類型)等一些需求屬性
func beginFadeAnimation(isFadeIn fadeIn: Bool, displayType: MKLabelDisplayType = .normal, completionBlock: @escaping completionBlock) {
? ? ? ? self.displayType = displayType
? ? ? ? self.completion = completionBlock
? ? ? ? self.isFadeIn = fadeIn
? ? ? ? self.beginTime = CACurrentMediaTime()
? ? ? ? self.endTime = self.beginTime! + self.durationTime
? ? ? ? self.mkDisplayLink?.isPaused = false
? ? ? ? self.setAttributedTextString(NSAttributedString(string:self.text!))
? ? }
其次,根據(jù)設(shè)置的屬性計(jì)算出每個(gè)文字在動(dòng)畫(huà)過(guò)程中顯示的時(shí)間點(diǎn)以及顯示的時(shí)長(zhǎng)。
? ? func setAttributedTextString(_ attributedText: NSAttributedString) {
? ? ? ? self.attributedTextString = self.setAlphaAttributedTextString(attributedText) as? NSMutableAttributedString
? ? ? ? var delayTime = 0.0, animationDuration = 0.0
? ? ? ? let totalLength = attributedText.length
? ? ? ? if delayTimeArray.count > 0 {
? ? ? ? ? ? delayTimeArray.removeAll()
? ? ? ? ? ? animationTimeArray.removeAll()
? ? ? ? }
? ? ? ? for index in 0..<totalLength {
? ? ? ? ? ? switch self.displayType {
? ? ? ? ? ? case .normal:
? ? ? ? ? ? ? ? delayTime = Double(arc4random_uniform(UInt32(durationTime/2.0 * 100)))/100.0
? ? ? ? ? ? ? ? let animationTime = durationTime - delayTime
? ? ? ? ? ? ? ? animationDuration = (Double(arc4random_uniform(UInt32(animationTime * 100)))/100.0)
? ? ? ? ? ? case .ascend:
? ? ? ? ? ? ? ? delayTime = Double((durationTime * Double(index))/Double(totalLength))
? ? ? ? ? ? ? ? let animationTime = durationTime - delayTime
? ? ? ? ? ? ? ? animationDuration = animationTime
? ? ? ? ? ? ? ? if self.isFadeIn == false {
? ? ? ? ? ? ? ? ? ? if animationTime > delayTime{
? ? ? ? ? ? ? ? ? ? ? ? animationDuration = delayTime
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? case .descend:
? ? ? ? ? ? ? ? delayTime = Double((durationTime * Double(index))/Double(totalLength))
? ? ? ? ? ? ? ? let animationTime = durationTime - delayTime
? ? ? ? ? ? ? ? animationDuration = animationTime
? ? ? ? ? ? ? ? if self.isFadeIn == false {
? ? ? ? ? ? ? ? ? ? if animationTime > delayTime{
? ? ? ? ? ? ? ? ? ? ? ? animationDuration = delayTime
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? case .middle:
? ? ? ? ? ? ? ? delayTime = Double((durationTime * Double(index))/Double(totalLength))
? ? ? ? ? ? ? ? let animationTime = durationTime - delayTime
? ? ? ? ? ? ? ? animationDuration = animationTime
? ? ? ? ? ? ? ? if self.isFadeIn == false {
? ? ? ? ? ? ? ? ? ? if animationTime > delayTime{
? ? ? ? ? ? ? ? ? ? ? ? animationDuration = delayTime
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? default:
? ? ? ? ? ? ? ? delayTime = Double(arc4random_uniform(UInt32(durationTime/2.0 * 100)))/100.0
? ? ? ? ? ? ? ? let animationTime = durationTime - delayTime
? ? ? ? ? ? ? ? animationDuration = (Double(arc4random_uniform(UInt32(animationTime * 100)))/100.0)
? ? ? ? ? ? }
? ? ? ? ? ? delayTimeArray.append(delayTime)
? ? ? ? ? ? animationTimeArray.append(animationDuration)
? ? ? ? ? ? if self.displayType == .middle{
? ? ? ? ? ? ? ? delayTimeArray.reverse()
? ? ? ? ? ? ? ? animationTimeArray.reverse()
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? if self.displayType == .descend{
? ? ? ? ? ? delayTimeArray.reverse()
? ? ? ? ? ? animationTimeArray.reverse()
? ? ? ? }
? ? }
最后,在屏幕每次刷新,selector方法調(diào)用時(shí),計(jì)算每個(gè)文字的顯示情況以及透明度,注意透明度是根據(jù)當(dāng)前時(shí)間與動(dòng)畫(huà)開(kāi)始時(shí)間,動(dòng)畫(huà)周期,文字的動(dòng)畫(huà)顯示時(shí)長(zhǎng)綜合關(guān)聯(lián)計(jì)算出來(lái)的,此處也是該效果的實(shí)現(xiàn)核心。
? ?func updateLabelDisplay(displayLink: CADisplayLink) -> Void {
? ? ? ? guard self.attributedTextString != nil else {
? ? ? ? ? ? return
? ? ? ? }
? ? ? ? guard self.endTime != nil else {
? ? ? ? ? ? return
? ? ? ? }
? ? ? ? let nowTime = CACurrentMediaTime()
? ? ? ? let attributedLength = self.attributedTextString?.length
? ? ????for index in 0..<attributedLength! {
? ? ? ? ? ? self.attributedTextString?.enumerateAttributes(in: NSMakeRange(index, 1), options: NSAttributedString.EnumerationOptions.longestEffectiveRangeNotRequired, using: {(value, range, error) -> Void in
? ? ? ? ? ? ? ? let colorA = value[NSAttributedStringKey.foregroundColor] as! UIColor
? ? ? ? ? ? ? ? let colorAlpha = colorA.cgColor.alpha
? ? ? ? ? ? ? ? let isNeedUpdate = (nowTime - self.beginTime!) > self.delayTimeArray[index] || (isFadeIn && colorAlpha < 1.0) || (!isFadeIn && colorAlpha > 0.0)
? ? ? ? ? ? ? ? if isNeedUpdate == false {
? ? ? ? ? ? ? ? ? ? return
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? var percentage = (nowTime - self.beginTime! - self.delayTimeArray[index])/self.animationTimeArray[index]
? ? ? ? ? ? ? ? if !self.isFadeIn {
? ? ? ? ? ? ? ? ? ? percentage = 1 - percentage
? ? ? ? ? ? ? ? }
//? ? ? ? ? ? ? ? let color = self.textColor.withAlphaComponent(CGFloat(percentage))
? ? ? ? ? ? ? ? let color = colorA.withAlphaComponent(CGFloat(percentage))
? ? ? ? ? ? ? ? self.attributedTextString?.addAttribute(NSAttributedStringKey.foregroundColor, value: color, range: range)
? ? ? ? ? ? })
? ? ? ? }
? ? ? ? super.attributedText = self.attributedTextString
? ? ? ? if nowTime > self.endTime! {
? ? ? ? ? ? self.mkDisplayLink?.isPaused = true
? ? ? ? ? ? guard self.completion != nil else {
? ? ? ? ? ? ? ? return
? ? ? ? ? ? }
? ? ? ? ? ? self.completion!(true, self.text!)
? ? ? ? }
? ? }
此處僅寫(xiě)出了三個(gè)比較核心的函數(shù),來(lái)講解具體實(shí)現(xiàn)的步驟,因?yàn)槲业拈_(kāi)源庫(kù)實(shí)現(xiàn)的功能比較多,此處不便一一貼出代碼詳解,有興趣的同學(xué)可以去我的Github上下載一下源碼查看https://github.com/minhechen/MKLabel歡迎討論。
最后,推廣時(shí)間,求關(guān)注,求star,謝謝你的時(shí)間。
愿世界更美好。