引言跑马灯Marquee是移动应用中一种常见的信息展示模式——文字在有限的空间内水平滚动用于展示公告、快讯、通知等需要用户关注但又不占用太多屏幕空间的内容。从证券交易软件的行情滚动条到新闻客户端的头条快讯从直播平台的弹幕滚动到智能家居面板的状态轮播跑马灯效果无处不在。然而在 HarmonyOS NEXTAPI 24中早期版本存在的.marquee()属性方法已被移除系统不再提供开箱即用的跑马灯 API。这意味着开发者需要自行实现这一效果。本文将拆解跑马灯的技术原理使用animateTo显式动画与onFinish回调构建一套完整的自定义跑马灯系统并在此基础上实现暂停/恢复、速度调节和内容切换等交互功能。读完本文你将能够理解跑马灯效果的底层实现原理掌握animateToonFinish回调实现循环动画的技巧学会控制动画生命周期启动、暂停、恢复、销毁了解如何构建可调节速度的动画系统获得一个生产可用的自定义跑马灯方案跑马灯的技术原理核心思路跑马灯的本质是将一段文本在水平方向上匀速平移当文本完全移出可视区域后从起点重新开始循环。从动画的角度看这是一个平移 → 复位 → 平移的无限循环帧 1: [重要公告今晚22:00-24:00将进行服务器...] ← 起点文本从右侧开始 帧 30: [重要公告今晚22:00-24:00将进行服务...] ← 匀速左移 帧 60: [重要公告今晚22:00-24:...] ← 继续左移 帧 90: [重要公告今晚...] ← 即将移出 帧 91: [重要公告今晚22:00-24:00将进行服务器...] ← 复位到起点重新开始为什么不用系统 Marquee在 HarmonyOS 的早期版本中Text组件提供了.marquee()属性方法开发者只需配置start、step、loop等参数即可获得跑马灯效果。但在 API 24 中这个方法已被移除。可能的移除原因包括性能不可控系统 Marquee 的运行机制是黑盒开发者无法精确控制其渲染时机和资源消耗定制能力有限系统 Marquee 不支持中途修改速度、动态切换内容等高级需求与新版动画系统冲突API 24 的动画框架进行了重构旧 Marquee 与新框架不兼容失去系统 Marquee 并不意味着失去跑马灯效果。实际上用animateTotranslate构建自定义跑马灯能获得更好的控制力和扩展性。animateTo 循环动画的实现animateTo是 ArkUI 的显式动画 API。常规的animateTo调用在动画执行完毕后即结束但通过onFinish回调我们可以实现循环startMarquee():void{if(!this.playing){this.offset0;return;}letdurationthis.getDuration();animateTo({duration:duration,curve:Curve.Linear,onFinish:(){this.offset0;// 复位this.startMarquee();// 递归调用启动下一轮}},(){this.offset-this.scrollDistance;// 向左平移});}这段代码的关键设计点Linear 曲线跑马灯需要恒定速度任何缓动曲线都会导致视觉上的加速/减速感onFinish 中复位动画完成时立即将 offset 归零然后递归启动新一轮playing 守卫在函数开头检查播放状态为暂停功能提供控制点动画生命周期的精细控制自定义跑马灯的一个核心优势是可以在任意时刻打断并重启动画。由于animateTo的新调用会自动中断前一个动画我们可以在速度变化、内容切换、暂停恢复时简单地重新调用startMarquee()changeSpeed(idx:number):void{this.speedLevelidx;this.startTopMarquee();// 新 animateTo 自动中断旧动画this.startSecondMarquee();}togglePlay():void{this.playing!this.playing;this.startMarquee();// 暂停时 offset 置 0 并返回播放时启动新动画}这比系统 API 灵活得多——无需等待当前滚动周期结束状态变化立即可见。计算动画时长跑马灯的速度通常用像素/秒px/s来衡量而非直接使用毫秒数。这样的好处是无论文本多长滚动速度保持一致。动画时长需要通过以下公式计算duration(scrollDistance/speed)*1000其中scrollDistance是文本需要滚动的总距离像素speed是滚动速度px/s。在本 Demo 中我们定义了三个速度档位档位速度 (px/s)580px 对应时长视觉感受慢速60~9.7s适合较短的公告文字阅读体验好标准100~5.8s常规滚动速度适合中等长度文本快速160~3.6s适合需要快速浏览的长文本privatespeeds:number[][60,100,160];privatescrollDistance:number580;getDuration(speed:number):number{return(this.scrollDistance/speed)*1000;}实战 Demo公告快讯中心本文的 Demo 是一个公告快讯中心页面包含以下功能模块两条可独立控制的跑马灯重要通知红色主题和一般公告蓝色主题全局播放控制一键暂停/恢复所有跑马灯三段速度调节慢速、标准、快速快讯内容切换系统维护 / 活动推广 / 安全提醒 三种预设内容历史公告列表展示已发布的公告记录状态设计StatetopPlaying:booleantrue;// 顶部跑马灯播放状态StatesecondPlaying:booleantrue;// 第二条跑马灯播放状态StatespeedLevel:number1;// 速度档位 (0/1/2)StatecontentIdx:number0;// 内容模板索引StatetopOffset:number0;// 顶部文本水平偏移StatesecondOffset:number0;// 第二条文本水平偏移跑马灯动画核心实现两条跑马灯各自拥有独立的startXxxMarquee()方法可以互不干扰地独立控制startTopMarquee():void{if(!this.topPlaying){this.topOffset0;return;}letdurthis.getDuration(this.getSpeed());animateTo({duration:dur,curve:Curve.Linear,onFinish:(){this.topOffset0;this.startTopMarquee();}},(){this.topOffset-this.scrollDistance;});}在 UI 层文本被包裹在Row中并通过.translate()绑定 offsetRow(){Text(this.contents[this.contentIdx]).fontSize(13).fontColor(#1a1a2e).maxLines(1)}.translate({x:this.topOffset,y:0}).layoutWeight(1).clip(true)// 裁剪超出部分隐藏复位时的闪烁交互控制独立暂停/恢复点击跑马灯卡片切换该条跑马灯的播放状态toggleTop():void{this.topPlaying!this.topPlaying;this.startTopMarquee();// playingfalse 时复位true 时启动}全局播放控制通过 Toggle 开关统一控制两条跑马灯toggleAll(val:boolean):void{this.topPlayingval;this.secondPlayingval;if(val){this.startTopMarquee();this.startSecondMarquee();}}速度调节选择速度后立即中断当前动画并按新速度重启changeSpeed(idx:number):void{this.speedLevelidx;this.startTopMarquee();// 以新速度重新计算 duration 并启动this.startSecondMarquee();}内容切换切换顶部跑马灯的展示文本changeContent(idx:number):void{this.contentIdxidx;this.startTopMarquee();// 新内容 复位 重新滚动}页面结构整个页面的布局由 Scroll 包裹包含五个白色卡片区域Header标题栏公告快讯 滚动状态徽章 Scroll ├── 重要通知跑马灯卡片红色边框 红色标签 ├── 一般公告跑马灯卡片蓝色边框 蓝色标签 ├── 滚动控制卡片Toggle 全部播放 速度三段选择 ├── 快讯内容卡片内容模板三选一 ├── 历史公告卡片四条带标签和时间的公告记录 └── 底部状态栏当前速度 引擎信息公告数据结构classAnnouncement{title:string;content:string;time:string;tag:string;tagColor:string;constructor(title:string,content:string,time:string,tag:string,tagColor:string){this.titletitle;this.contentcontent;this.timetime;this.tagtag;this.tagColortagColor;}}历史公告列表中的每条公告包含标题、摘要、发布时间和一个彩色标签系统/版本/通知/安全使用 ForEach 渲染ForEach(this.getAnnouncements(),(item:Announcement,idx:number){Row(){Column(){Text(item.title).fontSize(14).fontWeight(FontWeight.Medium)Text(item.content).fontSize(12).fontColor(#888899).maxLines(1)}.layoutWeight(1)Column(){Text(item.tag).fontSize(10).fontColor(item.tagColor).backgroundColor(item.tagColor15)Text(item.time).fontSize(10).fontColor(#CCCCDD)}}.borderRadius(10).backgroundColor(#F8F9FA)},(item:Announcement)item.title)性能优化与注意事项1. clip(true) 消除复位闪烁当动画完成一轮、offset 从-scrollDistance瞬间复位到0时文本会闪现回起点。通过给容器添加.clip(true)超出容器的文本部分被裁剪用户看不到复位瞬间的视觉效果。2. Linear 曲线的必要性跑马灯动画必须使用Curve.Linear。任何非线性曲线都会导致文本在不同区段的速度不一致开始时慢然后加速EaseIn或开始时快然后减速EaseOut这会破坏跑马灯匀速滚动的核心体验。3. 动画中断与内存管理每次调用startMarquee()都会创建一个新的animateTo动画实例。由于新动画会自动中断旧动画不用担心动画堆积。但需要注意onFinish的递归调用链——如果暂停状态playing false递归会在函数开头终止不会无限循环。4. 多跑马灯的独立控制本例中两条跑马灯有各自独立的playing状态和offset值可以独立暂停和恢复。在扩展更多跑马灯时建议将每条跑马灯的状态封装到独立的数据结构中如数组避免为每条跑马灯写重复的状态变量和启动方法。5. 响应式更新陷阱在 ArkTS 中State变量的变化会触发 UI 重新渲染。跑马灯动画中offset以 60fps 的频率更新通过 animateTo 的插值这意味着 UI 也会以 60fps 的频率局部更新。对于简单的 translate 变换这种更新完全在 GPU 合成层完成性能影响极小。但应注意不要在高频更新的动画循环中执行复杂的计算或布局操作。扩展方向掌握了基础的自定义跑马灯后可以进一步探索无缝循环通过复制文本内容实现真正无缝的循环两个相同的文本块交替出现消除复位时的视觉间断双向滚动利用playMode: PlayMode.Alternate实现文字在容器内来回弹跳渐变遮罩在容器两端添加透明度渐变遮罩让滚动文字的进出更自然手势联动将滚动速度与手势拖拽速度绑定实现跟手效果的跑马灯垂直跑马灯将 translate 的 x 轴改为 y 轴实现垂直方向的文字滚动总结本文通过拆解跑马灯的技术原理使用animateToonFinish递归回调构建了一套完整的自定义跑马灯系统。在没有系统 Marquee API 的情况下我们不仅实现了一模一样的视觉效果还获得了更强的控制力——独立暂停、速度调节、内容切换这些功能在自定义方案中只需寥寥几行代码即可实现。这个实践也揭示了一个更普遍的规律不要被 API 的存废所限制。当某个便捷 API 被移除时理解其底层原理能让你用更基础的 API 重建它而且通常能获得更好的灵活性和可控性。animateTo是 ArkUI 中最基础的动画 API但配合onFinish、状态管理和数学计算它可以衍生出跑马灯、呼吸灯、打字机效果等丰富多彩的动画模式。从这个角度看学习自定义跑马灯不仅是学习一个动画技巧更是学习一种用基础积木搭建高级效果的思维方式——这对于任何平台的 UI 开发都至关重要。