鸿蒙自定义跑马灯

665 阅读3分钟

鸿蒙自定义跑马灯

前言

鸿蒙NEXT里面有一个Marquee组件,可以实现文字跑马灯效果,当文字长度超过控件长度时就会将文字滚动显示,不过用起来感觉坑好多,所以我这还是自己手写了一个。

Marquee组件问题

在我使用官方Marquee组件时候,有一些功能感觉很坑:

  1. 只有文字超过长度才能滚动,不能选择开启
  2. 所有Marquee组件播放的时候都是同时启动
  3. 任何点击效果都会重置所有Marquee组件,然后重新滚动
  4. Marquee组件速度不可设置

可能是我们项目有这个要求,所以官方Marquee组件没法使用,我写了个自定义地Marquee来解决这些问题。

而且实现了速度控制,加个动态marginTop,多用几个组件弹幕效果也解决了,要什么自行车,哈哈哈!

自定义Marquee组件

和前面自定义对话框差不多,就几个属性,然后一个页面,再加个定时就可以解决:

/**
 * 自定义跑马灯
 *
 * @author lfq 2024-08-26
 */
@Component
export struct CustomMarquee {
  @Prop marqueeParam: MarqueeParam
  @State marqueeTextOffset: number = 0;
  marqueeTextLength: number = 0;
  marqueeScrollLength: number = 0;
  onBounce?: () => void;

  // 在自定义组件析构显示之前执行
  aboutToAppear(): void {
    this.startMarquee();
  }

  build() {
    Scroll() {
      Row() {
        Text(this.marqueeParam.marqueeText)
          .fontColor(Color.White)
          .fontSize(12)
          .textAlign(TextAlign.Center)
          .backgroundColor(Color.Transparent)
          .margin({ left: 10, right: 10 })
          .onAreaChange((oldValue, newValue) => {
            LogUtil.d("marqueeTextLength", "old: " + Number(oldValue.width), "new: " + Number(newValue.width))
            // 获取当前文本内容宽度
            this.marqueeTextLength = Number(newValue.width);
          })
      }.offset({ x: this.marqueeTextOffset })
    }
    .width('100%')
    .align(Alignment.Start)
    .enableScrollInteraction(false)
    .flexGrow(1)
    .scrollable(ScrollDirection.Horizontal)
    .scrollBar(BarState.Off)
    .onAreaChange((oldValue, newValue) => {
      LogUtil.d("marqueeScrollLength", "old: " + Number(oldValue.width), "new: " + Number(newValue.width))
      // 获取当前Scroll组件宽度
      this.marqueeScrollLength = Number(newValue.width);
    })
  }

  private timer: number | null = null;
  startMarquee() {
    // 关掉之前的
    if (this.timer) {
      clearInterval(this.timer);
    }

    // 初始偏移值
    this.marqueeTextOffset = this.marqueeScrollLength

    // 计时
    this.timer = setInterval(()=>{
      // 重新开始
      if (this.marqueeTextOffset < - this.marqueeTextLength) {
        this.marqueeTextOffset = this.marqueeScrollLength
        if (this.onBounce) {
          this.onBounce();
        }
      }else {
        this.marqueeTextOffset--;
      }
    }, 30 / this.marqueeParam.speed);
  }
}

export class MarqueeParam {
  marqueeText: string = "这个是跑马灯";
  speed: number = 2;
}

这里有几点要讲下:

文字滚动

这里地文字滚动是通过Scroll和它子view的offset来实现地,启动一个定时器,来回更新地它的offset就OK了:

Scroll() {
  Row() {

  }.offset({ x: this.marqueeTextOffset })
}
.width('100%')
.align(Alignment.Start)
.enableScrollInteraction(false)
.flexGrow(1)
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)

定时更新

文字滚动就一个控制变量,我们只要写一个定时器让offset变化起来就可以了:

  startMarquee() {
    // 关掉之前的
    if (this.timer) {
      clearInterval(this.timer);
    }

    // 初始偏移值
    this.marqueeTextOffset = this.marqueeScrollLength

    // 计时
    this.timer = setInterval(()=>{
      // 重新开始
      if (this.marqueeTextOffset < - this.marqueeTextLength) {
        this.marqueeTextOffset = this.marqueeScrollLength
        if (this.onBounce) {
          this.onBounce();
        }
      }else {
        this.marqueeTextOffset--;
      }
    }, 30 / this.marqueeParam.speed);
  }

就是一个递减操作,到头了就重新复制,每次结束执行下onBounce方法,这里注意下offset的方向。

速度控制

速度控制就更简单了,我们加个变量来控制定时器的更新间隔就可以了:

    // 计时
    this.timer = setInterval(()=>{
      
    }, 30 / this.marqueeParam.speed);

宽度变化感知

因为有时候跑马灯也会跟随屏幕变化,比如说横竖屏切换,这里还是要控制下逻辑的:

  Scroll() {
      Row() {

      }.offset({ x: this.marqueeTextOffset })
    }
    ...
    .onAreaChange((oldValue, newValue) => {
      LogUtil.d("marqueeScrollLength", "old: " + Number(oldValue.width), "new: " + Number(newValue.width))
      // 获取当前Scroll组件宽度
      this.marqueeScrollLength = Number(newValue.width);
    })

这里在整个控件最外层的Scroll组件的宽度上加个监听,如果发生了变化改下宽度值。

小结

写了一个鸿蒙版本的跑马灯,实现了文字滚动效果、速度可设置、可根据宽高自适应、多个组件之间互不干扰,自用还是挺OK的。