鸿蒙Next实现歌词字体颜色渐变 原创 精华

auhgnixgnahz
发布于 2025-9-15 09:00
浏览
0收藏

实现一个音乐播放器中,歌词颜色渐变的效果。简单点说,就是解析歌词内容,拿到每个词,或者每句话的开始时间、结束时间,执行颜色渐变的动画,然后依次执行。今天这篇先实现一个简单的一句话字体颜色渐变效果。
实现演示:
鸿蒙Next实现歌词字体颜色渐变-鸿蒙开发者社区
实现思路:
1.实现字体颜色渐变可以使用之前介绍过的Canvas绘制字体和线性渐变色实现,这种方案需要大量的绘制,相对复杂。
2.如果让字体镂空,底部背景使用animateTo实现颜色渐变,这样也可以实现字体颜色渐变。这样就需要用到一个重要属性blendMode
**图像混合模式:**blendMode
这是一个通用属性,是将当前控件的内容(包含子节点内容)与下方画布(可能为离屏画布)已有内容进行混合。
blendMode(value: BlendMode, type?: BlendApplyType)
BlendMode:混合模式。 这里主要介绍需要用到的两种模式,之后出一期总结一下这个属性的各种效果。
DST_IN:只显示目标像素中与源像素重叠的部分。
SRC_OVER:将源像素按照透明度进行混合,覆盖在目标像素上
BlendApplyType:是否离屏
离屏时会创建当前组件大小的离屏画布,再将当前组件(含子组件)的内容绘制到离屏画布上,再用指定的混合模式与下方画布已有内容进行混合。
3.每个歌词,按照动画时常,依次执行动画,因此可以分为执行过、正在执行、未执行三种状态,根据这三种状态分别设置歌词的渐变色。
避坑:
由于当前animateTo与V2状态装饰器的刷新机制不兼容,如果使用V2需要animateToImmediately先执行依次,再执行animateTo,见下面代码,如果使用V1则正常,不需要加。
源码

@Entry
@ComponentV2
struct LyricTest {
  private uiContext = this.getUIContext();
  lineLrc: string[] = 'HamonyOS学习笔记'.split('')
  @Local playState: boolean = false
  @Local currentIndex: number = 0; //每词在每句中索引
  @Local colorStopValue: number = 0;
  @Local lrcDuration: number = 1000; // 歌词持续时间
  timer: number | null = null
  playLrc() {
    //由于当前animateTo与V2的刷新机制不兼容
    //将额外的修改先刷新,再执行原来的动画达成预期的效果
    animateToImmediately({
      duration: 0
    }, () => {
    })
    this.uiContext.animateTo({
      duration: this.lrcDuration,
      finishCallbackType: FinishCallbackType.LOGICALLY,
      curve: Curve.Linear,
      iterations: 1,
      onFinish: () => {
        this.colorStopValue = 0
        this.currentIndex++;
        if (this.currentIndex < this.lineLrc.length) {
          this.playLrc();
        }else {
          this.currentIndex=0
          this.playState = false
        }
      }
    }, () => {
      this.colorStopValue = 1
    })
  }
  build() {
    Column({space:20}) {
      Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Center }) {
        ForEach(this.lineLrc, (text: string, wordIndex: number) => {
          Row() {
            Text(text)
              .fontSize(30)
              .blendMode(BlendMode.DST_IN, BlendApplyType.OFFSCREEN)
          }
          .margin({right:5})
          .blendMode(BlendMode.SRC_OVER, BlendApplyType.OFFSCREEN)
          .linearGradient({
            direction: GradientDirection.Right,
            colors: (() => {
              if (wordIndex < this.currentIndex) {
                // 已播放的字:全红
                return [['#FF0000', 0.0], ['#FF0000', 1.0]];
              } else if (wordIndex === this.currentIndex) {
                // 正在播放的字:红色渐变(从 0 到 this.colorStopValue)
                return [
                  ['#FF0000', 0.0],
                  ['#FF0000', this.colorStopValue],
                  ['#FFFFFF', this.colorStopValue],
                  ['#FFFFFF', 1.0]
                ];
              } else {
                // 未播放的字:全白
                return [['#FFFFFF', 0], ['#FFFFFF', 1.0]];
              }
            })()
          })
        })
      }
      .width('90%')
      .margin({top:30})
      Button(this.playState?'暂停':'开始').fontSize(24).onClick(()=>{
        this.playState=!this.playState
        if (this.playState)this.playLrc();
      })
    }
    .width('100%')
    .height('100%')
    .linearGradient({
      // 渐变方向
      direction: GradientDirection.Bottom,
      // 渐变颜色是否重复
      repeating: true,
      // 数组末尾元素占比小于1时满足重复着色效果
      colors: [['#17233c', 0.0], ['#2b3755', 0.6], ['#181e34', 1]]
    })
  }
}

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
标签
收藏
回复
举报
回复
    相关推荐