专栏名称: 杜玮
iOS开发工程师
目录
相关文章推荐
鲁中晨报  ·  奥运冠军武大靖,有新身份! ·  6 小时前  
鲁中晨报  ·  大反转!突然曝光:竟然是假的! ·  6 小时前  
德州晚报  ·  这项补贴,德州今年继续! ·  18 小时前  
鲁中晨报  ·  再次道歉!全额退款 ·  昨天  
山东省交通运输厅  ·  潍宿高铁郯城沂河特大桥建设迎来新年“开门红” ·  2 天前  
51好读  ›  专栏  ›  杜玮

使用CADisplayLink实现UILabel动画特效

杜玮  · 掘金  ·  · 2018-07-23 08:38

正文

阅读 3

使用CADisplayLink实现UILabel动画特效

在开发时,我们有时候会遇到需要定时对UIView进行重绘的需求,进而让view产生不同的动画效果。

本文 项目

效果图

typewritter

shine

fade

wave

初探 CADisplayLink

定时对View进行定时重绘可能会第一时间想到使用 NSTimer ,但是这样的动画实现起来是 不流畅 的,因为在timer所处的 runloop 中要处理多种不同的输入,导致timer的最小周期是在50到100毫秒之间,一秒钟之内最多只能跑20次左右。

但如果我们希望在屏幕上看到流畅的动画,我们就要维持60帧的刷新频率,也就意味着每一帧的间隔要在 0.016 秒左右, NSTimer 是无法实现的。所以要用到 Core Animation 的另一个timer, CADisplayLink

CADisplayLink 的头文件中,我们可以看到它的使用方法跟 NSTimer 是十分类似的,其同样也是需要注册到RunLoop中,但不同于 NSTimer 的是,它在 屏幕需要进行重绘时 就会让RunLoop调用 CADisplayLink 指定的selector,用于准备下一帧显示的数据。而 NSTimer 是需要在 上一次RunLoop整个完成之后 才会调用制定的selector,所以在调用频率与上比 NSTimer 要频繁得多。

另外和 NSTimer 不同的是, NSTimer 可以指定 timeInterval ,对应的是selector调用的间隔,但如果 NSTimer 触发的时间到了,而RunLoop处于阻塞状态,其触发时间就会 推迟 到下一个RunLoop。而 CADisplayLink 的timer间隔是 不能调整 的,固定就是一秒钟发生 60 次,不过可以通过设置其 frameInterval 属性,设置调用一次selector之间的 间隔帧数 。另外需要注意的是如果selector执行的代码超过了 frameInterval 的持续时间,那么 CADisplayLink 就会 直接忽略 这一帧,在下一次的更新时候再接着运行。

配置 RunLoop

在创建CADisplayLink的时候,我们需要指定一个RunLoop和 RunLoopMode ,通常RunLoop我们都是选择使用主线程的RunLoop,因为所有UI更新的操作都必须放到主线程来完成,而在模式的选择就可以用 NSDefaultRunLoopMode ,但是不能保证动画平滑的运行,所以就可以用 NSRunLoopCommonModes 来替代。但是要小心,因为如果动画在一个 高帧率 情况下运行,会导致一些别的类似于定时器的任务或者类似于滑动的其他iOS动画会暂停,直到动画结束。

private func setup() {
	_displayLink = CADisplayLink(target: self, selector: #selector(update))
	_displayLink?.isPaused = true
	_displayLink?.add(to: RunLoop.main, forMode: .commonModes)
}

复制代码

实现不同的字符变换动画

在成功建立 CADisplayLink 计时器后,就可以着手对字符串进行各类动画操作了。在这里我们会使用 NSAttributedString 来实现效果

setupAnimatedText(from labelText: String?) 这个方法中,我们需要使用到两个数组,一个是 durationArray ,一个是 delayArray ,通过配置这两个数组中的数值,我们可以实现对字符串中各个字符的 出现时间 出现时长 的控制。

打字机效果的配置

  • 每个字符出现所需时间相同
  • 下一个字符等待上一个字符出现完成后再出现
  • 通过修改 NSAttributedStringKey.baselineOffset 调整 字符位置
case .typewriter:
	attributedString.addAttribute(.baselineOffset, value: -label.font.lineHeight, range: NSRange(location: 0, length: attributedString.length))
	let displayInterval = duration / TimeInterval(attributedString.length)
	for index in 0..<attributedString.length {
		durationArray.append(displayInterval)
		delayArray.append(TimeInterval(index) * displayInterval)
	}

复制代码

闪烁效果的配置

  • 每个字符出现所需时间随机
  • 确保所有字符能够在 duration 内均完成出现
  • 修改 NSAttributedStringKey.foregroundColor 透明度 来实现字符的出现效果
case .shine:
	attributedString.addAttribute(.foregroundColor, value: label.textColor.withAlphaComponent(0), range: NSRange(location: 0, length: attributedString.length))
	for index in 0..<attributedString.length {
		delayArray.append(TimeInterval(arc4random_uniform(UInt32(duration) / 2 * 100) / 100))
		let remain = duration - Double(delayArray[index])
		durationArray.append(TimeInterval(arc4random_uniform(UInt32(remain) * 100) / 100))
	}
复制代码

渐现效果的配置

  • 每个字符出现所需时间渐减
  • 修改 NSAttributedStringKey.foregroundColor 透明度 来实现字符的出现效果
case .fade:
	attributedString.addAttribute(.foregroundColor, value: label.textColor.withAlphaComponent(0), range: NSRange(location: 0, length: attributedString.length))
	let displayInterval = duration / TimeInterval(attributedString.length)
	for index in 0..<attributedString.length  {
		delayArray.append(TimeInterval(index) * displayInterval)
		durationArray.append(duration - delayArray[index])
	}
复制代码

完善每一帧的字符串更新效果

接下来就需要完善刚才在 CADisplayLink 中配置的 update 方法了,在这个方法中我们会根据我们刚才配置的两个数组中的相关数据对字符串进行变换。

核心代码

  • 通过 开始时间 当前时间 获取 动画进度
  • 根据 字符位置 对应 duationArray delayArray 中的数据
  • 根据 durationArray delayArray 中的数据计算当前字符的 显示进度
var






请到「今天看啥」查看全文