iOS文字滚动:使用CATextLayer实现的跑马灯(附源码)

news/2025/2/12 13:14:38/

引言

在 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的主要特点:

  1. 专注于文本渲染:CATextLayer不像UILabel那样支持复杂的用户交互和动态布局,它主要负责在屏幕上渲染文本,减少了多余的开销。
  2. 高性能的文本绘制:由于其直接依赖于Core Animation,CATextLayer在渲染性能上笔UILabel更加优秀,特别是在处理大量文本和需要高帧率动画时,能够提供更平滑的效果。
  3. 支持基本的文本属性:CATextLayer支持字体、大小、颜色、对齐方式等基本的文本属性,但它并不完全支持 NSAtrributedString中的所有样式。这使得它非常适合需要简洁文本样式和高效动画的场景。
  4. 与Core Animation配合使用:CATextLayer天生与Core Animation配合,能够与其他图层(例如CALayer)进行协调,创建复杂的动画效果,如滚动、变换、透明度变化等。利用CATextLayer,可以实现诸如跑马灯、动态通知等动画效果。
  5. 不直接管理布局:与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.attributedTextif 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 ?? 0guard 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()}}
  1. 声明了一个CATextLayer特殊图层。
  2. 定义了滚动的速度。
  3. setupTextLayer()方法中计算文本的宽度,来决定整个文字图层是否需要滚动。
  4. 为了适应约束布局,重写layoutSubviews()方法,并在组件已经有了宽度的时候重新执行setupTextLayer()方法。

创建CATextLayer

当获取到文本内容,计算出文本宽度之后就可以根据内容和宽度来创建CATextLayer添加到UILabel的图层之上并设置布局。

    /// 添加文字图层/// - Parameters:///  - attributedString: 富文本///  - width: 文字宽度private func addMarqueeTextLayer(attributedString:NSAttributedString,width:CGFloat) {let textLayer = CATextLayer()textLayer.string = attributedStringtextLayer.contentsScale = UIScreen.main.scaletextLayer.alignmentMode = .lefttextLayer.frame = CGRect(x: 0, y: 0, width: width, height: frame.height)layer.addSublayer(textLayer)self.marqueeLayer = textLayerprint("string:\(attributedString)")textLayer.backgroundColor = UIColor.red.cgColor}
  1. 创建图层并设置图层的文字内容。
  2. 设置contentsScale这一步十分关键,否则在Retina会出现模糊。
  3. 设置frame,x和y分别从0开始,宽度为文字总宽度,高度为组件高度。
  4. 设置UILabel的原本颜色为透明色,目的是隐藏UILabel原来的显示内容。

添加动画

CATextLayer创建完成之后,我们只需要让它的最左端从组件的最右端开始执行动画,直到CATextLayer图层的最右端与组件的最左端对齐为止,为此我们需要计算起始位置和结束位置。

    /// 添加图层动画/// - Parameters:/// - width: 文字宽度/// - textLayer: 文字图层private func addMarqueeAnimation(width: CGFloat, textLayer: CATextLayer) {//添加动画let startOffset = bounds.width + width / 2.0let endOffset = -width/2.0let duration = Double(width) / Double(speed)let animation = CAKeyframeAnimation(keyPath: "position.x")animation.values = [startOffset, endOffset]animation.keyTimes = [0, 1]animation.duration = durationanimation.repeatCount = .infinityanimation.isRemovedOnCompletion = falsetextLayer.add(animation, forKey: "marqueeScroll")}
  1. 我们针对图层的position.x执行关键帧动画,那么它的起点位置应该是组件宽度+文字宽度/2。
  2. 而结束为止应该是负的文字宽度。
  3. 根据速度计算动画时长。
  4. 设置重复次数无限大,让动画一直循环。

清理图层

最后还有一个重要的方法,清理特殊图层。

    /// 清理图层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 = .whitemarqueeLabel.font = UIFont.systemFont(ofSize: 20)marqueeLabel.backgroundColor = .orangeview.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 = attributedTextmarqueeLabel.font = UIFont.systemFont(ofSize: 20)marqueeLabel.backgroundColor = .orangeview.addSubview(marqueeLabel)marqueeLabel.snp.makeConstraints { make inmake.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


http://www.ppmy.cn/news/1570721.html

相关文章

Golang Gin框架mqtt消费者

这里主要是展示一个非常简单的Gin框架下的mqtt消费者&#xff0c;在保持启动后持续轮训消费。简单实用是主旨。 viper全局调用yaml文件中的数据 redisClient 缓存客户端 doJYService 业务逻辑代码 package mainimport ("fmt""github.com/gin-gonic/gin"&…

运用Deek Seeker协助数据分析

我的数据源有两张表&#xff0c;一个是每日销售表(字段有日期、产品名称、实际销量)&#xff0c;一个是每月目标表(字段有年度月份、产品名称、目标销量);我的需求是&#xff0c;按月、按年来统计每个产品的目标完成情况请问用PowerBl进行分析&#xff0c;应该如何建立数据模型…

分巧克力

儿童节那天有 K位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友们。小明一共有 N块巧克力&#xff0c;其中第i块是HixWi的方格组成的长方形。为了公平起见&#xff0c;小明需要从这 N块巧克力中切出 K块巧克力分给小朋友们。切出的巧克力需要满足 1.形状是正方形&…

【C语言】指针详细解读3

1. 数组名的理解 我们使用指针一般访问数组内容时&#xff0c;我们可能会这样写&#xff1a; int arr[10] {1,2,3,4,5,6,7,8,9,10}; int *p &arr[0]; 这⾥我们使⽤ &arr[0] 的⽅式拿到了数组第⼀个元素的地址&#xff0c;但是其实数组名本来就是地址&#xff0c;⽽…

学习笔记:机器学习中的数学原理(一)

1. 集合 集合分为有限集和无限集&#xff1b; 对于有限集&#xff0c;两集合元素数相等即为等势&#xff1b; 对于无限集&#xff0c;两集合元素存在一一映射关系即为等势&#xff1b; 无限集根据是否与正整数集等势分为可数集和不可数集。 2. sigmoid函数&#xff08;也叫…

使用线性回归模型逼近目标模型 | PyTorch 深度学习实战

前一篇文章&#xff0c;计算图 Compute Graph 和自动求导 Autograd | PyTorch 深度学习实战 本系列文章 GitHub Repo: https://github.com/hailiang-wang/pytorch-get-started 使用线性回归模型逼近目标模型 什么是回归什么是线性回归使用 PyTorch 实现线性回归模型代码执行结…

[手机Linux] onepluse6T 系统重新分区

一&#xff0c;刷入TWRP 1. 电脑下载 Fastboot 工具&#xff08;解压备用&#xff09;和对应机型 TWRP&#xff08;.img 后缀文件&#xff0c;将其放入前面解压的文件夹里&#xff09; 或者直接这里下载:TWRP 2. 将手机关机&#xff0c;长按音量上和下键 开机键 进入 fastbo…

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_get_options函数

声明 就在 main函数所在的 nginx.c 中&#xff1a; static ngx_int_t ngx_get_options(int argc, char *const *argv); 实现 static ngx_int_t ngx_get_options(int argc, char *const *argv) {u_char *p;ngx_int_t i;for (i 1; i < argc; i) {p (u_char *) argv[i]…