在智慧大屏、车载中控或穿戴设备中,超长文本的跑马灯滚动是常见需求。过去开发者常通过拼接空格来调整两轮滚动之间的间距,但这种方法存在宽度不稳定、逻辑代码污染等问题。HarmonyOS NEXT 6.1(API 23)为Text组件的跑马灯配置对象TextMarqueeOptions新增了spacing属性,允许开发者通过LengthMetrics模型精确控制物理间距,彻底告别土办法。
spacing参数的底层机制
跑马灯动效并非简单的平移动画,而是由ArkUI的Paragraph引擎在渲染循环中通过动态位移裁剪视窗实现的。在引入spacing之前,引擎使用固定的默认阈值48.0vp计算两轮滚动的相遇间隔。现在,一旦传入LengthMetrics类型的spacing参数,引擎会在离屏内存中动态预留精确的空白物理间隙,确保多分辨率、多设备适配的一致性。
API接口与约束
spacing属性的声明为:spacing?: LengthMetrics。类型必须为LengthMetrics,支持使用LengthMetrics.vp(value)或LengthMetrics.px(value)创建实例。若不传参,默认回退至48.0vp。特别注意:如果传入的LengthMetrics单位是百分比(LengthUnit.PERCENT),系统会直接静默拦截,该设置不生效,仍按默认值处理。此属性从API 23开始支持元服务,系统能力为SystemCapability.ArkUI.ArkUI.Full,覆盖Phone、PC/2in1、Tablet、TV、Wearable设备。
实战案例:跑马灯间距精密控制台
下面通过一个完整示例演示如何在ArkUI中动态调节spacing,并验证不同计量单位的效果。代码位于entry/src/main/ets/pages/TextMarqueeDetail.ets中。
核心逻辑:通过状态机控制跑马灯播放状态,通过Slider滑块调节间距数值,通过三个按钮切换VP、PX和百分比单位。在构建spacing参数时,根据当前selectedUnit动态创建LengthMetrics实例。注意百分比单位会静默失效。
- 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)
- : 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时,拖动Slider从48逐步增加到160,可以清晰看到两轮滚动文本之间的物理间隙按像素级精准拉开,动画平滑无闪烁。切换为百分比单位后,无论数值如何变化,间距始终锁定在48.0vp,验证了静默失效机制。整个过程无闪退,体现了系统排版健壮性。
避坑指南
1. 百分比单位陷阱:由于跑马灯在无限滚动轴线上无法确定百分比参照,传入LengthUnit.PERCENT实例会静默回退至默认值。解决方案:始终使用VP或PX单位。
2. 动态参数频繁抖动:若在高频事件中频繁更新spacing值,会导致Paragraph引擎帧缓存失效,引起动画突跳或卡顿。建议仅在状态切换或固定节点更新spacing,或在外层添加节流处理器。
总结
HarmonyOS NEXT 6.1的spacing属性让开发者能以物理级精度控制跑马灯滚动间隙,彻底告别字符串拼接的粗糙做法。合理选择VP或PX单位,注意避开百分比盲区,即可在多设备上实现一致的文本滚动体验。推荐在需要精致排版的场景中积极采用此接口。 |