HarmonyOS5 音乐播放器app(一):歌曲展示与收藏功能(附代码)
2025-06-28 20:44:37
110次阅读
0个评论
鸿蒙音乐应用收藏功能开发指南
一、核心数据模型设计
响应式歌曲数据模型
@Observed
class Song {
id: string = '';
title: string = '';
singer: string = '';
mark: string = '1'; // 1:标准音质 2:VIP音质
label: Resource = $r('app.media.ic_music_icon');
src: string = '';
lyric: string = '';
isCollected: boolean = false; // 核心收藏状态标识
constructor(id: string, title: string, singer: string,
mark: string, label: Resource, src: string,
lyric: string, isCollected: boolean) {
// 初始化逻辑
}
}
二、应用整体架构
标签页导航实现
@Entry
@Component
struct Index {
@State currentIndex: number = 0;
@State songList: Song[] = [
new Song('1', '海阔天空', 'Beyond', '1',
$r('app.media.ic_music_icon'), '1.mp3', '1.lrc', false),
// 其他歌曲...
];
build() {
Tabs({ barPosition: BarPosition.End }) {
TabContent() {
RecommendedMusic({ songList: $songList })
}.tabBar(this.tabContent('首页', 0))
TabContent() {
CollectedMusic({ songList: $songList })
}.tabBar(this.tabContent('我的', 1))
}
}
@Builder
tabContent(title: string, index: number) {
// 标签页样式构建
}
}
三、收藏功能核心实现
歌曲列表项组件
@Component
export struct SongListItem {
@Prop song: Song;
@Link songList: Song[];
@State isAnimating: boolean = false;
// 收藏状态切换
private toggleCollect() {
this.song.isCollected = !this.song.isCollected;
this.updateSongList();
this.triggerAnimation();
this.emitCollectEvent();
}
// 更新歌曲列表状态
private updateSongList() {
this.songList = this.songList.map(item =>
item.id === this.song.id ? { ...item, isCollected: this.song.isCollected } : item
);
}
// 触发收藏动画
private triggerAnimation() {
this.isAnimating = true;
setTimeout(() => this.isAnimating = false, 300);
}
// 发送全局事件
private emitCollectEvent() {
getContext(this).eventHub.emit('collected',
this.song.id, this.song.isCollected ? '1' : '0');
}
build() {
Row() {
// 歌曲信息区域...
// 收藏按钮带动画
Image(this.song.isCollected
? $r('app.media.ic_item_collected')
: $r('app.media.ic_item_uncollected'))
.scale({ x: this.isAnimating ? 1.8 : 1, y: this.isAnimating ? 1.8 : 1 })
.animation({ duration: 300, curve: Curve.EaseOut })
.onClick(() => this.toggleCollect())
}
}
}
四、收藏页面实现
已收藏歌曲筛选展示
@Component
export struct CollectedMusic {
@Link songList: Song[];
build() {
Column() {
Text('收藏').fontSize(26).fontWeight(FontWeight.Bold);
List() {
ForEach(
this.songList.filter(song => song.isCollected),
(song: Song) => {
ListItem() {
SongListItem({ song: song, songList: $songList })
}
}
)
}
}
}
}
五、关键交互设计
收藏动画实现原理
.scale({ x: this.isAnimating ? 1.8 : 1, y: this.isAnimating ? 1.8 : 1 })
.animation({
duration: 300,
curve: Curve.EaseOut
})
状态管理流程
用户点击 → 更新isCollected状态 → 触发动画 → 更新全局列表 → 发送事件通知
六、完整代码结构
点击查看完整实现import { promptAction } from "@kit.ArkUI"
@Observed
class Song {
id: string = ''
title: string = ''
singer: string = ''
// 音乐品质标识,1:sq,2:vip
mark: string = "1"
// 歌曲封面图片
label: Resource = $r('app.media.ic_music_icon')
// 歌曲播放路径
src: string = ''
// 歌词文件路径
lyric: string = ''
// 收藏状态, true:已收藏 false:未收藏
isCollected: boolean = false
constructor(id: string, title: string, singer: string, mark: string, label: Resource, src: string, lyric: string, isCollected: boolean) {
this.id = id
this.title = title
this.singer = singer
this.mark = mark
this.label = label
this.src = src
this.lyric = lyric
this.isCollected = isCollected
}
}
@Entry
@Component
struct Index {
@State currentIndex: number = 0
// 存储收藏歌曲列表
@State songList: Array<Song> = [
new Song('1', '海阔天空', 'Beyond', '1', $r('app.media.ic_music_icon'), 'common/music/1.mp3', 'common/music/1.lrc', false),
new Song('2', '夜空中最亮的星', '逃跑计划', '1', $r('app.media.ic_music_icon'), 'common/music/2.mp3', 'common/music/2.lrc', false),
new Song('3', '光年之外', 'GAI周延', '2', $r('app.media.ic_music_icon'), 'common/music/3.mp3', 'common/music/3.lrc', false),
new Song('4', '起风了', '买辣椒也用券', '1', $r('app.media.ic_music_icon'), 'common/music/4.mp3', 'common/music/4.lrc', false),
new Song('5', '孤勇者', '陈奕迅', '2', $r('app.media.ic_music_icon'), 'common/music/5.mp3', 'common/music/5.lrc', false)
]
@Builder
tabStyle(index: number, title: string, selectedImg: Resource, unselectedImg: Resource) {
Column() {
Image(this.currentIndex == index ? selectedImg : unselectedImg)
.width(20)
.height(20)
Text(title)
.fontSize(16)
.fontColor(this.currentIndex == index ? "#ff1456" : '#ff3e4040')
}
}
build() {
Tabs() {
TabContent() {
// 首页标签内容
RecommendedMusic({ songList: this.songList})
}
.tabBar(this.tabStyle(0, '首页', $r('app.media.home_selected'), $r("app.media.home")))
TabContent() {
// 我的标签内容占位
CollectedMusic({ songList: this.songList})
}
.tabBar(this.tabStyle(1, '我的', $r('app.media.userfilling_selected'), $r("app.media.userfilling")))
}
.barPosition(BarPosition.End)
.width("100%")
.height("100%")
.onChange((index: number) => {
this.currentIndex = index
})
}
}
// 推荐歌单
@Component
export struct RecommendedMusic {
// 创建路由栈管理导航
@Provide('navPath') pathStack: NavPathStack = new NavPathStack()
// 热门歌单标题列表
@State playListsTiles: string[] = ['每日推荐', '热门排行榜', '经典老歌', '流行金曲', '轻音乐精选']
@Link songList: Array<Song>
@Builder
shopPage(name: string, params:string[]) {
if (name === 'HotPlaylist') {
HotPlayList({
songList: this.songList
});
}
}
build() {
Navigation(this.pathStack) {
Scroll() {
Column() {
// 推荐标题栏
Text('推荐')
.fontSize(26)
.fontWeight(700)
.height(56)
.width('100%')
.padding({ left: 16, right: 16 })
// 水平滚动歌单列表
List({ space: 10 }) {
ForEach(this.playListsTiles, (item: string, index: number) => {
ListItem() {
HotListPlayItem({ title: item })
.margin({
left: index === 0 ? 16 : 0,
right: index === this.playListsTiles.length - 1 ? 16 : 0
})
.onClick(() => {
this.pathStack.pushPathByName('HotPlaylist', [item])
})
}
}, (item: string) => item)
}
.height(200)
.width("100%")
.listDirection(Axis.Horizontal)
.edgeEffect(EdgeEffect.None)
.scrollBar(BarState.Off)
// 热门歌曲标题栏
Text('热门歌曲')
.fontSize(22)
.fontWeight(700)
.height(56)
.width('100%')
.padding({ left: 16, right: 16 })
// 歌曲列表
List() {
ForEach(this.songList, (song: Song, index: number) => {
ListItem() {
SongListItem({ song: song, index: index,songList: this.songList})
}
}, (song: Song) => song.id)
}
.cachedCount(3)
.divider({
strokeWidth: 1,
color: "#E5E5E5",
startMargin: 16,
endMargin: 16
})
.scrollBar(BarState.Off)
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
})
}
.width("100%")
}
.scrollBar(BarState.Off)
}
.hideTitleBar(true)
.mode(NavigationMode.Stack)
.navDestination(this.shopPage)
}
}
// 热门歌单
@Component
export struct HotListPlayItem {
@Prop title: string // 接收外部传入的标题
build() {
Stack() {
// 背景图片
Image($r('app.media.cover5'))
.width("100%")
.height('100%')
.objectFit(ImageFit.Cover)
.borderRadius(16)
// 底部信息栏
Row() {
Column() {
// 歌单标题
Text(this.title)
.fontSize(20)
.fontWeight(700)
.fontColor("#ffffff")
// 辅助文本
Text("这个歌单很好听")
.fontSize(16)
.fontColor("#efefef")
.height(12)
.margin({ top: 5 })
}
.justifyContent(FlexAlign.SpaceBetween)
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
// 播放按钮
SymbolGlyph($r('sys.symbol.play_round_triangle_fill'))
.fontSize(36)
.fontColor(['#99ffffff'])
}
.backgroundColor("#26000000")
.padding({ left: 12, right: 12 })
.height(72)
.width("100%")
}
.clip(true)
.width(220)
.height(200)
.align(Alignment.Bottom)
.borderRadius({ bottomLeft: 16, bottomRight: 16 })
}
}
// 歌曲列表项
@Component
export struct SongListItem {
//定义当前歌曲,此歌曲是由前面通过遍历出来的单个数据
@Prop song: Song
@Link songList: Array<Song>
//当前点击歌曲的index值
@Prop index: number
/**
* 点击红心收藏效果
*/
collectStatusChange(): void {
const songs = this.song
// 切换收藏状态
this.song.isCollected = !this.song.isCollected
// 更新收藏列表
this.songList = this.songList.map((item) => {
if (item.id === songs.id) {
item.isCollected = songs.isCollected
}
return item
})
promptAction.showToast({
message: this.song.isCollected ? '收藏成功' : '已取消收藏',
duration: 1500
});
// 触发全局收藏事件
getContext(this).eventHub.emit('collected', this.song.id, this.song.isCollected ? '1' : '0')
}
aboutToAppear(): void {
// 初始化歌曲数据(如果需要)
}
build() {
Column() {
Row() {
Column() {
Text(this.song.title)
.fontWeight(500)
.fontColor('#ff070707')
.fontSize(16)
.margin({ bottom: 4 })
Row({ space: 4 }) {
Image(this.song.mark === '1' ? $r('app.media.ic_vip') : $r('app.media.ic_sq'))
.width(16)
.height(16)
Text(this.song.singer)
.fontSize(12)
.fontWeight(400)
.fontColor('#ff070707')
}
}.alignItems(HorizontalAlign.Start)
Image(this.song.isCollected ? $r('app.media.ic_item_collected') : $r('app.media.ic_item_uncollected'))
.width(50)
.height(50)
.padding(13)
.onClick(() => {
this.collectStatusChange()
})
}
.width("100%")
.justifyContent(FlexAlign.SpaceBetween)
.padding({
left: 16,
right: 16,
top: 12,
bottom: 12
})
.onClick(() => {
// 设置当前播放歌曲
this.song = this.song
// todo: 添加播放逻辑
})
}.width("100%")
}
}
@Component
export struct HotPlayList {
@Prop title:string
@Link songList: Array<Song>
build() {
NavDestination(){
List(){
ForEach(this.songList,(song:Song)=>{
ListItem(){
SongListItem({song:song,songList:this.songList})
}
},(song:Song)=>song.id)
}.cachedCount(3)
.divider({
strokeWidth:1,
color:"#E5E5E5",
startMargin:16,
endMargin:16
})
.scrollBar(BarState.Off)
}.width("100%").height("100%")
.title(this.title)
}
}
@Component
export struct CollectedMusic {
@Link songList: Array<Song>
build() {
Column(){
Text('收藏')
.fontSize(26)
.fontWeight(700)
.height(56)
.width('100%')
.padding({ left: 16, right: 16 })
List(){
ForEach(this.songList.filter((song) => song.isCollected),(song:Song,index:number)=>{
ListItem(){
SongListItem({song:song,songList:this.songList})
}
},(song:Song)=>song.id)
}.cachedCount(3)
.layoutWeight(1)
.divider({
strokeWidth:1,
color:"#E5E5E5",
startMargin:16,
endMargin:16
})
.scrollBar(BarState.Off)
}.width("100%").height("100%")
}
}
七、设计要点总结
- 响应式数据:使用@Observed实现数据变化自动更新UI
- 状态管理:通过@State和@Link实现组件间状态共享
- 动画效果:scale变换配合animation实现流畅交互
- 数据筛选:filter方法实现收藏歌曲快速筛选
- 事件通信:eventHub实现跨组件事件通知
00
- 0回答
- 0粉丝
- 0关注
相关话题
- HarmonyOS5 购物商城app(一):商品展示(附代码)
- HarmonyOS5 运动健康app(一):健康饮食(附代码)
- [HarmonyOS NEXT 实战案例:音乐播放器] 基础篇 - 水平分割布局打造音乐播放器界面
- [HarmonyOS NEXT 实战案例:音乐播放器] 进阶篇 - 交互式音乐播放器的状态管理与控制
- HarmonyOS5 运动健康app(二):健康跑步(附代码)
- HarmonyOS5 运动健康app(三):健康睡眠(附代码)
- HarmonyOS5 购物商城app(二):购物车与支付(附代码)
- HarmonyOS5 儿童画板app:手绘写字(附代码)
- HarmonyOS NEXT 应用开发实战:音乐播放器的完整实现
- [HarmonyOS NEXT 实战案例十三] 音乐播放器网格布局(下)
- [HarmonyOS NEXT 实战案例十三] 音乐播放器网格布局(上)
- [HarmonyOS NEXT 实战案例四:SideBarContainer] 侧边栏容器实战:音乐播放器侧边栏 - 播放列表与歌单管理 基础篇
- [HarmonyOS NEXT 实战案例四:SideBarContainer] 侧边栏容器实战:音乐播放器侧边栏 - 播放列表与歌单管理 进阶篇
- 鸿蒙HarmonyOS ArkTS视频播放器组件详解
- 鸿蒙运动开发实战:打造专属运动视频播放器