当前位置: 首页 > article >正文

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

  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.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()
        }
    }
  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 = 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
        
    }
  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.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")
    }
  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 = .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


http://www.kler.cn/a/534021.html

相关文章:

  • RabbitMQ:python基础调用
  • 信标链的基本概念
  • 【技术追踪】DiffMIC:用于医学图像分类的双引导扩散网络(MICCAI-2024)
  • VMware Workstation Pro安装了Ubuntu 24.04实现与Windows10之间的复制粘贴
  • 【高级篇 / IPv6】(7.2) ❀ 05. 在60E上配置ADSL拨号宽带上网(IPv6) ❀ FortiGate 防火墙
  • 读写锁: ReentrantReadWriteLock
  • 2. 【.NET 8 实战--孢子记账--从单体到微服务--转向微服务】--什么是微服务--微服务概述与演变
  • 整理:熟悉MySQL的使用和运行原理,掌握索引、事务、锁等机制。了解存储引擎、读写分离、分库分表。
  • QT笔记——多语言翻译
  • 传感器——针孔相机模型
  • java开发面试自我介绍模板_java面试自我介绍3篇
  • 8-登录流程
  • kakailio官网推荐的安装流程ubuntu 22.04
  • 解决php8.3无法加载curl扩展
  • 【Kubernetes Pod间通信-第2篇】使用BGP实现Pod到Pod的通信
  • 【R语言】数据操作
  • trinitycore服务器离线,原来是mysql里数据库flag设置为2离线状态了
  • 安卓系统源码如何导入原生androidx资源文件?
  • 说一下JVM管理的常见参数
  • 怀旧经典:1200+款红白机游戏合集,Windows版一键畅玩
  • 【LeetCode 刷题】贪心算法(2)-进阶
  • LLM框架对比选择:MaxKB、Dify、FastGPT、RagFlow【RAG+AI工作流+Agent]
  • uniapp商城之用户模块【会员中心】
  • 老游戏回顾:G2
  • openwebui入门
  • 数字人|通过语音和图片来创建高质量的视频