Skip to content

【续】鸿蒙 5.0 快速入门


七、黑马云音乐【续】


7.5 发现组件


7.5.1 概述


  • 功能结构:
微信截图_20250930215619
  • ❓ 梳理:前面我们实现跳转用到了 Navigation 组件,然后对于 广告页 Start布局页 Layout

    都是作为子页存在,那么它们是用 NavDestination 组件。那么又在布局页内部有四个组件,

    我们是通过条件渲染的方式来展示的。现在对于发现组件又需要跳转到一个新的页面 播放页 Play

    那么应该怎么做呢??难道还来 NavDestination ??这是不行的 ..

微信截图_20251001152844
  • 解决方案 :使用 AppStorageV2 (应用全局UI状态存储) 来存储到之前的控制跳转的对象

    pathStack ,我们在其他页面拿到这个对象就可以用它来实现跳转了

  • AppStorageV2 官方文档


7.5.2 准备工作


  • 在这个部分,我们专注于做 跳转 这个功能的实现,

    那么其他前面涉及到的基础代码就先可以忽略,直接复制下面给出的代码就好了


  • 1). 在 src/main/ets 下面创建一个 models 目录 , 然后新建一个 music.ets 文件,放入下面代码:
typescript
export interface SongItemType {
  img: string
  name: string
  author: string
  url: string
  id:string
}

说明:因为这个接口在发现页和播放页都要使用,那么直接抽取在文件里 export 给外面导入就好了


  • 2). 发现页:src/main/ets/pages/Find.ets
📌 点击展开代码
typescript
import { SongItemType } from "../models/music"

@ComponentV2
export struct Find {
  songs: SongItemType[] = [
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/0.jpg',
      name: '直到世界的尽头',
      author: 'WANDS',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/0.m4a',
      id: '0000'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/1.jpg',
      name: '画',
      author: '赵磊',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/1.mp3',
      id: '0001'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/2.jpg',
      name: 'Sweet Dreams',
      author: 'TPaul Sax / Eurythmics',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/2.mp3',
      id: '0002'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/3.jpg',
      name: '奢香夫人',
      author: '凤凰传奇',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/3.m4a',
      id: '0003'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/4.jpg',
      name: '空心',
      author: '光泽',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/4.mp3',
      id: '0004'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/5.jpg',
      name: '反转地球',
      author: '潘玮柏',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/5.mp3',
      id: '0005'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/6.jpg',
      name: 'No.9',
      author: 'T-ara',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/6.m4a',
      id: '0006'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/7.jpg',
      name: '孤独',
      author: 'G.E.M.邓紫棋',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/7.m4a',
      id: '0007'
    },

    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/8.jpg',
      name: 'Lose Control',
      author: 'Hedley',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/8.m4a',
      id: '0008'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/9.jpg',
      name: '倩女幽魂',
      author: '张国荣',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/9.m4a',
      id: '0009'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/10.jpg',
      name: '北京北京',
      author: '汪峰',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/10.m4a',
      id: '0010'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/11.jpg',
      name: '苦笑',
      author: '汪苏泷',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/11.mp3',
      id: '0011'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/12.jpg',
      name: '一生所爱',
      author: '卢冠廷 / 莫文蔚',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/12.m4a',
      id: '0012'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/13.jpg',
      name: '月半小夜曲',
      author: '李克勤',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/13.mp3',
      id: '0013'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/14.jpg',
      name: 'Rolling in the Deep',
      author: 'Adele',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/14.m4a',
      id: '0014'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/15.jpg',
      name: '海阔天空',
      author: 'Beyond',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/15.m4a',
      id: '0015'
    }
  ]

  build() {
    Column() {
      Text('猜你喜欢')
        .fontColor('#fff')
        .width('100%')
        .margin({ bottom: 10 })
      List() {
        ForEach(this.songs, (item: SongItemType, index: number) => {
          ListItem() {
            Row() {
              // 图
              Stack() {
                Image(item.img)
                  .width(80)
                  .border({ radius: 8 })
                  .margin({ right: 10 })
                Image($r('app.media.wave'))
                  .width(24)
              }

              // 字
              Column() {
                Text(item.name)
                  .fontColor('#F3F3F3')
                  .width('100%')
                  .fontWeight(700)
                  .margin({ bottom: 15 })
                Row() {
                  Text('VIP')
                    .fontColor('#9A8E28')
                    .border({ width: 1, color: '#9A8E28', radius: 12 })
                    .padding({
                      left: 5,
                      right: 5,
                      top: 3,
                      bottom: 3
                    })
                    .margin({ right: 10 })
                  Text(item.author)
                    .fontColor('#696969')
                }
                .width('100%')
              }
              .layoutWeight(1)

              // 更多
              Image($r('app.media.ic_more'))
                .width(24)
                .fillColor('#FEFEFE')
            }
            .width('100%')
            .height(80)
            // .backgroundColor(Color.Pink)
            .margin({ bottom: 10 })

          }
        })
      }
      .scrollBar(BarState.Off)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#131313')
    .padding({ left: 10, right: 10 })
    // 扩充组件安全区域
    .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
  }
}

  • 2). 播放页:src/main/ets/pages/Play.ets
📌 ​点击展开代码
typescript
import { SongItemType } from "../models/music"

// 跳转页面入口函数
@Builder
export function PlayBuilder() {
  Play()
}

@ComponentV2
struct Play {
  @Local panelHeight: string = '0%'
  @Local panelOpacity: number = 0
  pathStack : NavPathStack = new NavPathStack();

