iOS使用CADisplayLink詳解,NSMutableAttributedString來(lái)實(shí)現(xiàn)文字淡入淡出效果

什么是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í)間。

愿世界更美好。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容