iOS文字滚动:使用CATextLayer实现的跑马灯(附源码)
引言
在 iOS 开发中,跑马灯效果(Marquee Effect)是一种常见的文本滚动效果,广泛应用于广告展示、动态消息栏、通知推送等场景。通过跑马灯效果,我们能够以流畅的方式展示超出屏幕范围的文本,提升用户体验。
通常,在 iOS 中实现跑马灯效果,我们可能会想到UILabel。然而,虽然UILabel提供了丰富的文本样式支持,它在动画和性能方面却有一定局限性。特别是在需要自定义动画效果和处理高性能的场景中,UILabel并不是最理想的选择。
此时,CATextLayer就成为了一个更灵活的替代方案。CATextLayer是一个低级的图层类,继承自CALayer,它专注于文本渲染,并且可以与 Core Animation 配合,实现高效且平滑的动画效果。相比于UILabel,CATextLayer更加轻量,且能更精确地控制动画和渲染性能,因此非常适合用于跑马灯这类需要高效动画渲染的场景。
本文将通过一个实际的例子,介绍如何使用CATextLayer实现一个简洁、流畅的跑马灯效果,帮助你在 iOS 项目中灵活运用这一技术。
CATextLayer基础
CATextLayer是Core Animation框架中的一种特殊图层(CALayer的子类),专门用于渲染文本。它与UILabel的最大区别在于,它并不直接处理用户交互和文本样式的布局,而是通过图层的方式,专注于高效的文本渲染和动画效果。
在Core Animation中,CATextLayer被设计为一个性能高效的图层,能够承载大量的文本内容并进行平滑的动画。与UILabel类不同,CATextLayer主要用于显示文本,而不涉及复杂的布局计算和视图管理,因此它非常适合用于需要高效渲染的场景,比如动画、动态图文等。
CATextLayer的主要特点:
- 专注于文本渲染:CATextLayer不像UILabel那样支持复杂的用户交互和动态布局,它主要负责在屏幕上渲染文本,减少了多余的开销。
- 高性能的文本绘制:由于其直接依赖于Core Animation,CATextLayer在渲染性能上笔UILabel更加优秀,特别是在处理大量文本和需要高帧率动画时,能够提供更平滑的效果。
- 支持基本的文本属性:CATextLayer支持字体、大小、颜色、对齐方式等基本的文本属性,但它并不完全支持 NSAtrributedString中的所有样式。这使得它非常适合需要简洁文本样式和高效动画的场景。
- 与Core Animation配合使用:CATextLayer天生与Core Animation配合,能够与其他图层(例如CALayer)进行协调,创建复杂的动画效果,如滚动、变换、透明度变化等。利用CATextLayer,可以实现诸如跑马灯、动态通知等动画效果。
- 不直接管理布局:与UILabel不同CATextLayer不负责复杂的自动布局或响应交互,它仅在指定的区域内渲染文本,适合用于自定义动画和轻量级显示。
通过CATextLayer,开发者可以更加灵活地控制文本的渲染和动画,尤其在需要高效且流畅显示大段文本的场景中,CATextLayer 提供了比 UILabel 更加高效的方案。
实现步骤
为了让我们自定义的跑马灯文字组件使用起来和普通的UILabel一样,可以直接继承自UILabel来构建自定义的文字自动滚动组件。在自定义的组件内部创建一个CATextLayer图层用来承载文字和执行动画。
准备工作
为了实现滚动效果,首先我们需要当前文本展示完成后所需的组件宽度和滚动速度,然后根据宽度再来创建特殊的文字图层CATextLayer以及执行滚动动画。
/// 文字图层
private var marqueeLayer: CATextLayer?
/// 速度
private var speed: CGFloat = 50.0
private func setupTextLayer() {
self.layer.masksToBounds = true
// 清理图层
clearLayer()
// 计算文字宽度,优先考虑富文本
var attributedText = self.attributedText
if attributedText == nil {
attributedText = NSAttributedString(string: text ?? "", attributes: [.font: font, .foregroundColor: textColor])
}
let textWidth = attributedText?.boundingRect(with: CGSize(width: CGFloat(MAXFLOAT), height: frame.height), options: .usesLineFragmentOrigin, context: nil).width ?? 0
guard let attributedText = attributedText else {
return
}
// 添加文字图层
addMarqueeTextLayer(attributedString: attributedText, width: textWidth)
if textWidth <= self.bounds.width {
return
}
// 添加动画
addMarqueeAnimation(width: textWidth, textLayer: marqueeLayer!)
}
override func layoutSubviews() {
super.layoutSubviews()
if self.bounds.size.width > 0 {
setupTextLayer()
}
}
- 声明了一个CATextLayer特殊图层。
- 定义了滚动的速度。
- setupTextLayer()方法中计算文本的宽度,来决定整个文字图层是否需要滚动。
- 为了适应约束布局,重写layoutSubviews()方法,并在组件已经有了宽度的时候重新执行setupTextLayer()方法。
创建CATextLayer
当获取到文本内容,计算出文本宽度之后就可以根据内容和宽度来创建CATextLayer添加到UILabel的图层之上并设置布局。
/// 添加文字图层
/// - Parameters:
/// - attributedString: 富文本
/// - width: 文字宽度
private func addMarqueeTextLayer(attributedString:NSAttributedString,width:CGFloat) {
let textLayer = CATextLayer()
textLayer.string = attributedString
textLayer.contentsScale = UIScreen.main.scale
textLayer.alignmentMode = .left
textLayer.frame = CGRect(x: 0, y: 0, width: width, height: frame.height)
layer.addSublayer(textLayer)
self.marqueeLayer = textLayer
print("string:\(attributedString)")
textLayer.backgroundColor = UIColor.red.cgColor
}
- 创建图层并设置图层的文字内容。
- 设置contentsScale这一步十分关键,否则在Retina会出现模糊。
- 设置frame,x和y分别从0开始,宽度为文字总宽度,高度为组件高度。
- 设置UILabel的原本颜色为透明色,目的是隐藏UILabel原来的显示内容。
添加动画
CATextLayer创建完成之后,我们只需要让它的最左端从组件的最右端开始执行动画,直到CATextLayer图层的最右端与组件的最左端对齐为止,为此我们需要计算起始位置和结束位置。
/// 添加图层动画
/// - Parameters:
/// - width: 文字宽度
/// - textLayer: 文字图层
private func addMarqueeAnimation(width: CGFloat, textLayer: CATextLayer) {
//添加动画
let startOffset = bounds.width + width / 2.0
let endOffset = -width/2.0
let duration = Double(width) / Double(speed)
let animation = CAKeyframeAnimation(keyPath: "position.x")
animation.values = [startOffset, endOffset]
animation.keyTimes = [0, 1]
animation.duration = duration
animation.repeatCount = .infinity
animation.isRemovedOnCompletion = false
textLayer.add(animation, forKey: "marqueeScroll")
}
- 我们针对图层的position.x执行关键帧动画,那么它的起点位置应该是组件宽度+文字宽度/2。
- 而结束为止应该是负的文字宽度。
- 根据速度计算动画时长。
- 设置重复次数无限大,让动画一直循环。
清理图层
最后还有一个重要的方法,清理特殊图层。
/// 清理图层
private func clearLayer() {
self.marqueeLayer?.removeAllAnimations()
self.marqueeLayer?.removeFromSuperlayer()
self.marqueeLayer = nil
}
使用示例
在使用时,我们只需要像普通UILabel一样使用,支持绝对布局和相对布局。
/// 添加跑马灯文字
private func addMarqueeLabel() {
let marqueeLabel = PHMarqueeLabel(frame: CGRect(x: 100.0, y: 100.0, width: 200.0, height: 40.0))
marqueeLabel.text = "今天是一个好天气,适合出去玩耍"
marqueeLabel.textColor = .white
marqueeLabel.font = UIFont.systemFont(ofSize: 20)
marqueeLabel.backgroundColor = .orange
view.addSubview(marqueeLabel)
}
/// 添加跑马灯文字
private func addMarqueeLabel() {
let marqueeLabel = PHMarqueeLabel(frame: .zero)
let text = "今天是一个好天气,适合出去玩耍"
let attributedText = NSMutableAttributedString(string: text)
attributedText.addAttributes([.foregroundColor: UIColor.blue], range: NSRange(location: 0, length: 2))
attributedText.addAttributes([.foregroundColor: UIColor.green], range: NSRange(location: 2, length: 2))
marqueeLabel.attributedText = attributedText
marqueeLabel.font = UIFont.systemFont(ofSize: 20)
marqueeLabel.backgroundColor = .orange
view.addSubview(marqueeLabel)
marqueeLabel.snp.makeConstraints { make in
make.top.equalTo(100.0)
make.leading.trailing.equalToSuperview().inset(100.0)
}
}
效果如下:
结语
通过本文,我们探讨了如何使用 CATextLayer 实现一个高效流畅的跑马灯效果。相比于 UILabel,CATextLayer在渲染性能上更具优势,特别是在需要动态更新和动画效果时,它能够提供更加平滑的用户体验。尽管 CATextLayer 支持的文本样式有限,但对于一些简单的文本显示需求,尤其是高效动画渲染,它无疑是一个理想的选择。
在实际开发中,使用 CATextLayer 实现跑马灯效果,能够帮助我们节省性能开销,减少无谓的视图层级,同时通过 Core Animation 提供流畅的视觉体验。无论是在广告轮播、消息通知还是动态标签的场景中,CATextLayer 都能够发挥出色的表现。
当然,CATextLayer 也并非万能,对于需要高度自定义富文本样式的场景,我们仍然可以结合 UILabel 或其他控件来实现更复杂的效果。但对于大多数简洁、流畅的滚动效果,CATextLayer 是一个值得推荐的解决方案。
希望通过本文的介绍,能够帮助你在项目中更好地使用 CATextLayer来实现跑马灯效果,提升动画表现与用户体验。
https://download.csdn.net/download/weixin_39339407/90341158https://download.csdn.net/download/weixin_39339407/90341158