HarmonyOS5 音樂播放器app(一):歌曲展示與收藏功能(附代碼)

鴻蒙音樂應用開發:從收藏功能實現看狀態管理與交互設計

在移動應用開發中,收藏功能是用戶體驗的重要組成部分。本文將以鴻蒙OS音樂應用為例,詳細解析如何實現具有動畫效果的收藏功能,涉及狀態管理、組件通信和交互動畫等核心技術點。

一、收藏功能的核心數據模型設計

首先定義Song數據模型,通過@Observed裝飾器實現數據響應式:

@Observed
class Song {id: string = ''title: string = ''singer: string = ''mark: string = "1" // 音樂品質標識,1:sq, 2:viplabel: 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) {this.id = idthis.title = titlethis.singer = singerthis.mark = markthis.label = labelthis.src = srcthis.lyric = lyricthis.isCollected = isCollected}
}

isCollected字段作為收藏狀態的核心標識,配合@Observed實現數據變更時的UI自動更新。

二、首頁與收藏頁的整體架構

應用采用標簽頁架構,通過Tabs組件實現"首頁"與"我的"頁面切換:

@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),// 省略其他歌曲...]@BuildertabStyle(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%")}
}

通過@State管理標簽頁索引currentIndex,當用戶切換標簽時,自動更新UI顯示推薦歌曲或收藏歌曲。

三、收藏功能的核心實現:狀態切換與動畫效果

在歌曲列表項組件中,實現收藏狀態切換邏輯與動畫效果:

@Component
export struct SongListItem {@Prop song: Song@Link songList: Array<Song>@Prop index: number// 動畫狀態標識@State isAnimating: boolean = false/*** 收藏狀態切換方法*/collectStatusChange(): void {// 1. 切換當前歌曲收藏狀態this.song.isCollected = !this.song.isCollected// 2. 更新列表中對應歌曲的狀態this.songList = this.songList.map(item => {if (item.id === this.song.id) {item.isCollected = this.song.isCollected}return item})// 3. 觸發收藏動畫this.isAnimating = truesetTimeout(() => {this.isAnimating = false}, 300)// 4. 發送收藏事件(可用于全局狀態同步)getContext(this).eventHub.emit('collected', this.song.id, this.song.isCollected ? '1' : '0')}build() {Column() {Row() {Column() {Text(this.song.title).fontWeight(500).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)}}.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).scale({ x: this.isAnimating ? 1.8 : 1, y: this.isAnimating ? 1.8 : 1 }).animation({ duration: 300, curve: Curve.EaseOut }).onClick(() => this.collectStatusChange())}.width("100%").padding({ left: 16, right: 16, top: 12, bottom: 12 })}.width("100%")}
}

核心動畫效果通過scale變換和animation配置實現:

  • 點擊時圖標放大至1.8倍
  • 300ms的緩出動畫(Curve.EaseOut
  • isAnimating狀態控制動畫的觸發與結束

四、收藏列表的篩選與展示

"我的"頁面通過filter方法篩選已收藏歌曲:

@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).divider({ strokeWidth: 1, color: "#E5E5E5" }).scrollBar(BarState.Off)}.width("100%").height("100%")}
}

通過filter(song => song.isCollected)實現已收藏歌曲的精準篩選,確保"我的"頁面只顯示用戶收藏的內容。