  songs: SongItemType[] = [
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/0.jpg',
      name: '直到世界的尽头',
      author: 'WANDS',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/0.m4a',
      id: '0000'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/1.jpg',
      name: '画',
      author: '赵磊',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/1.mp3',
      id: '0001'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/2.jpg',
      name: 'Sweet Dreams',
      author: 'TPaul Sax / Eurythmics',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/2.mp3',
      id: '0002'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/3.jpg',
      name: '奢香夫人',
      author: '凤凰传奇',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/3.m4a',
      id: '0003'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/4.jpg',
      name: '空心',
      author: '光泽',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/4.mp3',
      id: '0004'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/5.jpg',
      name: '反转地球',
      author: '潘玮柏',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/5.mp3',
      id: '0005'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/6.jpg',
      name: 'No.9',
      author: 'T-ara',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/6.m4a',
      id: '0006'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/7.jpg',
      name: '孤独',
      author: 'G.E.M.邓紫棋',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/7.m4a',
      id: '0007'
    },

    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/8.jpg',
      name: 'Lose Control',
      author: 'Hedley',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/8.m4a',
      id: '0008'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/9.jpg',
      name: '倩女幽魂',
      author: '张国荣',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/9.m4a',
      id: '0009'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/10.jpg',
      name: '北京北京',
      author: '汪峰',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/10.m4a',
      id: '0010'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/11.jpg',
      name: '苦笑',
      author: '汪苏泷',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/11.mp3',
      id: '0011'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/12.jpg',
      name: '一生所爱',
      author: '卢冠廷 / 莫文蔚',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/12.m4a',
      id: '0012'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/13.jpg',
      name: '月半小夜曲',
      author: '李克勤',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/13.mp3',
      id: '0013'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/14.jpg',
      name: 'Rolling in the Deep',
      author: 'Adele',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/14.m4a',
      id: '0014'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/15.jpg',
      name: '海阔天空',
      author: 'Beyond',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/15.m4a',
      id: '0015'
    }
  ]
  // 当前播放的歌曲
  @Local
  playState:SongItemType = this.songs[0]


  @Builder
  deleteButton(index: number) {
    Button('删除')
      .backgroundColor('#ec5c87')
      .fontColor('#fff')
      .width(80)
      .height('100%')
      .type(ButtonType.Normal)

  }

  number2time(number: number) {
    // 毫秒 → 秒 → 分+秒; 先判断是否大于1分钟
    if (number > 60 * 1000) {
      const s = Math.floor(number/1000%60)
      const m = Math.floor(number/1000/60)
      const second = s.toString().padStart(2, '0')
      const minute = m.toString().padStart(2, '0')
      return minute + ':' + second
    } else {
      const s = Math.floor(number/1000%60)
      const second = s.toString().padStart(2, '0')
      return '00:' + second
    }
  }

  build() {
    NavDestination() {
      Stack({ alignContent: Alignment.Bottom }) {
        // 播放
        Stack() {
          // 变色背景
          Image(this.playState.img)
            .width('100%')
            .height('100%')
            .blur(1000)
            .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])

          // 内容
          Column() {
            // //   播放界面
            Column() {
              // 图片
              Stack({ alignContent: Alignment.Top }) {
                Row() {
                  Row() {
                    Image(this.playState.img)
                      .width('70%')
                      .borderRadius(400)
                  }
                  .backgroundImage($r('app.media.ic_cd'))
                  .backgroundImageSize(ImageSize.Cover)
                  .justifyContent(FlexAlign.Center)
                  .width('100%')
                  .borderRadius(400)
                  .clip(true)
                  .aspectRatio(1)
                }
                .margin({
                  top: 50
                })
                .width('90%')
                .aspectRatio(1)
                .justifyContent(FlexAlign.Center)
                .padding(24)
                // 唱针
                Image($r('app.media.ic_stylus'))
                  .width(200)
                  .aspectRatio(1)
                  .rotate({
                    angle: -55,
                    centerX: 100,
                    centerY: 30
                  })
                  .animation({
                    duration: 500
                  })
              }
              // 歌曲信息
              Stack() {
                // 第一个
                Column({ space: 8 }) {
                  Text(this.playState.name)
                    .fontSize(28)
                    .fontWeight(FontWeight.Bold)
                    .fontColor('#4bb0c4')
                  Text(this.playState.author)
                    .fontSize(18)
                    .fontColor('#4bb0c4')
                }
                .layoutWeight(1)
                .justifyContent(FlexAlign.Center)
                .zIndex(1)
                // 第二个
                Column({ space: 8 }) {
                  Text(this.playState.name)
                    .fontSize(28)
                    .fontWeight(FontWeight.Bold)
                    .fontColor('#ec5c87')
                  Text(this.playState.author)
                    .fontSize(18)
                    .fontColor('#ec5c87')
                }
                .layoutWeight(1)
                .justifyContent(FlexAlign.Center)
                .zIndex(2)
                // 第三个
                Column({ space: 8 }) {
                  Text(this.playState.name)
                    .fontSize(28)
                    .fontWeight(FontWeight.Bold)
                    .fontColor(Color.White)
                  Text(this.playState.author)
                    .fontSize(18)
                    .fontColor(Color.White)
                }
                .layoutWeight(1)
                .justifyContent(FlexAlign.Center)
                .zIndex(3)
              }
              .layoutWeight(1)
              // 操作
              Row() {
                Badge({ value: '99+', style: { badgeSize: 12, badgeColor: '#45CCCCCC' } }) {
                  Image($r("app.media.ic_like"))
                    .fillColor(Color.White)
                    .width(24)
                }

                Badge({ value: '10W', style: { badgeSize: 12, badgeColor: '#45cccccc' } }) {
                  Image($r("app.media.ic_comment_o"))
                    .fillColor(Color.White)
                    .width(18)
                }

                Badge({ value: 'hot', style: { badgeSize: 12, badgeColor: '#a8ff3131' } }) {
                  Image($r("app.media.ic_bells_o"))
                    .fillColor(Color.White)
                    .width(24)
                }

                Badge({ value: 'vip', style: { badgeSize: 12, badgeColor: '#b7efd371' } }) {
                  Image($r("app.media.ic_download_o"))
                    .fillColor(Color.White)
                    .width(24)
                }
              }
              .width('100%')
              .justifyContent(FlexAlign.SpaceAround)
              // 播放
              Column() {
                // 进度条
                Row({ space: 4 }) {
                  Text("00:00")
                    .fontSize(12)
                    .fontColor(Color.White)
                  Slider({
                    value: 0,
                    min: 0,
                    max: 0
                  })
                    .layoutWeight(1)
                    .blockColor(Color.White)
                    .selectedColor(Color.White)
                    .trackColor('#ccc5c5c5')
                    .trackThickness(2)
                  Text("00:00")
                    .fontSize(12)
                    .fontColor(Color.White)
                }
                .width('100%')
                .padding(24)
                // 切换
                Row() {
                  Image($r('app.media.ic_auto'))
                    .fillColor(Color.White)
                    .width(30)

                  Image($r("app.media.ic_prev"))
                    .fillColor(Color.White)
                    .width(30)

                  // 播放按钮
                  Image($r('app.media.ic_paused'))
                    .fillColor(Color.White)
                    .width(50)
                  // 下一首
                  Image($r('app.media.ic_next'))
                    .fillColor(Color.White)
                    .width(30)
                  // 播放列表
                  Image($r('app.media.ic_song_list'))
                    .fillColor(Color.White)
                    .width(30)
                    .onClick(() => {
                      this.panelHeight = '50%'
                      this.panelOpacity = 1
                    })
                }
                .width('100%')
                .padding({
                  bottom: 24
                })
                .justifyContent(FlexAlign.SpaceAround)
              }
              .width('100%')
            }
            .layoutWeight(1)
            .width('100%')
          }
        }
        .width('100%')
        .height('100%')
        .backgroundColor(Color.Transparent)


        // 列表
        Column() {
          Column() {

          }
          .width('100%')
          .layoutWeight(1)
          .onClick(() => {
            this.panelHeight = '0%'
            this.panelOpacity = 0
          })

          Column() {
            Row() {
              Row() {
                Image($r("app.media.ic_play"))
                  .width(20)
                  .fillColor('#ff5186')
              }
              .width(50)
              .aspectRatio(1)
              .justifyContent(FlexAlign.Center)

              Row({ space: 8 }) {
                Text(`播放列表 (0)`)
                  .fontColor(Color.White)
                  .fontSize(14)
              }
              .layoutWeight(1)

              Image($r('app.media.ic_close'))
                .fillColor('#ffa49a9a')
                .width(24)
                .height(24)
                .margin({ right: 16 })
                .onClick(() => {
                  this.panelHeight = '0%'
                  this.panelOpacity = 0
                })
            }
            .width('100%')
            .backgroundColor('#ff353333')
            .padding(8)
            .border({
              width: { bottom: 1 },
              color: '#12ec5c87'
            })
            .borderRadius({
              topLeft: 4,
              topRight: 4
            })

            // 播放列表
            List() {
              ForEach(this.songs, (item: SongItemType, index: number) => {
                ListItem() {
                  Row() {
                    Row() {
                      Text((index + 1).toString())
                        .fontColor('#ffa49a9a')
                    }
                    .width(50)
                    .aspectRatio(1)
                    .justifyContent(FlexAlign.Center)

                    // 列表
                    Row({ space: 10 }) {
                      Column() {
                        Text(item.name)
                          .fontSize(14)
                          .fontColor('#ffa49a9a')
                        Text(item.author)
                          .fontSize(12)
                          .fontColor( Color.Gray)
                      }
                      .layoutWeight(1)
                      .alignItems(HorizontalAlign.Start)
                      .justifyContent(FlexAlign.Center)
                    }
                    .layoutWeight(1)

                    Image($r('app.media.ic_more'))
                      .width(24)
                      .height(24)
                      .margin({ right: 16 })
                      .fillColor(Color.Gray)
                  }
                  .alignItems(VerticalAlign.Center)
                }
                .swipeAction({
                  end: this.deleteButton(index)
                })
                .border({
                  width: { bottom: 1 },
                  color: '#12ec5c87'
                })
              })
            }
            .layoutWeight(1)
            .backgroundColor('#ff353333')

          }
          .height(400)
        }
        .height(this.panelHeight)
        // .height('100%')
        .animation({
          duration: 300
        })
        .backgroundColor('#ff353333')
        .opacity(this.panelOpacity)
        .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])


      }
      .width('100%')
      .height('100%')
      .backgroundColor(Color.Transparent)

    }
    .onReady((context: NavDestinationContext) => {
      //当组件准备好的时候,把自己放进控制跳转对象里,那么自己就是能跳转的对象之一
      this.pathStack = context.pathStack
    })
    .hideTitleBar(true)  // 隐藏标题栏
  }
}

7.5.3 跳转


  • 1). 路由表 router_map.json 增加 播放页 Play
微信截图_20251001155741
  • 2). 使用 AppstorageV2 共享控制跳转的对象 pathStack :
