• 鸿蒙小案例-动态歌词


    之前有个播放器要显示歌词,但没找到鸿蒙中现成的组件,只能摸索着自己写一个
    先看下效果

    鸿蒙动态歌词展示

    原理其实很简单
    首先布局什么的就不多说了,歌词显示这块肯定是要全部显示的,主要操作难点在于怎么根据播放时长动态跳转到歌词位置
    在这里我们用个List组件包裹,并且绑定scroller滚动条,循环显示所有歌词,当播放到指定位置时,通过scrollToIndex方法跳到歌词的这一行由此实现动态歌词
    拿到歌词后转换成两个长度一致的数组,分别为时间数组,歌词数组,他们的下标应该是一一对应的,
    播放时长随时变动,一变动就去时间数组寻找最接近的那个值,拿到下标,然后用这个下标去用scroller的scrollToIndex方法跳到歌词的这一行
    全局常量

    lrcScroller: Scroller = new Scroller() //歌词滚动条
    @State lrcKeys: number[] = [] //歌词时间数据
    @State lrcValues: string[] = [] //歌词具体数据
    @Watch('upPlayStatus')
    @State playStartTime: number = 0 //当前播放起始时间
    @State upPlayScrollerIndex: number = 0 //歌词滚动索引
    @State upPlayIndex: number = 0 //歌词显示索引
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    显示代码

    List({ initialIndex: 1, scroller: this.lrcScroller }) {
          ForEach(this.lrcValues, (item: string, index: number) => {
            ListItem() {
              Column() {
                Row() {
                  Text(item)
                    .fontColor(this.upPlayIndex === index ? '#00AE68' : '#000000')
                    .fontSize(15)
                    .maxLines(3)
                    .textOverflow({ overflow: TextOverflow.Ellipsis })
                    .padding(5)
                    .fontWeight(this.upPlayIndex === index ? FontWeight.Bold : FontWeight.Normal)
                    .textAlign(TextAlign.Center)
                }.borderRadius(5).backgroundColor('#F3F3FA')
                .margin({ bottom: 5 })
              }.alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center).width('100%')
            }
          })
        }.animation({ duration: 500 })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    其中
    this.lrcValues=歌词数组,结构为string
    item = 具体的每一行的歌词,结构为string
    index = 歌词数组的下标,结构为number
    this.upPlayIndex = 当前播放的时间在歌词数组中的下标

    歌词+时间获取代码

    //获取歌词
      async queryLrcById() {
        let lrcMap: Map<string, string[] | number[]> = await CommUtils.queryLrcById(this.PlayState.id)
        this.lrcKeys = lrcMap.get('key') as number[]
        this.lrcValues = lrcMap.get('value') as string[]
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    this.PlayState.id = 当前播放歌曲id
    CommUtils.queryLrcById = 根据id获取歌词数据,返回的是两个数组,Map结构
    this.lrcKeys = 时间数组
    this.lrcValues = 歌词数组
    这个方法应该是在你点击一个按钮,然后要显示歌词时调用

    动态调整歌词

    //显示歌词附带的滚动效果
      upPlayStatus() {
        if (this.lrcStatus && this.lrcKeys.length > 0) {
          const indexStr = CommUtils.findClosestNumber(this.lrcKeys, this.playStartTime)
          setTimeout(() => {
            this.upPlayIndex = this.lrcKeys.indexOf(indexStr)
            const index = this.upPlayIndex - 4 < 0 ? 0 : this.upPlayIndex - 4
            if (index > this.upPlayScrollerIndex) {
              this.lrcScroller.scrollToIndex(index)
              this.upPlayScrollerIndex = index
            }
          }, 1000)
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    this.playStartTime = 当前播放的歌曲的已播放时长
    index = scroller需要滚动到的行数/下标
    this.upPlayScrollerIndex = 上一个已经跳动的下标
    通过@Watch装饰器来实现调用
    为什么会有下面这块代码呢

    if (index > this.upPlayScrollerIndex) {
        .......  
     }
    
    • 1
    • 2
    • 3

    因为歌词数据并不是连续的,它是跳着来的,有时候歌曲的伴奏,哼唱这些都是没有歌词的,但是我们的获取最相近下标那个方法是没做区分的,会有很多重复的index出现,所以我们在需要跳动前记录下跳动的坐标,在下一次跳动前加个判断,只有大于上一个下标的时候,我再去跳,小于等于的话就不去跳,节省下消耗
    为什么会有定时器呢
    因为获取最相近的下标并不太精准,我们延时一下再跳
    为什么会有下标 减 4 呢?
    因为list的scroller的scrollToIndex方法会直接置顶显示,我们减4 它就始终处于屏幕正中央(这里说明一下,要根据外围包裹组件的具体高度来调整,一屏能显示几行歌词,这里就减去 总行数除以2,否则它不会显示在正中间)
    findClosestNumber方法如下

    //获取一个数组中跟传入值最相近的数值
      static findClosestNumber(arr: number[], target: number) {
        return arr.reduce((p, c) => (Math.abs(target - c) < Math.abs(target - p) ? c : p))
      }
    
    • 1
    • 2
    • 3
    • 4

    arr = 时间数组
    target = 当前播放时间

    到这里就基本上实现动态歌词的功能,当然还有一些细节需要自行去优化下
    比如:
    切歌后,这个index的值要等于0,滚动条要滚动到0,数组要等于0等。。。

    //切歌时重置歌词相关组件
      clearLrcCom() {
        this.upPlayScrollerIndex = 0
        this.upPlayIndex = 0
        this.lrcValues = []
        this.lrcKeys = []
        this.lrcScroller.scrollToIndex(0)
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这个方法是要在切歌时,隐藏歌词时 调用

  • 相关阅读:
    【物理应用】基于Matlab模拟RANS湍流
    【车载开发系列】Autosar框架中的WatchDog
    Google Earth Engine ——利用where来合理划分NDVI阈值
    吴恩达2022机器学习专项课程C2W2:实验SoftMax
    DOE认证是什么
    【Spring Boot】DataSource数据源的自动配置解析
    电机学 基础概念 野火电机第四章 电机分类
    [第二十二篇]——Docker 安装 MongoDB
    FPGA基础知识|芯片设计基础知识
    基于C语言的词法分析程序的设计与实现
  • 原文地址:https://blog.csdn.net/q2158798/article/details/138196925