五、附:代碼
import { promptAction } from "@kit.ArkUI"@Observed
class Song {id: string = ''title: string = ''singer: string = ''// 音樂品質標識,1:sq,2:vipmark: string = "1"// 歌曲封面圖片label: Resource = $r('app.media.ic_music_icon')// 歌曲播放路徑src: string = ''// 歌詞文件路徑lyric: string = ''// 收藏狀態, true:已收藏 false:未收藏isCollected: boolean = falseconstructor(id: string, title: string, singer: string, mark: string, label: Resource, src: string, lyric: string, isCollected: boolean) {this.id = idthis.title = titlethis.singer = singerthis.mark = markthis.label = labelthis.src = srcthis.lyric = lyricthis.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)]@BuildertabStyle(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>@BuildershopPage(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%")}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/84373.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/84373.shtml
英文地址,請注明出處:http://en.pswp.cn/web/84373.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

PHP函數大全參考代碼

字符串相關操作函數 去除空格或其他字符 trim刪除字符串兩端空格或其他預定義字符rtrim刪除字符串右邊空格或其他預定義字符choprtrim() 的別名 chop() 與 Perl 的 chop() 函數有所不同&#xff0c;它會刪除字符串的最后一個字符。ltrim刪除字符串左邊空格或其他預定義字符 字…

Flowise工作流引擎的本地部署與遠程訪問實踐

文章目錄 前言1. Docker安裝Flowise2. Ubuntu安裝Cpolar3. 配置Flowise公網地址4. 遠程訪問Flowise5. 固定Cpolar公網地址6. 固定地址訪問 前言 當多數團隊仍深陷傳統數據處理框架的桎梏時&#xff0c;創新者已率先引入Flowise智能流程引擎&#xff0c;成功將面向大型語言模型…

端側AI+OS垂直創新研究報告

端側AIOS垂直創新研究報告 摘要 端側AIOS研究背景、核心創新點及產業價值 研究背景 隨著AI技術的快速發展&#xff0c;端側AI已成為2025年的重要技術趨勢[4]。端側AI是指將AI計算能力從云端遷移到終端設備上&#xff0c;實現本地化的智能處理。這一技術變革主要受到隱私安全…

【JVM 07-運行時常量池重要組成部分-StringTable】

StringTable 筆記記錄 1. 常量池、運行時常量池與字符串常量池(StringTable)的關系2. String str"a"放入字符串常量池的過程3. 常見面試題4. StringTable特性5.StringTable的位置變更5.1 為什么位置變換&#xff1f;5.2 位置變更演示 6. StringTable垃圾回收7. Strin…

算法-每日一題(DAY10)打家劫舍

1.題目鏈接&#xff1a; 198. 打家劫舍 - 力扣&#xff08;LeetCode&#xff09; 2.題目描述&#xff1a; 你是一個專業的小偷&#xff0c;計劃偷竊沿街的房屋。每間房內都藏有一定的現金&#xff0c;影響你偷竊的唯一制約因素就是相鄰的房屋裝有相互連通的防盜系統&#xf…

android UI 布局

一&#xff1a;約束布局 參考&#xff1a; 【約束布局】ConstraintLayout 約束布局 ( 簡介 | 引入依賴 | 基本操作 | 垂直定位約束 | 角度定位約束 | 基線約束 )_韓曙亮-2048 AI社區 以下是一個基于 ConstraintLayout 的簡單 Android 示例&#xff0c;包含三個控件&#xff0…

【K8S】詳解Labels?? 和 ??Annotations

在 Kubernetes&#xff08;K8s&#xff09;中&#xff0c;??Labels&#xff08;標簽&#xff09;?? 和 ??Annotations&#xff08;注解&#xff09;?? 都是用于為資源對象&#xff08;如 Pod、Service、Deployment&#xff09;附加元數據的機制&#xff0c;但它們在設計…

系統模塊編程與實現

設備類&#xff08;Device Class&#xff09;?? 和 ??設備節點&#xff08;Device Node&#xff09;??是深入 Linux 設備管理和驅動模型的核心基礎。它們就像“骨骼”與“門戶”&#xff0c;共同構建了 Linux 與硬件交互的核心橋梁。 一、設備類與設備節點 1. ??設備…

視頻壓縮、碼率與流媒體傳輸知識總結

&#x1f3a5; 視頻壓縮、碼率與流媒體傳輸知識總結 本筆記整理了 I/P/B 幀結構、碼率計算、文件大小估算、壓縮格式對比、推流帶寬建議等視頻工程常見技術要點。 一、單幀與未壓縮視頻數據量估算 分辨率&#xff1a;19201080&#xff08;1080p&#xff09; 色深&#xff1a;…

嵌入式C++學習路線

&#x1f680; 嵌入式C學習路線圖 從C語言基礎到嵌入式C高手的完整路徑 &#x1f4cb; 學習進度追蹤 總體目標&#xff1a; 20-26周完成全部學習內容 前置條件&#xff1a; C語言基礎 STM32開發經驗 學習方式&#xff1a; 理論學習 實踐項目 階段1: C基礎過渡 (2-3周) 目標…

VSCode1.101.1Win多語言語言編輯器便攜版安裝教程

軟件下載 【名稱】&#xff1a; VSCode1.101.1 【大小】&#xff1a; 120M 【語言】&#xff1a; 簡體中文 【安裝環境】&#xff1a; Win10/Win11 【迅雷網盤下載鏈接】&#xff08;務必手機注冊&#xff09;&#xff1a; 迅雷 【網站下載鏈接】: 其他網盤 軟件介紹 VSCod…

ssh 服務和 rsync 數據同步

目錄 一、ssh服務 1、概述 2、命令解析 遠程登錄命令 遠程拷貝命令 3、登錄方式配置 1、用戶名密碼登錄 2、公鑰驗證登錄 二、rsync 數據同步 1、rsync概述 2、rsync運行原理 3、rsync部署 一、ssh服務 1、概述 ssh服務&#xff0c;一種遠程管理連接工具&#xf…

使用隨機森林實現目標檢測

核心實現思路 滑動窗口策略&#xff1a;在圖像上滑動固定大小的窗口&#xff0c;對每個窗口進行分類多維特征提取&#xff1a;結合統計特征、紋理特征、邊緣特征、形狀特征等隨機森林分類&#xff1a;訓練二分類器判斷窗口是否包含目標后處理優化&#xff1a;使用非極大值抑制…

3.6 move_base導航初體驗

1.環境搭建 在工作空間src下git wpr_simulation&#xff0c;安裝install_for_noetic.sh&#xff0c;然后再回退工作空間進行編譯 下載參數文件 git clone https://github.com/6-robot/wpb_home.git下載需要魔法&#xff0c;在這里可以使用手機熱點進行平替 進入腳本文件夾 …

Mysql高級——MVCC(多版本并發控制)

MySQL MVCC&#xff08;多版本并發控制&#xff09;詳解 MVCC&#xff08;Multi-Version Concurrency Control&#xff09;是 MySQL InnoDB 存儲引擎實現的一種并發控制機制&#xff0c;用于在保證事務隔離性的同時&#xff0c;提高數據庫的并發性能。下面從原理、實現、事務隔…

Oracle union連接的怎么排序

在Oracle數據庫中&#xff0c;使用UNION或UNION ALL操作符來合并兩個或多個查詢結果時&#xff0c;如果想對這些合并后的結果進行排序&#xff0c;通常有兩種方法可以實現&#xff1a; 方法1&#xff1a;在最后的查詢結果上使用ORDER BY 你可以在所有使用UNION或UNION ALL合并…

uni-app總結2-所需知識儲備和學習途徑

使用uni-app進行跨平臺開發&#xff0c;開發者不用去掌握各個平臺的開發語言&#xff0c;只需一套代碼即可完成多端的產品輸出。那么使用uni-app需要掌握什么呢&#xff0c;這里給大家分享一下。 Vue.js uni-app里是通過Vue來開發的&#xff0c;所以首先肯定是要掌握Vue語言。…

如何高效實現公司文件管理

要實現公司文件管理的高效&#xff0c;企業應聚焦統一文件規范、部署文檔管理系統、強化權限控制、推動協同編輯、實施定期清理、推進文化建設、引入可視化分析。其中&#xff0c;統一文件規范是文件高效管理的基礎。若缺乏清晰的命名規則與分類體系&#xff0c;即便配備了先進…

多模態大語言模型arxiv論文略讀(124)

MediConfusion: Can you trust your AI radiologist? Probing the reliability of multimodal medical foundation models ?? 論文標題&#xff1a;MediConfusion: Can you trust your AI radiologist? Probing the reliability of multimodal medical foundation models …

nacos的總結

服務發現與健康監測&#xff1a;Nacos 支持多種服務注冊方式&#xff0c;包括 API、SDK 和 Annotation 等&#xff0c;服務消費者可以通過 DNS 或 RPC 方式方便地發現服務。其健康檢查機制通過主動和被動的方式實時監測服務實例的健康狀態&#xff0c;確保流量不會被發送到不健…