📌 官方文档:
  • 介绍:
    • 为了增强状态管理框架对 应用全局UI状态变量 存储的能力,

      开发者可以使用 AppStorageV2 存储 应用全局UI状态变量 数据。

    • AppStorageV2 是提供状态变量在应用级全局共享的能力,

      开发者可以通过 connect 绑定同一个 key,进行跨 ability 的数据共享。


  • 概述:

    • AppStorageV2 是在应用 U I启动时 会被创建的单例。

      它的目的是为了提供应用状态数据的中心存储,这些状态数据在应用级别都是可访问的。

      AppStorageV2 将在应用运行过程保留其数据。数据通过唯一的键字符串值访问。

      需要注意的是,AppStorageAppStorageV2 之间的数据互不共享。

    • AppStorageV2 可以修改 connect 的返回值,实现与 UI 组件的同步。

    • AppStorageV2 支持应用的 主线程 内多个UIAbility 实例间的状态共享。


  • 使用说明:

    • connect:创建或获取存储的数据。

      • 说明

        1. 若未指定 key,使用第二个参数作为默认构造器;

          否则使用第三个参数作为默认构造器(第二个参数非法也使用第三个参数作为默认构造器)。

        2. 确保数据已经存储在 AppStorageV2 中,可省略默认构造器,获取存储的数据;

          否则必须指定默认构造器,不指定将导致应用异常。

        3. 同一个 key,connect 不同类型的数据会导致应用异常,应用需要确保类型匹配。

        4. key 建议使用有意义的值,可由字母、数字、下划线组成,长度不超过255,

          使用非法字符或空字符的行为是未定义的。

        5. 关联 @Observed 对象时,由于该类型的 name 属性未定义,

        需要指定key或者自定义name属性。

    • remove:删除指定 key 的存储数据。

      • 删除 AppStorageV2 中不存在的 key 会报警告。
    • keys:返回所有 AppStorageV2 中的 key。

以上接口详细描述请参考 状态管理API指南


  • 主要关于 connect :
typescript
static connect<T extends object>(

    type: TypeConstructorWithArgs<T>, //指定的类型

    keyOrDefaultCreator?: string | StorageDefaultCreator<T>, //指定一个key

    defaultCreator?: StorageDefaultCreator<T> //初始值(构造器)

): T | undefined
微信截图_20251001162043

index.ets

typescript
import { AppStorageV2 } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  //我们在子页的跳转对象叫 pathStack , 所以这里也注意修改成 pathStack
  // pathStack : NavPathStack = new NavPathStack();
  pathStack : NavPathStack = AppStorageV2.connect(NavPathStack,'navStack',() => new NavPathStack())!

  build() {
    Navigation(this.pathStack){
    }.onAppear(() => {
      //通过名称Name推路径(其实就是跳转页面)
      //软件一打开应当打开的是广告页,所以跳转
      this.pathStack.pushPathByName("Start", null, false);
    })
    .hideNavBar(true) //不会把自己放到控制跳转对象(本质是不会进Stack栈)里面 -> 点返回不会返回到这个导航页
  }
}

📌 说明:

  • 我们经常会看到 ? 或者 ! , 比如上面的: (❓ ​那么到底都是什么意思)
typescript
pathStack : NavPathStack = AppStorageV2.connect(NavPathStack,'navStack',() => new NavPathStack())!

这句代码最后面的 !


  • 可选属性 / 可选参数 :?
typescript
interface Person {
  name: string;
  age?: number; // age 是可选属性
}

let person: Person = {
  name: "Alice"
};

console.log(person.age); // 输出:undefined

typescript
// 定义一个函数,其中第二个参数是可选的
function greet(name: string, greeting?: string) {
  if (greeting) {
    console.log(`${greeting}, ${name}!`);
  } else {
    console.log(`Hello, ${name}!`);
  }
}

// 调用函数时,可以省略可选参数
greet("Alice"); // 输出:Hello, Alice!
greet("Bob", "Good morning"); // 输出:Good morning, Bob!

  • 可选链(Optional Chaining)运算符 ?.

    • 它用于在访问对象的属性或调用对象的方法时,

      确保对象不是 nullundefined,从而避免运行时错误。

typescript
type Person = {
  name: string;
  address?: {
    city: string;
    zipCode: string;
  };
};
  • 📌 注意 : typeinterface 的区别

    • interface 只能用于定义对象类型和函数类型。
    • type 可以用于定义任何类型,包括基本类型、联合类型、元组等。
    • interface 支持扩展和合并,而 type 不支持。
  • 在上面的代码块中如果要访问对象的属性:

    • person.address?.city:如果 person.addressnullundefined

      则不会尝试访问 city 属性,直接返回 undefined

    • 如果 person.address 存在,则正常访问 city 属性。


  • 非空断言:!

    • 当你确信一个值不会是 nullundefined,但 TypeScript 编译器无法推断这一点时,

      可以使用 ! 来断言该值是非空的。

    • 使用 ! 时需要谨慎,因为如果值实际上是 nullundefined,运行时会抛出错误。


  • 3). 其它页面使用 AppstorageV2 使用共享的控制跳转对象 pathStack :

在其他页面,比如 Find.ets 写上同样的下面的代码:

typescript
pathStack : NavPathStack = AppStorageV2.connect(NavPathStack,'navStack',() => new NavPathStack())!
微信截图_20251001163148
  • 4). 使用控制跳转对象实现 Find.ets 跳转到 Play.ets :
微信截图_20251001163723

说明:现在播放页的页面是写死的,无论点哪个都是同一首歌的信息,后面再进行优化就好了


7.5.4 基础播放功能


  • 播放功能:(使用 AVPlayer 播放音频 (ArkTS) )
  • AVPlayer 播放流程:AVPlayer 官方文档
    • 创建 AVPlayer
    • 设置播放资源
    • 设置播放参数(音量/倍速/焦点模式)
    • 播放控制(播放/暂停/跳转/停止)
    • 重置
    • 销毁资源
0000000000011111111.20250930135432.95159710182455718810890938279591 (1)
微信截图_20251001200722
  • 说明:像上面这样,这种具有大量数据和功能的,

    我们最好应该统一管理比较好,所以最好 封装播放器工具类

微信截图_20251001200915
  • 1). 创建一个 utils 目录, 下面创建一个 AvPlayerManager.ets :
微信截图_20251001202954
  • 2). 编写 AvPlayerManager.ets 基本的代码如 init() 实现基础的播放功能:

  • 会遇到如下问题:
微信截图_20251001203645
  • 说明:需要等待的话,那么我们就需要用到 await 关键字,并且需要用 async 修饰用到 await 的方法

  • 示例代码:

➡️ AvPlayerManager.ets

typescript
import { media } from '@kit.MediaKit'
import { SongItemType } from '../models/music'

class AvPlayerManager{
  //属性 + 方法
  //播放器
  player : media.AVPlayer | null = null

  //定义初始化方法:创建播放器 + 监听播放器状态
  async init() {
    if(!this.player){
      this.player = await media.createAVPlayer()
    }
    
    //监听状态
    this.player.on('stateChange',(state) => {
      if(state === 'initialized') {
        this.player?.prepare()
      }else if(state === 'prepared'){
        this.player?.play()
      }
    })
  }
  
  //播放歌曲 → 设置播放资源
  singPlay (song:SongItemType){
    this.player!.url = song.url
  }
}

//new 出来一个对象导出给其它地方使用
export const playManager : AvPlayerManager = new AvPlayerManager()

  • 3). 下一步我们需要考虑的是打开软件的那一刻,把这个 init() 给调用一下

➡️ EntryAbility.ets

微信截图_20251001204548
  • 4). 然后就是在发现页点击音乐 onClick() 那一刻跳转到播放器并且播放对应音乐:
微信截图_20251001204723
  • 5). 接下来就是重新运行项目,打开模拟器点击一首歌就会发现已经可以播放音乐了

7.5.5 共享播放数据


7.5.5.1 方案

  • 7.5.4 我们做了基础的播放功能,可以发现,无论我们点哪个都是固定的一个界面。

    应该要实现的是点了哪首歌就显示哪首歌相关的信息,而且,这个项目后面还有播控中心,

    这也需要用到播放的相关歌的信息,而且 AvPlayer 是不是也需要用到相关歌的信息 ...

微信截图_20251001211203

那么就会想到 .............

微信截图_20251001211242
  • 共享播放数据的使用思路:
微信截图_20251001211514
7.5.5.2 共享音乐图片

  • 1). 在 models 下封装一个共享播放数据的类

➡️ models/globalMusic.ets

typescript
export class GlobalMusic {
  img: string = ""  // 音乐封面
  name: string = ""  // 音乐名称
  author: string = ""  // 作者
  url: string = ""  // 当前播放链接
  time: number = 0  // 播放时间
  duration: number = 0  // 歌曲总时长
}

  • 2). 在 AvplayerManager.ets 里面使用 AppstorageV2 全局共享播放数据
