在智慧大屏、车载中控、移动终端及智能穿戴等多元化场景中,超长文本的滚动展示(跑马灯动效)是高频且不可或缺的交互手段。当信息长度超出容器边界时,生硬截断会破坏阅读体验,而有序的循环滚动则能流畅传递信息。然而,过去版本中相邻两轮滚动之间的“真空期”难以精细控制——间距过小导致文本粘连,过大又缺乏节奏感。HarmonyOS NEXT 6.1(API 23)针对这一痛点,在ArkUI的Text组件配置对象TextMarqueeOptions中新增了核心参数spacing,通过LengthMetrics模型实现像素级精确间距调节,彻底告别字符串拼接占位符的“土办法”。
一、背景:从底层绘图到精细化控制
跑马灯并非简单的平移动画,而是由底层Paragraph引擎在渲染循环中,通过动态位移剪裁视窗实现的高帧率绘制。在引入spacing之前,Paragraph引擎硬编码了默认相遇间距阈值48.0vp,无论文本长度、容器宽度或屏幕分辨率如何,两轮滚动间的距离都固定不变。新增的spacing参数允许开发者传入LengthMetrics实例,Paragraph在计算物理帧首尾循环时动态读取该值,在离屏内存中精准留出空白间隙,既保证物理分界,又能适配多分辨率与多端缩放。
二、API详解与核心约束
spacing属性在TextMarqueeOptions中声明为“spacing?: LengthMetrics;”类型。
- 单位支持:LengthMetrics.vp(value)或LengthMetrics.px(value)创建实例。
- 默认值:若未传入,系统回退至48.0vp。
- 关键静默失效机制:若传入的LengthMetrics单位设置为百分比(LengthUnit.PERCENT),例如LengthMetrics.percent(0.2),系统在底层解析时会直接拦截——因为无限滚动轴线上无法锚定稳定的父容器百分比参照,该设置将不生效,强制回退至默认48.0vp(且无报错提醒)。
- 系统能力:SystemCapability.ArkUI.ArkUI.Full,支持Phone、PC、Tablet、TV、Wearable设备。
三、项目实战:跑马灯间距精密控制台Demo
我们创建了一个完整的ArkUI Demo(TextMarqueeDetail.ets),通过状态机控制播放状态,并提供VP、PX、百分比三种单位的同台对照测试,直观演示PERCENT静默失效现象。
- import { router, LengthMetrics, LengthUnit } from '@kit.ArkUI';
- @Entry
- @Component
- struct TextMarqueeDetail {
- @State marqueeStart: boolean = true;
- @State spacingValue: number = 48;
- @State selectedUnit: LengthUnit = LengthUnit.VP;
- @State marqueeLogs: string = '已开启跑马灯。通过调节下方参数,动态控制相邻两轮循环的物理间距。';
- private marqueeText: string = '极简排版探索:HarmonyOS NEXT 6.1 (API 23) 精准控制跑马灯双循环间距属性 spacing 实战演练,支持高帧率零抖动渲染。';
- build() {
- Column() {
- // 导航栏
- Row() {
- Button('返回').onClick(() => { router.back(); })
- .backgroundColor('#F5A623').fontColor('#000000').height(36).margin({ right: 16 })
- Text('跑马灯间距 spacing 调优实战').fontColor('#FFFFFF').fontSize(18).fontWeight(FontWeight.Bold)
- }.width('100%').height(56).backgroundColor('#111111').padding({ left: 16, right: 16 })
- Column() {
- // 场景一:跑马灯展示看板
- Column() {
- Text('【超长跑马灯动态演示容器】').fontSize(14).fontColor('#F5A623').fontWeight(FontWeight.Bold).margin({ bottom: 12 })
- Text(this.marqueeText)
- .fontSize(18).fontColor('#FFFFFF').fontWeight(FontWeight.Medium)
- .width('100%').padding(16).backgroundColor('#111111').borderRadius(8).margin({ bottom: 12 })
- .marqueeOptions({
- start: this.marqueeStart,
- step: 6.0,
- loop: -1,
- fromStart: true,
- delay: 0,
- fadeout: true,
- spacing: this.selectedUnit === LengthUnit.PERCENT
- ? LengthMetrics.percent(0.2) // 百分比单位将静默失效,回退至48.0vp
- : new LengthMetrics(this.spacingValue, this.selectedUnit)
- })
- Row() {
- Text(`当前间距 (spacing):${this.spacingValue} ${this.selectedUnit === LengthUnit.VP ? 'vp' : this.selectedUnit === LengthUnit.PX ? 'px' : '%'}`)
- .fontSize(12).fontColor('#888888')
- Blank()
- Text(this.marqueeStart ? '播放中' : '已暂停').fontSize(12).fontColor(this.marqueeStart ? '#4CAF50' : '#F44336')
- }.width('100%')
- }.width('100%').padding(16).backgroundColor('#161616').borderRadius(10).border({ width: 1, color: '#2C2C2C' }).margin({ bottom: 20 })
- // 场景二:控制面板
- Column() {
- Text('【间距调优精密控制台】').fontSize(14).fontColor('#F5A623').fontWeight(FontWeight.Bold).margin({ bottom: 12 })
- Column() {
- Text('间距大小 (Value)').fontSize(12).fontColor('#A0A0A0').margin({ bottom: 6 })
- Slider({ value: this.spacingValue, min: 0, max: 200, step: 4, style: SliderStyle.OutSet })
- .blockColor('#F5A623').trackColor('#2C2C2C').selectedColor('#F5A623').showTips(true)
- .onChange((value: number) => {
- this.spacingValue = Math.floor(value);
- this.marqueeLogs = `动态调节两轮间距为: ${this.spacingValue} ${this.selectedUnit === LengthUnit.VP ? 'vp' : 'px'}`;
- })
- }.width('100%').margin({ bottom: 16 })
- Text('LengthMetrics 计量单位').fontSize(12).fontColor('#A0A0A0').margin({ bottom: 8 })
- Row({ space: 10 }) {
- Button('VP (生效)').onClick(() => {
- this.selectedUnit = LengthUnit.VP;
- this.marqueeLogs = '计量单位切换为 VP,间距设置完美生效。';
- }).backgroundColor(this.selectedUnit === LengthUnit.VP ? '#E65100' : '#2C2C2C').fontColor('#FFFFFF').height(36).fontSize(12).flexGrow(1)
- Button('PX (生效)').onClick(() => {
- this.selectedUnit = LengthUnit.PX;
- this.marqueeLogs = '计量单位切换为 PX,间距设置完美生效。';
- }).backgroundColor(this.selectedUnit === LengthUnit.PX ? '#E65100' : '#2C2C2C').fontColor('#FFFFFF').height(36).fontSize(12).flexGrow(1)
- Button('% (静默失效)').onClick(() => {
- this.selectedUnit = LengthUnit.PERCENT;
- this.marqueeLogs = '计量单位为 PERCENT,系统将静默拦截此参数,强制回退至默认 48.0vp 间距。';
- }).backgroundColor(this.selectedUnit === LengthUnit.PERCENT ? '#E65100' : '#2C2C2C').fontColor('#FFFFFF').height(36).fontSize(12).flexGrow(1)
- }.width('100%').margin({ bottom: 20 })
- Row({ space: 10 }) {
- Button(this.marqueeStart ? '暂停跑马灯' : '启动跑马灯').onClick(() => {
- this.marqueeStart = !this.marqueeStart;
- this.marqueeLogs = this.marqueeStart ? '成功唤醒跑马灯循环播放' : '跑马灯已进入静止状态';
- }).backgroundColor('#E65100').fontColor('#FFFFFF').height(40).flexGrow(1)
- }.width('100%').margin({ bottom: 12 })
- Text(this.marqueeLogs).fontSize(12).fontColor('#888888').lineHeight(16).width('100%')
- }.width('100%').padding(16).backgroundColor('#161616').borderRadius(10).border({ width: 1, color: '#2C2C2C' })
- }.width('100%').flexGrow(1).padding(16).backgroundColor('#000000')
- }.height('100%').width('100%').backgroundColor('#000000')
- }
- }
复制代码
四、运行效果与避坑指南
在实际测试中,当计量单位设为VP或PX并拖拽滑块时,相邻两轮文本的物理间隙随滑块值(如从48增至160)像素级精准拉开,动画平稳无抖动。切换至百分比单位后,无论滑块数值如何变化,间隙始终锁定在默认48.0vp,验证了静默失效机制。
避坑要点:
1. 禁止使用百分比单位:由于底层渲染无法获取稳定父容器参照,传入PERCENT将无提示回退。务必使用VP或PX。
2. 避免频繁抖动更新:step和spacing应在状态切换等固定节点更新,若在高频回调中无规律变更(如从20px跳变至150px),会导致Paragraph帧缓存失效,引发动画突跳与卡顿。建议在外层增加节流(Throttle)处理。
五、总结
spacing参数让开发者告别字符串拼接空格的脏代码,实现物理级别的精确间距控制。合理选用VP/PX单位并规避百分比陷阱,即可在穿戴、车载、大屏等多端设备上营造极致的滚动节奏美感。推荐在HarmonyOS 6.1项目中积极采用此接口,提升文字交互的精致度。 |