微信截图_20251001212457
typescript
//共享播放数据
currentSong : GlobalMusic = AppStorageV2.connect(GlobalMusic,'SONG_KEY',() => new GlobalMusic())!

  • 3). 先初步的在 SingPlay 里面传递播放的 img 给全局的播放数据:
微信截图_20251001212716
  • 4). 接下来就是哪里需要这个共享数据就哪里取 → 播放页需要:

  • 当我们打开复制的 Play 页面的时候 → 破案了,之前写死的数据如下:

微信截图_20251001212859
  • 修改为 AppstorageV2 共享出来的数据:
微信截图_20251001213205
  • 5). 重新运行项目,测试:
微信截图_20251001213321

发现图片已经是我们点击的那个音乐对应的图片了,其他相关数据后续继续改就好了


7.5.5.3 共享歌曲时间

  • 任务概述:
微信截图_20251001215133
  • 实现思路:
微信截图_20251001215214
  • 先尝试着能不能监听到歌曲的总时长(❗ 剧透:会出现意外):

➡️ AvPlayManager.ets

微信截图_20251001215741
  • 说明:之前复制的代码有一个函数 number2time 作用是:把 毫秒(计算机看) 转化为 分+秒(人看)
微信截图_20251001220137

➡️ Play.ets

微信截图_20251001220250
  • 修改后:
微信截图_20251001220448
  • 修改后总时长不会发生改变,这就是前面说的 "❗ ​意外"

  • 说明:简单的 @AppstorageV2 无法深度地监测到类中属性值的变化。

    官方文档:那么为了增强状态管理框架对类对象中属性的观测能力,

    开发者可以使用 @ObservedV2 装饰器和 @Trace 装饰器装饰类以及类中的属性。)

➡️ 修改 globalMusic.ets

typescript
@ObservedV2
export class GlobalMusic {
  @Trace img: string = ""  // 音乐封面
  @Trace name: string = ""  // 音乐名称
  @Trace author: string = ""  // 作者
  @Trace url: string = ""  // 当前播放链接
  @Trace time: number = 0  // 播放时间
  @Trace duration: number = 0  // 歌曲总时长
}

  • 重新运行项目,测试:
微信截图_20251001221320

可以发现效果实现了!


  • 接下来就是实现监听播放时长的变化:

➡️ AvPlayManager.ets

微信截图_20251001221547

➡️ Play.ets

微信截图_20251001221609

测试:

微信截图_20251001221645
  • 接下来就是修改一下 进度条 的部分:
微信截图_20251001222102

那么先在 AvPlayerManager.ets 实现一下跳转的方法,然后再后面在 Slider.onChange() 这里调用

➡️ AvPlayerManager.ets

微信截图_20251001222420

➡️ Play.ets

微信截图_20251001222540

重新运行项目,测试:

微信截图_20251001223548

7.5.6 播控功能


7.5.6.1 歌曲列表

  • 任务:点击歌曲,添加歌曲到播放列表并播放歌曲
微信截图_20251002125819
  • 1). 首先我们需要在共享歌曲数据 GlobalMusic 这里添加两个属性:
typescript
import { SongItemType } from "./music"

@ObservedV2
export class GlobalMusic {
  @Trace img: string = ""  // 音乐封面
  @Trace name: string = ""  // 音乐名称
  @Trace author: string = ""  // 作者
  @Trace url: string = ""  // 当前播放链接
  @Trace time: number = 0  // 播放时间
  @Trace duration: number = 0  // 歌曲总时长
  
  //歌曲列表
  @Trace playIndex : number = 0 // 当前播放的音乐
  @Trace playList : SongItemType[] = [] // 播放列表
}

  • 2). 在 AvPlayerManager.ets 里面播放歌曲的方法 singPlay() 做修改:
typescript
singPlay (song:SongItemType){
    // 是否在列表里 → some 检查数组里面的数据是否存在满足条件的 → 只要有一个满足 some 返回 true
    const inList = this.currentSong.playList.some(item => item.id === song.id)
    if (inList) {
        //在列表里面 → 是否是正在播放的 → currentSong url === song.url
        if (this.currentSong.url === song.url) {
            this.player?.play()
        } else {
            //设置新的索引 → 切歌
            this.currentSong.playIndex = this.currentSong.playList.findIndex(item => item.id === song.id)

            //切歌 ?
        }
    } else {
        //不在列表里面 → 添加到列表 + 切换歌曲
        this.currentSong.playList.unshift(song) // 把要播放的歌添加到队首
        this.currentSong.playIndex = 0
        //切换歌曲 ?
    }
}

目前切歌的方法我们还没实现,暂时是这样的代码


  • 3). 补充切换歌曲的方法 changeSong()

(说明:这里同时也补充了前面给共享音乐数据其他属性赋值 , 前面只赋值了 img )

typescript
//切换歌曲
async changeSong() {
    await this.player?.reset()
    this.currentSong.duration = 0
    this.currentSong.time = 0
    this.currentSong.img = this.currentSong.playList[this.currentSong.playIndex].img
    this.currentSong.name = this.currentSong.playList[this.currentSong.playIndex].name
    this.currentSong.author = this.currentSong.playList[this.currentSong.playIndex].author
    this.currentSong.url = this.currentSong.playList[this.currentSong.playIndex].url
    this.player!.url = this.currentSong.url
}

  • 4). 补充切换歌曲方法到 singPlay()
typescript
singPlay (song:SongItemType){
    // 是否在列表里 → some 检查数组里面的数据是否存在满足条件的 → 只要有一个满足 some 返回 true
    const inList = this.currentSong.playList.some(item => item.id === song.id)
    if (inList) {
      //在列表里面 → 是否是正在播放的 → currentSong url === song.url
      if (this.currentSong.url === song.url) {
        this.player?.play()
      } else {
        //设置新的索引 → 切歌
        this.currentSong.playIndex = this.currentSong.playList.findIndex(item => item.id === song.id)

        //切歌
        this.changeSong() 
      }
    } else {
      //不在列表里面 → 添加到列表 + 切换歌曲
      this.currentSong.playList.unshift(song) // 把要播放的歌添加到队首
      this.currentSong.playIndex = 0
      //切换歌曲 
      this.changeSong() 
    }
  }

  • 5). 重新运行项目,在模拟器测试,目前已经可以完成简单的切歌了

7.5.6.2 播放&暂停

微信截图_20251002144955
  • 1). 先修改共享歌曲数据 GlobalMusic
typescript
import { SongItemType } from "./music"

@ObservedV2
export class GlobalMusic {
  @Trace img: string = ""  // 音乐封面
  @Trace name: string = ""  // 音乐名称
  @Trace author: string = ""  // 作者
  @Trace url: string = ""  // 当前播放链接
  @Trace time: number = 0  // 播放时间
  @Trace duration: number = 0  // 歌曲总时长

  //歌曲列表
  @Trace playIndex : number = 0 // 当前播放的音乐
  @Trace playList : SongItemType[] = [] // 播放列表

  //播放&暂停
  @Trace isPlay : boolean = false  // 播放的状态
}

  • 2). 修改 AvPlayerManager.ets 需要更新播放状态的地方,并且添加暂停的方法 paused()
typescript
import { media } from '@kit.MediaKit'
import { GlobalMusic } from '../models/globalMusic'
import { SongItemType } from '../models/music'
import { AppStorageV2 } from '@kit.ArkUI'

class AvPlayerManager{
  //属性 + 方法
  //播放器
  player : media.AVPlayer | null = null

  //共享播放数据
  currentSong : GlobalMusic = AppStorageV2.connect(GlobalMusic,'SONG_KEY',() => new GlobalMusic())!

  //定义初始化方法:创建播放器 + 监听播放器状态
  async init() {
    if(!this.player){
      this.player = await media.createAVPlayer()
    }

    //监听状态
    this.player.on('stateChange',(state) => {
      if(state === 'initialized') {
        this.player?.prepare()
      }else if(state === 'prepared'){
        this.player?.play()
        this.currentSong.isPlay = true
      }
    })

    //监听歌曲总时长
    this.player.on('durationUpdate',(duration) => {
      //总时长有变化更新到全局共享数据
      this.currentSong.duration = duration
    })

    //监听播放时长的变化
    this.player.on('timeUpdate',(time) => {
      this.currentSong.time = time
    })
  }

  //播放歌曲 → 设置播放资源
  // singPlay (song:SongItemType){
  //   this.player!.url = song.url
  //   this.currentSong.img = song.img
  // }
  singPlay (song:SongItemType){
    // 是否在列表里 → some 检查数组里面的数据是否存在满足条件的 → 只要有一个满足 some 返回 true
    const inList = this.currentSong.playList.some(item => item.id === song.id)
    if (inList) {
      //在列表里面 → 是否是正在播放的 → currentSong url === song.url
      if (this.currentSong.url === song.url) {
        this.player?.play()
        this.currentSong.isPlay = true
      } else {
        //设置新的索引 → 切歌
        this.currentSong.playIndex = this.currentSong.playList.findIndex(item => item.id === song.id)

        //切歌
        this.changeSong()
      }
    } else {
      //不在列表里面 → 添加到列表 + 切换歌曲
      this.currentSong.playList.unshift(song) // 把要播放的歌添加到队首
      this.currentSong.playIndex = 0
      //切换歌曲
      this.changeSong()
    }
  }

  //切换歌曲
  async changeSong() {
    await this.player?.reset()
    this.currentSong.duration = 0
    this.currentSong.time = 0
    this.currentSong.img = this.currentSong.playList[this.currentSong.playIndex].img
    this.currentSong.name = this.currentSong.playList[this.currentSong.playIndex].name
    this.currentSong.author = this.currentSong.playList[this.currentSong.playIndex].author
    this.currentSong.url = this.currentSong.playList[this.currentSong.playIndex].url
    this.player!.url = this.currentSong.url
  }

  //跳转进度 seek
  seekPlay(value : number){
    this.player?.seek(value)
  }

  //暂停播放
  paused() {
    this.player?.pause() //播放器暂停
    this.currentSong.isPlay = false //修改状态
  }
}

//new 出来一个对象导出给其它地方使用
export const playManager : AvPlayerManager = new AvPlayerManager()

  • 3). 在 Play.ets 做更改,比如暂停播放图片的更改,还有点击事件:
微信截图_20251002145932
  • 5). 重新运行项目,在模拟器测试,目前已经可以暂停&播放了

  • 6). 接着还有一个问题就是:所有歌都有 wave.gif , 我们需要像之前一样添加一个条件渲染
微信截图_20251002151231
  • 6.1). 把全局共享音乐数据拿到发现页:
微信截图_20251002151741
  • 6.2). 条件渲染 wave.gif
微信截图_20251002152151
  • 7). 重新运行项目,在模拟器测试,就可以实现播放的歌曲才有渲染效果:
微信截图_20251002152135
7.5.6.3 上一首&下一首

微信截图_20251002152518
  • 1). 在 AvPlayerManager.ets 增加 上一首 & 下一首 的方法:
微信截图_20251002153321
  • 2). 在播放页 Play.ets 对应位置调用方法:
微信截图_20251002153937
  • 3). 重新运行项目,打开模拟器测试,记得要先选几首歌(之前的逻辑是选歌才能进数组,视觉数组是假的)

发现切歌是实现了,但是还是会有瑕疵, 这里先暂时不管,后面再根据实际的歌曲软件进行优化...


7.5.6.4 切换播放模式

  • 思路:
微信截图_20251002155940
  • 1). 先修改共享歌曲数据 GlobalMusic
typescript
import { SongItemType } from "./music"

@ObservedV2
export class GlobalMusic {
  @Trace img: string = ""  // 音乐封面
  @Trace name: string = ""  // 音乐名称
  @Trace author: string = ""  // 作者
  @Trace url: string = ""  // 当前播放链接
  @Trace time: number = 0  // 播放时间
  @Trace duration: number = 0  // 歌曲总时长

  //歌曲列表
  @Trace playIndex : number = 0 // 当前播放的音乐
  @Trace playList : SongItemType[] = [] // 播放列表

  //播放&暂停
  @Trace isPlay : boolean = false  // 播放的状态
  
  //播放模式
  @Trace playMode : 'auto' | 'random' | 'repeat' = 'auto'
}

  • 2). 有了对应数据,那么可以在 Play 页面实现一下 "视觉上" 的播放模式切换 (条件渲染):
微信截图_20251002160801
    1. . 重新运行项目, 在模拟器上测试,就发现实现了 "视觉上" 的播放模式切换

  • 4). 那么现在播放模式还没有真正的改变,只是视觉上实现了。接下来就来实现播放模式的功能,

    本质上播放模式影响上一首下一首,我们只需要完成 下一首 对应的功能,那么上一首就也完成了 ...

微信截图_20251002164106
微信截图_20251002162542
  • 5). 修改 Play 页面播放列表的展示,之前一直是 this.songs 展示全部歌曲,

    现在我们改成真实在列表里面的歌曲才渲染出来,方便我们调试 ...

微信截图_20251002162857
  • 6). 重新运行项目,使用模拟器,加入一定量的歌曲测试一下刚刚的下一首功能能不能实现

  • 7). 然后补充 上一首 的逻辑,其实是更简单,因为没有 自动播放下一首 → 重复播放 的情况:
微信截图_20251002163942
7.5.6.5 播放列表

  • 任务:
    • 渲染列表内容(长度,列表歌单内容)
    • 点击播放歌曲
    • 滑动移除歌曲

  • 1). 播放列表长度:
微信截图_20251002164737
  • 2). 播放列表点击切歌:
微信截图_20251002165047
  • 3). 重新运行项目,打开模拟器测试

  • 4). 分析删除歌曲:
微信截图_20251002165412
  • 5). 在 globalMusic 添加一个重置数据的方法:
微信截图_20251002170729
  • 6). 在 AvplayerManager 添加删除歌曲的方法:
微信截图_20251002171852

记得回退页面要在前面补充上:

typescript
pathStack : NavPathStack = AppStorageV2.connect(NavPathStack,'navStack',() => new NavPathStack())!

  • 7). 在播放页 Play 调用方法:
微信截图_20251002170917
微信截图_20251002170858
  • 8). 重新运行项目,打开模拟器测试功能

7.5.7 播控中心


7.5.7.1 概述

  • 功能结构:
微信截图_20251002172800
微信截图_20251002172912
7.5.7.2 后台播放

  • 1). 因为需要接入媒体会话 AvSession ,所以最好我们像前面的 AvPlayer 那样,

    utils 目录下创建一个工具类 AvSessionManager.ets :

typescript
import { avSession } from '@kit.AVSessionKit'

class AvSessionManager {
  session : avSession.AVSession | null = null

  async init(content : Context) {
    this.session = await avSession.createAVSession(content,'bgPlay','audio')
  }
}

export const sessionManager : AvSessionManager = new AvSessionManager()

  • 2). 然后再软件启动的时候要进行初始化:
微信截图_20251002174200

  • 3.1). 开通 ohos.permission.KEEP_BACKGROUND_RUNNING 权限:
微信截图_20251002174910
  • 3.2). 声明后台模式类型 :
微信截图_20251002175049
  • 3.3). 在 AvSession 工具类中增加一个申请长时任务的方法 startBackgroundTask()
微信截图_20251002180452
  • 4). 在歌曲播放的时候调用申请长时任务的方法:
微信截图_20251002180648
  • 5). 重新运行项目,打开模拟器测试功能

7.5.7.3 同步播控中心

  • 流程:
微信截图_20251002181029

应用可以通过 setAVMetadata 把会话的一些元数据信息设置给系统,从而在播控中心界面进行展示


  • 1). 在 AvSessionManager 里面增加一个设置元数据的方法 setAVMetadata() :
微信截图_20251002181815

需要获取全局共享歌曲数据:

typescript
playState : GlobalMusic = AppStorageV2.connect(GlobalMusic,'SONG_KEY', () => new GlobalMusic())!

  • 2). 把元数据同步给播控中心 (在换歌的时候需要同步):
微信截图_20251002183008
  • 3). 通过 setAVPlaybackState ,把当前的播放状态设置给系统:
微信截图_20251002183604
  • 4). 接下来就要思考一下在哪里调用它:
    • 播放 singPlay / 暂停 paused 状态改变的时候
    • 歌曲播放时间变化 timeUpdate 的时候
微信截图_20251002183810
微信截图_20251002183921
微信截图_20251002183937
  • 5). 注册控制命令(通过 on 接口 ), 并激活 session
微信截图_20251002184813
微信截图_20251002184821
  • 6). 注销会话,释放资源:

➡️ AvSessionManager

微信截图_20251002185036

➡️ AvPlayerManager

微信截图_20251002185237
微信截图_20251002185321

➡️ EntryAbility

微信截图_20251002185435
  • 7). 重新运行项目,打开模拟器测试功能
微信截图_20251002185728

现在播控中心的功能也是没问题的 ...


7.6 动态&我的


7.6.1 cursor


微信截图_20251002190553

  • 下载插件:
微信截图_20251002190650
  • 汉化插件:
微信截图_20251002190657
  • ArkTS 插件:
微信截图_20251002190923
  • 会话规则:
微信截图_20251002191013
微信截图_20251002191033
typescript
# 基本规则
- 总是用中文回复
- Harmony 项目的 ForEach 循环必须添加类型,没用上的_也必须添加类型
- 对象类型必须用 interface 抽离
- 图片素材在 entry/src/main/resources/base/media,需通过 $r('app.media.') 访问图片
- 组件的必传属性需初始化
- 图片类型为 ResourceStr
- interface 写在最顶部
- build 的根标签如果填充颜色的话,需写上 .expandSafeArea()
- RowColumn 的子组件控制间距尽量用 space 参数,尽量少用 margin
- 白色和黑色用 Color.WhiteColor.Black 代替

# 布局模式
- 线性布局 (Row/Column)
- 层叠布局 (Stack)
- 弹性布局 (Flex)
- 相对布局 (RelativeContainer)
- 栅格布局 (GridRow/GridCol)
- 创建列表 (List)
- 创建网格 (Grid/GridItem)
- 创建轮播 (Swiper)

# 滚动相关
- 支持滚动和滑动的组件 List/ListItem/ListItemGroupGrid/GridItemScrollSwiperWaterFlow/FlowItemScrollBarRefreshSwipeRefresher
- 嵌套滚动可使用 nestedScroll 属性,实现与父组件的滚动联动

  • 打开项目:
微信截图_20251002191652

7.6.2 动态


  • 效果图:
image

📌 AI 结构化提示词 = 目标 + 要求 + 注意事项 + 配套素材


📌 提示词
typescript
帮我把上面的界面用代码实现出来,
需求如下:
1. 界面为纵向布局
2. 上面是 互动广场 标题
3. 下面是 互动内容,内容根据 下方数据 ForEach 循环渲染
3.1 用户头像 和 描述内容为水平布局
3.2 用户头像 顶部对齐
注意:文字颜色和背景色必须根据上面图片设置,不能随意设置
数据如下:
songs: songItemType[] = [
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/0.jpg',
      name: '直到世界的尽头',
      author: 'WANDS',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/0.m4a',
      id: '0000'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/1.jpg',
      name: '画',
      author: '赵磊',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/1.mp3',
      id: '0001'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/2.jpg',
      name: 'Sweet Dreams',
      author: 'TPaul Sax / Eurythmics',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/2.mp3',
      id: '0002'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/3.jpg',
      name: '奢香夫人',
      author: '凤凰传奇',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/3.m4a',
      id: '0003'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/4.jpg',
      name: '空心',
      author: '光泽',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/4.mp3',
      id: '0004'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/5.jpg',
      name: '反转地球',
      author: '潘玮柏',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/5.mp3',
      id: '0005'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/6.jpg',
      name: 'No.9',
      author: 'T-ara',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/6.m4a',
      id: '0006'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/7.jpg',
      name: '孤独',
      author: 'G.E.M.邓紫棋',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/7.m4a',
      id: '0007'
    },

    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/8.jpg',
      name: 'Lose Control',
      author: 'Hedley',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/8.m4a',
      id: '0008'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/9.jpg',
      name: '倩女幽魂',
      author: '张国荣',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/9.m4a',
      id: '0009'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/10.jpg',
      name: '北京北京',
      author: '汪峰',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/10.m4a',
      id: '0010'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/11.jpg',
      name: '苦笑',
      author: '汪苏泷',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/11.mp3',
      id: '0011'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/12.jpg',
      name: '一生所爱',
      author: '卢冠廷 / 莫文蔚',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/12.m4a',
      id: '0012'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/13.jpg',
      name: '月半小夜曲',
      author: '李克勤',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/13.mp3',
      id: '0013'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/14.jpg',
      name: 'Rolling in the Deep',
      author: 'Adele',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/14.m4a',
      id: '0014'
    },
    {
      img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/15.jpg',
      name: '海阔天空',
      author: 'Beyond',
      url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/15.m4a',
      id: '0015'
    }
  ]
momentList: momentListType[] = [
    {
      author: 'Feast-aws',
      avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h0.jpg',
      content: '二十几岁的你,一无所有,也拥有一切!',
      comment: 604,
      like: 23382,
      song: this.songs[0]
    },
    {
      author: 'CeeYet',
      avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h1.jpg',
      content: '画一个姑娘陪着我,再画一个姑娘陪着我',
      comment: 1237,
      like: 5482,
      song: this.songs[1]
    },
    {
      author: 'Z_HOUSC',
      avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h2.jpg',
      content: '这个事还得从两个职业选手说起',
      comment: 8425,
      like: 4234,
      song: this.songs[2]
    },
    {
      author: '逆转大师',
      avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h3.jpg',
      content: '听奢香夫人,做上岸女人!',
      comment: 7658,
      like: 11545,
      song: this.songs[3]
    },
    {
      author: 'Moriaty',
      avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h4.jpg',
      content: '别一厢情愿的付出,他如果拒绝你了就别再纠缠了,别一次次满满热情得到的都是敷衍冷淡的回复,你不该这样的,你应该酷一点,别打扰真的是最好的选择。',
      comment: 543,
      like: 2234,
      song: this.songs[4]
    },
    {
      author: '丶你要去哪里',
      avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h5.jpg',
      content: '当年会一段潘帅的RAP是很拉风的事情',
      comment: 14415,
      like: 36297,
      song: this.songs[5]
    },
    {
      author: '一个人的某泥',
      avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h6.jpg',
      content: '抗韩二十年,死于朴智妍',
      comment: 1237,
      like: 5482,
      song: this.songs[6]
    },
    {
      author: '曾经那个少年1993',
      avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h7.jpg',
      content: '一个人去游泳,像投河;倒过来,一个人去投河,像游泳,太孤独。 ———-陈小三',
      comment: 7628,
      like: 36448,
      song: this.songs[7]
    },
    {
      author: '柳崽崽阿',
      avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h8.jpg',
      content: '哎呦,B站来的跟我一起摇💃',
      comment: 1237,
      like: 5482,
      song: this.songs[8]
    },
    {
      author: '李富贵他哥',
      avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h9.jpg',
      content: '我弟弟老是偷我的裙子丝袜穿,我担心他在女装的道路上越走越远,这让我这个当哥哥的很是担心啊',
      comment: 7362,
      like: 11482,
      song: this.songs[9]
    },
    {
      author: '一檀溪一',
      avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h10.jpg',
      content: '想起张雪峰说过的全世界最大的社区天通苑,住了400w人,光是地铁修了三站,去赶早上第一班天通苑的地铁,那才是真正的北京,真的是哭辽',
      comment: 6496,
      like: 8526,
      song: this.songs[10]
    },
    {
      author: '-_Dimple_',
      avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h11.jpg',
      content: '今天为了赶地铁在电梯上摔了一跤,手磕破了,马上起来进了地铁结果发现一着急坐反了。某一刻觉得自己长大了,摔倒了不是先难受,想哭觉得丢脸,而是想着快点赶上地铁。大概成年人的无奈就是没时间让我难过一会儿吧。',
      comment: 1237,
      like: 5482,
      song: this.songs[11]
    },
    {
      author: 'h奔放小青年',
      avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h12.jpg',
      content: '关于这部电影看到过一个评论:当年大学毕业以后我和女友各奔东西,曾经一个晚上,偶然又看了这部电影,当至尊宝拥吻紫霞的时候荡气回肠的歌声也让我潸然泪下。。。。。第二天我打起行囊踏上火车往她家赶,如今十年过去,我们已经有了一个可爱的女儿。 我想,这是对一部电影最好的肯定吧',
      comment: 1237,
      like: 5482,
      song: this.songs[12]
    },
    {
      author: '您的坑友已上线',
      avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h13.jpg',
      content: '李克勤说他唱了三十多年的歌,当接下面具的时候还很紧张,还很担心自己能不能被大家认出来,然后当《红日》想起来的时候,自己都被感动了。前天看张信哲在央视的开讲啦,坦言面对"过气“一次能够接受,撒贝宁说的很好,这批歌迷也许不会通过刷微博来支持,但是他在这些人的心里比微博上更重要。',
      comment: 1237,
      like: 5482,
      song: this.songs[13]
    },
    {
      author: '一个人的某泥',
      avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h14.jpg',
      content: '11年初三,无意中听到这首歌,推荐给同学,他们都说一般般。当我在家外放这首歌时,我奶奶对我说:“这什么歌,这么好听。',
      comment: 1237,
      like: 5482,
      song: this.songs[14]
    },
    {
      author: '不想早起',
      avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h15.jpg',
      content: '文不黑周树人 武不黑李小龙 音不黑黄家驹 影不黑周星驰',
      comment: 1237,
      like: 5482,
      song: this.songs[15]
    }
  ]

注意:是使用鸿蒙5.0 api16的语法,使用的语言是 ArkTS,然后组件都是使用V2版本的,而且颜色方案是 .fontColor('#颜色值') 这种形式

  • 把效果图&提示词交给 cursor 然后等待结果出来后应用:
微信截图_20251002195840
  • 如果效果还是有瑕疵可以继续自己微调或者让 cursor 调整:
微信截图_20251002200730
  • 最终效果:
微信截图_20251002204235
📌 示例代码:
typescript
interface songItemType {
  img: string;
  name: string;
  author: string;
  url: string;
  id: string;
}

interface momentListType {
  author: string;
  avatar: string;
  content: string;
  comment: number;
  like: number;
  song: songItemType;
}

@Component
export struct Moment {
  private songs: songItemType[] = [
    { img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/0.jpg', name: '直到世界的尽头', author: 'WANDS', url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/0.m4a', id: '0000' },
    { img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/1.jpg', name: '画', author: '赵磊', url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/1.mp3', id: '0001' },
    { img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/2.jpg', name: 'Sweet Dreams', author: 'TPaul Sax / Eurythmics', url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/2.mp3', id: '0002' },
    { img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/3.jpg', name: '奢香夫人', author: '凤凰传奇', url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/3.m4a', id: '0003' },
    { img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/4.jpg', name: '空心', author: '光泽', url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/4.mp3', id: '0004' },
    { img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/5.jpg', name: '反转地球', author: '潘玮柏', url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/5.mp3', id: '0005' },
    { img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/6.jpg', name: 'No.9', author: 'T-ara', url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/6.m4a', id: '0006' },
    { img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/7.jpg', name: '孤独', author: 'G.E.M.邓紫棋', url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/7.m4a', id: '0007' },
    { img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/8.jpg', name: 'Lose Control', author: 'Hedley', url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/8.m4a', id: '0008' },
    { img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/9.jpg', name: '倩女幽魂', author: '张国荣', url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/9.m4a', id: '0009' },
    { img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/10.jpg', name: '北京北京', author: '汪峰', url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/10.m4a', id: '0010' },
    { img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/11.jpg', name: '苦笑', author: '汪苏泷', url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/11.mp3', id: '0011' },
    { img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/12.jpg', name: '一生所爱', author: '卢冠廷 / 莫文蔚', url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/12.m4a', id: '0012' },
    { img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/13.jpg', name: '月半小夜曲', author: '李克勤', url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/13.mp3', id: '0013' },
    { img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/14.jpg', name: 'Rolling in the Deep', author: 'Adele', url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/14.m4a', id: '0014' },
    { img: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/15.jpg', name: '海阔天空', author: 'Beyond', url: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/15.m4a', id: '0015' }
  ];

  private momentList: momentListType[] = [
    { author: 'Feast-aws', avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h0.jpg', content: '二十几岁的你,一无所有,也拥有一切!', comment: 604, like: 23382, song: this.songs[0] },
    { author: 'CeeYet', avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h1.jpg', content: '画一个姑娘陪着我,再画一个姑娘陪着我', comment: 1237, like: 5482, song: this.songs[1] },
    { author: 'Z_HOUSC', avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h2.jpg', content: '这个事还得从两个职业选手说起', comment: 8425, like: 4234, song: this.songs[2] },
    { author: '逆转大师', avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h3.jpg', content: '听奢香夫人,做上岸女人!', comment: 7658, like: 11545, song: this.songs[3] },
    { author: 'Moriaty', avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h4.jpg', content: '别一厢情愿的付出,他如果拒绝你了就别再纠缠了,别一次次满满热情得到的都是敷衍冷淡的回复,你不该这样的,你应该酷一点,别打扰真的是最好的选择。', comment: 543, like: 2234, song: this.songs[4] },
    { author: '丶你要去哪里', avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h5.jpg', content: '当年会一段潘帅的RAP是很拉风的事情', comment: 14415, like: 36297, song: this.songs[5] },
    { author: '一个人的某泥', avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h6.jpg', content: '抗韩二十年,死于朴智妍', comment: 1237, like: 5482, song: this.songs[6] },
    { author: '曾经那个少年1993', avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h7.jpg', content: '一个人去游泳,像投河;倒过来,一个人去投河,像游泳,太孤独。 ———-陈小三', comment: 7628, like: 36448, song: this.songs[7] },
    { author: '柳崽崽阿', avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h8.jpg', content: '哎呦,B站来的跟我一起摇💃', comment: 1237, like: 5482, song: this.songs[8] },
    { author: '李富贵他哥', avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h9.jpg', content: '我弟弟老是偷我的裙子丝袜穿,我担心他在女装的道路上越走越远,这让我这个当哥哥的很是担心啊', comment: 7362, like: 11482, song: this.songs[9] },
    { author: '一檀溪一', avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h10.jpg', content: '想起张雪峰说过的全世界最大的社区天通苑,住了400w人,光是地铁修了三站,去赶早上第一班天通苑的地铁,那才是真正的北京,真的是哭辽', comment: 6496, like: 8526, song: this.songs[10] },
    { author: '-_Dimple_', avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h11.jpg', content: '今天为了赶地铁在电梯上摔了一跤,手磕破了,马上起来进了地铁结果发现一着急坐反了。某一刻觉得自己长大了,摔倒了不是先难受,想哭觉得丢脸,而是想着快点赶上地铁。大概成年人的无奈就是没时间让我难过一会儿吧。', comment: 1237, like: 5482, song: this.songs[11] },
    { author: 'h奔放小青年', avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h12.jpg', content: '关于这部电影看到过一个评论:当年大学毕业以后我和女友各奔东西,曾经一个晚上,偶然又看了这部电影,当至尊宝拥吻紫霞的时候荡气回肠的歌声也让我潸然泪下。。。。。第二天我打起行囊踏上火车往她家赶,如今十年过去,我们已经有了一个可爱的女儿。 我想,这是对一部电影最好的肯定吧', comment: 1237, like: 5482, song: this.songs[12] },
    { author: '您的坑友已上线', avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h13.jpg', content: '李克勤说他唱了三十多年的歌,当接下面具的时候还很紧张,还很担心自己能不能被大家认出来,然后当《红日》想起来的时候,自己都被感动了。前天看张信哲在央视的开讲啦,坦言面对"过气“一次能够接受,撒贝宁说的很好,这批歌迷也许不会通过刷微博来支持,但是他在这些人的心里比微博上更重要。', comment: 1237, like: 5482, song: this.songs[13] },
    { author: '一个人的某泥', avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h14.jpg', content: '11年初三,无意中听到这首歌,推荐给同学,他们都说一般般。当我在家外放这首歌时,我奶奶对我说:“这什么歌,这么好听。', comment: 1237, like: 5482, song: this.songs[14] },
    { author: '不想早起', avatar: 'http://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/HeimaCloudMusic/h15.jpg', content: '文不黑周树人 武不黑李小龙 音不黑黄家驹 影不黑周星驰', comment: 1237, like: 5482, song: this.songs[15] }
  ];

  build() {
    Column() {
      // 标题
      Text('互动广场')
        .fontSize(18)
        .fontColor('#FFFFFF')
        .fontWeight(FontWeight.Medium)
        .padding({ top: 16, bottom: 12 })

      // 列表
      List() {
        ForEach(this.momentList, (item: momentListType, index: number) => {
          ListItem() {
            Column() {
              // 头像 + 文本 行
              Row({ space: 12 }) {
                // 头像
                Image(item.avatar)
                  .width(36)
                  .height(36)
                  .borderRadius(18)

                // 文本区
                Column({ space: 8 }) {
                  Text(item.author)
                    .fontSize(16)
                    .fontColor('#FFFFFF')
                    .fontWeight(FontWeight.Medium)
                  Text(item.content)
                    .fontSize(14)
                    .fontColor('#B3B3B3')
                    .maxLines(3)
                    .textOverflow({ overflow: TextOverflow.Ellipsis })
                    .padding({ right: 50 })

                  // 歌曲卡片
                  Row({ space: 10 }) {
                    Image(item.song.img)
                      .width(64)
                      .height(64)
                      .borderRadius(6)
                    Column({ space: 6 }) {
                      Text(item.song.name)
                        .fontSize(15)
                        .fontColor('#FFFFFF')
                        .maxLines(1)
                        .textOverflow({ overflow: TextOverflow.Ellipsis })
                      Text(item.song.author)
                        .fontSize(12)
                        .fontColor('#9E9E9E')
                        .maxLines(1)
                        .textOverflow({ overflow: TextOverflow.Ellipsis })
                    }
                    Blank()
                    Image($r('app.media.ic_play'))
                      .width(18)
                      .height(18)
                      .opacity(0.9)
                  }
                    .height(64)
                    .backgroundColor('#2B2B2B')
                    .borderRadius(8)
                    .padding({ left: 8, right: 12 })
                    .justifyContent(FlexAlign.Start)
                    .alignItems(VerticalAlign.Center)
                    .width('100%')

                  // 操作区
                  Row({ space: 24 }) {
                    Row({ space: 6 }) {
                      Image($r('app.media.ic_share'))
                        .width(16)
                        .height(16)
                        .opacity(0.9)
                        .fillColor('#9E9E9E')
                      Text('分享')
                        .fontSize(12)
                        .fontColor('#9E9E9E')
                    }
                    Row({ space: 6 }) {
                      Image($r('app.media.ic_comment_o'))
                        .width(16)
                        .height(16)
                        .opacity(0.9)
                        .fillColor('#9E9E9E')
                      Text(`${item.comment}`)
                        .fontSize(12)
                        .fontColor('#9E9E9E')
                    }
                    Row({ space: 6 }) {
                      Image($r('app.media.ic_like'))
                        .width(16)
                        .height(16)
                        .opacity(0.9)
                        .fillColor('#9E9E9E')
                      Text(`${item.like}`)
                        .fontSize(12)
                        .fontColor('#9E9E9E')
                    }
                  }
                }
                .alignItems(HorizontalAlign.Start)
              }
                .alignItems(VerticalAlign.Top)

              // 分割线
              Divider()
                .color('#2F2F2F')
                .strokeWidth(1)
                .margin({ top: 16 })
            }
              .width('100%')
              .padding({ left: 16, right: 16, top: 14, bottom: 6 })
          }
        }, (item: momentListType, index: number) => item.song.id)
      }
        .edgeEffect(EdgeEffect.None)
        .width('100%')
        .height('100%')
    }
      .width('100%')
      .height('100%')
      .backgroundColor('#000000')
      .expandSafeArea()
      .padding({ top: 15 , bottom : 50})
  }
}

7.6.3 我的


  • 效果图:
image (1)
  • 提示词:
typescript
根据图片还原页面布局用代码实现出来
需求如下:
1. 页面分两块,纵向布局,上面是图片区域,占高度70%,下面是介绍区域,占高度30%
2. 图片区域是堆叠布局,大图片背景在下层,logo在上层,对齐方式底部居中
3. 介绍区域
3.1 第一行为软件名字(黑马云音乐);
3.2 第二行为用户基本信息
3.3 第三行是账户信息(关注、粉丝、点赞,数字和文字内容水平排列)
注意:文字颜色和背景色根据上面图片设置,不可以额外添加
注意是使用鸿蒙5.0 api16的语法,使用的语言是 ArkTS,然后组件都是使用V2版本的,而且颜色方案是 .fontColor('#颜色值') 这种形式
本地图片在resource/base/media 下面 , 背景大图是 bg_mine

📌 示例代码:
typescript
// 页面:我的
// API16 ArkTS(V2 组件语法)

// 账户信息项接口
interface AccountItem {
  label: string
  value: string
}

@ComponentV2
export struct Mine {
  // 账户信息数据
  private accountItems: AccountItem[] = [
    { label: '关注', value: '1' },
    { label: '粉丝', value: '100万' },
    { label: '赞', value: '1.67亿' }
  ]

  build() {
    Column() {
      // 顶部图片区域(70% 高度)
      Stack({ alignContent: Alignment.Bottom }) {
        // 背景大图
        Image($r('app.media.bg_mine'))
          .width('100%')
          .height('100%')

        // 底部居中 Logo
        Image($r('app.media.logo'))
          .width(100)
          .backgroundColor('#000')
          .border({radius : '50%'})
      }
        .width('100%')
        .height('70%')

      // 底部介绍区域(30% 高度)
      Column({ space: 12 }) {
        // 软件名称
        Row() {
          Text('黑马云音乐')
            .fontSize(22)
            .fontWeight(FontWeight.Medium)
            .fontColor('#FFFFFF')

          // VIP 标识
          Text('VIP')
            .fontSize(12)
            .fontColor('#FF5A8A')
            .margin({ left: 8 })
            .border({ width: 1, color: '#FF5A8A' })
            .padding({ left: 6, right: 6, top: 2, bottom: 2 })
            .borderRadius(10)
        }
          .margin({top : 25 , bottom : 25})
          .alignItems(VerticalAlign.Center)

        // 第二行:用户基本信息
        Row(){
          //图
          Image($r('app.media.ic_boy'))
            .width(15)
            .fillColor(Color.Blue)
            .margin({right : 10})
            .opacity(0.8)
          //文
          Text('00后  双子座  北京  歌龄·18年')
            .fontSize(14)
            .fontColor('#9E9E9E')
        }
          .width('80%')
          .justifyContent(FlexAlign.Center)


        // 第三行:账户信息(水平排列)
        Row({ space: 24 }) {
          ForEach(this.accountItems, (item: AccountItem, _: number) => {
            Row({ space: 5 }) {
              Text(item.value)
                .fontSize(14)
                .fontWeight(FontWeight.Medium)
                .fontColor('#9E9E9E')
              Text(item.label)
                .fontSize(12)
                .fontColor('#9E9E9E')
            }
          })
        }
          .width('60%')
          .justifyContent(FlexAlign.SpaceAround)
      }
        .width('100%')
        .height('30%')
        .padding({ left: 16, right: 16, top: 12})
        .backgroundColor('#0F0F10')
    }
      .width('100%')
      .height('100%')
      .backgroundColor('#000000')
      .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
  }
}

  • 效果:
微信截图_20251002212631

7.7 播放优化


  • 之前对于 AvPlayerManager.ets 里面的 singPlay(song) 方法的逻辑还可以进行优化:

    拿实际音乐软件 QQ音乐 做测试会得到下图:

微信截图_20251003124253
  • 之前添加歌曲的逻辑是,如果歌曲不在列表里,一律添加到队首,而实际是添加到播放歌曲的下一位

  • 重新设计后的逻辑如下:
微信截图_20251003124315
  • 对比之前的代码来看:
微信截图_20251003124305
  • 修改后代码:
typescript
singPlay (song:SongItemType){
    //申请长时任务
    sessionManager.startBackgroundTask()
    // 是否在列表里 → some 检查数组里面的数据是否存在满足条件的 → 只要有一个满足 some 返回 true
    const inList = this.currentSong.playList.some(item => item.id === song.id)
    if (inList) {
        //在列表里面 → 是否是正在播放的 → currentSong url === song.url
        if (this.currentSong.url === song.url) {
            this.player?.play()
            this.currentSong.isPlay = true
        } else {
            //设置新的索引 → 切歌
            this.currentSong.playIndex = this.currentSong.playList.findIndex(item => item.id === song.id)
            //切歌
            this.changeSong()
        }
    } else {
        //不在列表里面
        //如果列表为空,那么直接可以添加到队首就好了
        if(this.currentSong.playList.length === 0){
            this.currentSong.playList.unshift(song) // 把要播放的歌添加到队首
            this.currentSong.playIndex = 0
        } else {
            //列表不为空的话,添加到 playIndex 的后一位
            //lst.splice(start,deleteCount,...args)
            this.currentSong.playList.splice(this.currentSong.playIndex + 1,0,song)
            //说明(插入操作的位置,删除0个元素,插入的元素)
            this.currentSong.playIndex++
        }
        //切换歌曲
        this.changeSong()
    }
    sessionManager.setAVPlaybackState()
}

  • 重新运行项目,打开模拟器测试:
  1. 先添加一首 空心
微信截图_20251003125237
  1. 添加一首 孤独看看插入位置
微信截图_20251003125257
  1. 把当前播放歌曲改成第一位的空心
微信截图_20251003125318
  1. 再插入一首苦笑
微信截图_20251003125329
  • 可以发现功能没问题 ​✅​

7.8 项目源码