# HarmonyOS-Music **Repository Path**: isting/HarmonyOS-Music ## Basic Information - **Project Name**: HarmonyOS-Music - **Description**: HarmonyOS-Music - **Primary Language**: JavaScript - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 2 - **Created**: 2025-06-10 - **Last Updated**: 2025-06-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # HarmonyOS-Music 项目技术文档 ## 项目概述 HarmonyOS-Music是一款基于HarmonyOS平台开发的音乐应用,采用ArkTS语言和HarmonyOS的声明式UI开发框架,为用户提供音乐播放、搜索、收藏等功能。项目展示了HarmonyOS应用开发的核心技术和最佳实践,包括组件化设计、状态管理和页面导航等。 ## 技术栈 - **ArkTS**: HarmonyOS的声明式UI开发语言,基于TypeScript扩展 - **HarmonyOS UI框架**: 提供丰富的UI组件和布局能力 - **状态管理**: 使用@State、@Prop、@Link等装饰器管理组件状态 - **组件化开发**: 采用自定义组件封装复用逻辑 ## 项目结构 ``` entry/src/main/ ├── ets/ # ArkTS源代码 │ ├── common/ # 公共组件和工具 │ │ ├── components/ # 可复用组件 │ │ ├── constants/ # 常量定义 │ │ └── utils/ # 工具类 │ ├── entryability/ # 应用入口 │ └── pages/ # 页面组件 └── resources/ # 资源文件 ├── base/ # 基础资源 │ ├── element/ # 文本和颜色资源 │ └── media/ # 图片和媒体资源 └── rawfile/ # 原始文件资源 ``` ## 核心页面分析 ### 1. 应用入口 (Index.ets) Index.ets是应用的主入口,负责页面导航和状态管理。 #### 关键代码分析: ```typescript @Entry @Component struct Index { @State currentPage: string = 'home'; @State showPlayerPage: boolean = false; // 音乐播放相关状态 @State songTitle: string = '夜曲'; @State artistName: string = '周杰伦'; @State albumName: string = '十一月的萧邦'; @State isPlaying: boolean = false; @State currentTime: string = '0:00'; @State totalTime: string = '3:30'; @State progress: number = 0; build() { Stack({ alignContent: Alignment.Bottom }) { // 根据状态显示不同页面 if (this.showPlayerPage) { PlayerPage({ songTitle: this.songTitle, artistName: this.artistName, // 其他属性... onClose: () => { this.showPlayerPage = false; } }) } else { // 显示主导航页面 Stack({ alignContent: Alignment.Bottom }) { // 根据当前选中的页面显示对应内容 if (this.currentPage === 'home') { HomePage() } else if (this.currentPage === 'search') { SearchPage() } // 其他页面... // 迷你播放器 MiniPlayer({ songTitle: this.songTitle, artistName: this.artistName, // 其他属性和回调... }) // 底部导航栏 BottomNavBar({ currentPage: $currentPage }) } } } } } ``` #### 技术要点: 1. **@Entry装饰器**: 标记应用入口组件 2. **@State装饰器**: 定义组件内部状态,状态变化会触发UI刷新 3. **@Link装饰器**: 实现父子组件间的双向数据绑定 4. **条件渲染**: 使用if-else语句根据状态显示不同页面 5. **组件嵌套**: 通过组合不同组件构建复杂UI ### 2. 首页 (HomePage.ets) 首页是用户进入应用的第一个界面,主要展示推荐内容和热门歌单。 #### 关键代码分析: ```typescript @Component export struct HomePage { @State username: string = '小明'; @State currentPage: string = 'home'; build() { Column() { // 内容区域 - 使用滚动容器 Scroll() { Column() { // 顶部欢迎语和头像 Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { // 欢迎语 Column() { Text(`你好,${this.username}`) .fontSize(StyleConstants.FONT_SIZE_XLARGE) .fontWeight(FontWeight.Bold) Text('享受美妙的音乐时光') .fontSize(StyleConstants.FONT_SIZE_SMALL) .fontColor(StyleConstants.TEXT_SECONDARY) } // 用户头像 Stack() { Text(this.username.substring(0, 2)) .fontColor(StyleConstants.TEXT_WHITE) } .backgroundColor(StyleConstants.PRIMARY_COLOR) .borderRadius(StyleConstants.RADIUS_CIRCLE) } // 搜索框 Row() { Image(IconUtils.SEARCH) Text('搜索歌曲、艺术家或专辑') } .backgroundColor(StyleConstants.BACKGROUND_LIGHT_GRAY) .borderRadius(StyleConstants.RADIUS_XLARGE) // 为你推荐 Column() { // 推荐卡片横向滚动 Scroll() { Row({ space: StyleConstants.MARGIN_MEDIUM }) { this.RecommendCard('今日热门', '根据你的口味推荐', $r('app.media.icon'), '#5e35b1') // 更多推荐卡片... } } .scrollBar(BarState.Off) .scrollable(ScrollDirection.Horizontal) } // 热门歌单和最近播放... } } } } // 自定义组件构建器 @Builder RecommendCard(title: string, description: string, image: Resource, bgColor: string) { // 推荐卡片UI实现 } @Builder SongItem(title: string, artist: string, image: Resource) { // 歌曲项UI实现 } } ``` #### 技术要点: 1. **@Component装饰器**: 定义可复用的自定义组件 2. **@Builder装饰器**: 创建可复用的UI片段,类似于函数式组件 3. **Flex布局**: 使用Flex容器实现灵活的布局 4. **Scroll组件**: 实现内容滚动,支持水平和垂直滚动 5. **链式调用**: ArkTS支持链式调用设置组件属性 ### 3. 搜索页面 (SearchPage.ets) 搜索页面允许用户查找特定的歌曲、艺术家或专辑,并展示热门搜索和搜索历史。 #### 关键代码分析: ```typescript @Component export struct SearchPage { @State searchText: string = ''; @State searchHistory: string[] = ['夜曲 周杰伦', '陈奕迅', 'Beyond 海阔天空']; build() { Column() { Scroll() { Column() { // 搜索框 Row() { Row() { Image(IconUtils.SEARCH) TextInput({ placeholder: '搜索歌曲、艺术家或专辑', text: this.searchText }) .onChange((value) => { this.searchText = value; }) } Text('取消') } // 热门搜索 Column() { Text('热门搜索') Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Start }) { this.SearchTag('周杰伦') this.SearchTag('陈奕迅') // 更多搜索标签... } } // 搜索历史 Column() { Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { Text('搜索历史') Row() { Image(IconUtils.TRASH) Text('清除') } .onClick(() => { this.searchHistory = []; }) } // 历史记录列表 Column({ space: StyleConstants.MARGIN_MEDIUM }) { if (this.searchHistory.length > 0) { ForEach(this.searchHistory, (item: string, index: number) => { this.HistoryItem(item, index) }) } else { Text('暂无搜索历史') } } } } } } } @Builder SearchTag(text: string) { // 搜索标签UI实现 } @Builder HistoryItem(text: string, index: number) { // 历史记录项UI实现 } } ``` #### 技术要点: 1. **TextInput组件**: 实现文本输入功能,支持事件监听 2. **ForEach循环**: 基于数组数据动态渲染UI元素 3. **条件渲染**: 根据数组长度显示不同内容 4. **事件处理**: 通过onClick等事件处理用户交互 5. **Flex布局**: 使用Flex.wrap实现标签的自动换行 ### 4. 播放器页面 (PlayerPage.ets) 播放器页面是用户欣赏音乐的核心界面,提供了完整的音乐播放控制和歌词显示功能。 #### 关键代码分析: ```typescript @Component export struct PlayerPage { @State songTitle: string = '夜曲'; @State artistName: string = '周杰伦'; @State albumName: string = '十一月的萧邦'; @State isPlaying: boolean = true; @State currentTime: string = '1:13'; @State totalTime: string = '3:30'; @State progress: number = 35; // 播放进度百分比 @State albumRotation: number = 0; // 专辑封面旋转角度 @State pageOpacity: number = 0; // 页面透明度,用于入场动画 @State pageScale: number = 0.95; // 页面缩放,用于入场动画 private albumRotateTimer: number = -1; // 专辑旋转定时器ID // 关闭播放页面的回调函数 onClose?: () => void; aboutToAppear() { // 页面入场动画 animateTo({ duration: 300, curve: Curve.EaseOut, }, () => { this.pageOpacity = 1; this.pageScale = 1; }); // 创建专辑旋转动画 this.startAlbumRotation(); } aboutToDisappear() { // 停止专辑旋转动画 this.stopAlbumRotation(); } // 开始专辑旋转动画 private startAlbumRotation() { // 停止之前的动画 this.stopAlbumRotation(); // 只有在播放状态才旋转 if (this.isPlaying) { // 使用定时器实现旋转动画 this.albumRotateTimer = setInterval(() => { this.albumRotation = (this.albumRotation + 0.9) % 360; }, 50); } } // 停止专辑旋转动画 private stopAlbumRotation() { if (this.albumRotateTimer !== -1) { clearInterval(this.albumRotateTimer); this.albumRotateTimer = -1; } } build() { Column() { // 顶部控制 Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { Button({ type: ButtonType.Circle, stateEffect: true }) { Image(IconUtils.ARROW_DOWN) } .onClick(() => { if (this.onClose) { this.onClose(); } }) // 其他顶部控件... } // 专辑封面 Column() { Image($r('app.media.icon')) .rotate({ x: 0, y: 0, z: 1, angle: this.albumRotation }) // 添加旋转效果 .animation({ duration: 500 }) // 平滑过渡 } // 进度条 Column() { // 进度条 Row() { Column() .width(`${this.progress}%`) .height(4) .backgroundColor(StyleConstants.TEXT_WHITE) .animation({ duration: 300, curve: Curve.EaseOut }) // 添加进度条动画 } .backgroundColor('rgba(255, 255, 255, 0.2)') // 时间显示 Flex({ justifyContent: FlexAlign.SpaceBetween }) { Text(this.currentTime) Text(this.totalTime) } } // 播放控制 Row() { // 播放控制按钮... // 播放/暂停 Button({ type: ButtonType.Circle, stateEffect: true }) { Image(this.isPlaying ? IconUtils.PAUSE : IconUtils.PLAY) .animation({ duration: 200, curve: Curve.FastOutSlowIn }) // 添加图标切换动画 } .onClick(() => { this.isPlaying = !this.isPlaying; // 控制专辑旋转动画 if (this.isPlaying) { this.startAlbumRotation(); } else { this.stopAlbumRotation(); } }) } // 歌词展示 Column() { Text('你听见 风声了吗') Text('风声吹动树叶飘动就像我的心') .fontWeight(FontWeight.Medium) Text('动着摇着') } } } } ``` #### 技术要点: 1. **生命周期函数**: 使用aboutToAppear和aboutToDisappear管理组件生命周期 2. **动画效果**: 使用animateTo和animation实现过渡动画 3. **定时器**: 使用setInterval实现专辑旋转动画 4. **事件回调**: 通过回调函数实现组件间通信 5. **状态管理**: 使用@State管理UI状态和动画状态 ### 5. 底部导航栏组件 (BottomNavBar.ets) 底部导航栏是应用的主要导航工具,允许用户在不同页面间切换。 #### 关键代码分析: ```typescript @Component export struct BottomNavBar { @Link currentPage: string; build() { Flex({ justifyContent: FlexAlign.SpaceAround, alignItems: ItemAlign.Center }) { // 首页 Column() { Image(IconUtils.HOME) .fillColor(this.currentPage === 'home' ? StyleConstants.PRIMARY_COLOR : StyleConstants.TEXT_GRAY) Text('首页') .fontColor(this.currentPage === 'home' ? StyleConstants.PRIMARY_COLOR : StyleConstants.TEXT_GRAY) } .onClick(() => { this.currentPage = 'home'; }) // 搜索 Column() { Image(IconUtils.SEARCH) .fillColor(this.currentPage === 'search' ? StyleConstants.PRIMARY_COLOR : StyleConstants.TEXT_GRAY) Text('搜索') .fontColor(this.currentPage === 'search' ? StyleConstants.PRIMARY_COLOR : StyleConstants.TEXT_GRAY) } .onClick(() => { this.currentPage = 'search'; }) // 其他导航项... } .height(StyleConstants.BOTTOM_NAV_HEIGHT) .width('100%') .backgroundColor(StyleConstants.BACKGROUND_COLOR) .borderColor('rgba(0, 0, 0, 0.1)') .borderWidth({ top: 1 }) } } ``` #### 技术要点: 1. **@Link装饰器**: 实现与父组件的双向数据绑定 2. **条件样式**: 根据当前页面状态动态改变样式 3. **事件处理**: 通过onClick事件处理导航切换 4. **Flex布局**: 使用Flex实现均匀分布的导航项 ### 6. 迷你播放器组件 (MiniPlayer.ets) 迷你播放器显示在应用底部,提供当前播放歌曲的基本信息和控制。 #### 关键代码分析: ```typescript @Component export struct MiniPlayer { // 当前播放歌曲信息 @Prop songTitle: string = '夜曲'; @Prop artistName: string = '周杰伦'; @Prop coverImage: Resource = $r('app.media.icon'); @Prop isPlaying: boolean = false; // 播放控制回调 onPlayPause?: () => void; onNext?: () => void; onTap?: () => void; // 点击迷你播放器进入完整播放页面 build() { Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { // 歌曲信息部分 Row({ space: StyleConstants.MARGIN_MEDIUM }) { Image(this.coverImage) .borderRadius(StyleConstants.RADIUS_SMALL) Column() { Text(this.songTitle) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) Text(this.artistName) .fontSize(StyleConstants.FONT_SIZE_SMALL) .fontColor(StyleConstants.TEXT_SECONDARY) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) } } // 控制按钮部分 Row({ space: StyleConstants.MARGIN_LARGE }) { Button({ type: ButtonType.Circle, stateEffect: true }) { Image(this.isPlaying ? IconUtils.PAUSE : IconUtils.PLAY) } .onClick(() => { if (this.onPlayPause) { this.onPlayPause(); } }) Button({ type: ButtonType.Circle, stateEffect: true }) { Image(IconUtils.NEXT) } .onClick(() => { if (this.onNext) { this.onNext(); } }) } } .onClick(() => { if (this.onTap) { this.onTap(); } }) } } ``` #### 技术要点: 1. **@Prop装饰器**: 用于接收父组件传递的只读属性 2. **回调函数**: 通过可选的回调函数实现子到父的事件通知 3. **文本溢出处理**: 使用maxLines和textOverflow处理文本溢出 4. **事件冒泡**: 组件整体和内部按钮都有点击事件 ### 7. 样式常量 (StyleConstants.ets) 样式常量文件集中管理应用的样式定义,提高代码的可维护性。 #### 关键代码分析: ```typescript export class StyleConstants { /** * 颜色常量 */ // 主题色 static readonly PRIMARY_COLOR: string = '#5e35b1'; static readonly SECONDARY_COLOR: string = '#4527a0'; static readonly ACCENT_COLOR: string = '#03dac6'; // 文本颜色 static readonly TEXT_PRIMARY: string = '#212121'; static readonly TEXT_SECONDARY: string = '#757575'; static readonly TEXT_WHITE: string = '#FFFFFF'; static readonly TEXT_GRAY: string = '#9e9e9e'; // 背景颜色 static readonly BACKGROUND_COLOR: string = '#FFFFFF'; static readonly BACKGROUND_GRAY: string = '#f5f5f7'; static readonly BACKGROUND_LIGHT_GRAY: string = '#f5f5f5'; /** * 尺寸常量 */ // 字体大小 static readonly FONT_SIZE_SMALL: number = 12; static readonly FONT_SIZE_MEDIUM: number = 14; static readonly FONT_SIZE_LARGE: number = 16; static readonly FONT_SIZE_XLARGE: number = 18; // 间距 static readonly MARGIN_SMALL: number = 4; static readonly MARGIN_MEDIUM: number = 8; static readonly MARGIN_LARGE: number = 16; static readonly MARGIN_XLARGE: number = 24; // 圆角 static readonly RADIUS_SMALL: number = 8; static readonly RADIUS_MEDIUM: number = 8; static readonly RADIUS_LARGE: number = 12; static readonly RADIUS_XLARGE: number = 16; static readonly RADIUS_CIRCLE: number = 100; // 组件高度 static readonly STATUS_BAR_HEIGHT: number = 44; static readonly BOTTOM_NAV_HEIGHT: number = 56; static readonly MINI_PLAYER_HEIGHT: number = 64; } ``` #### 技术要点: 1. **静态常量**: 使用static readonly定义不可变的常量 2. **分类组织**: 按照颜色、尺寸等类别组织常量 3. **命名规范**: 使用全大写和下划线分隔的命名方式 ### 8. 图标工具类 (IconUtils.ets) 图标工具类集中管理应用中使用的所有图标资源。 #### 关键代码分析: ```typescript export class IconUtils { // 导航图标 static readonly HOME: Resource = $r('app.media.ic_home'); static readonly SEARCH: Resource = $r('app.media.ic_search'); static readonly LIBRARY: Resource = $r('app.media.ic_library'); static readonly USER: Resource = $r('app.media.ic_user'); static readonly SETTINGS: Resource = $r('app.media.ic_settings'); // 播放控制图标 static readonly PLAY: Resource = $r('app.media.ic_play'); static readonly PAUSE: Resource = $r('app.media.ic_pause'); static readonly NEXT: Resource = $r('app.media.ic_next'); static readonly PREV: Resource = $r('app.media.ic_prev'); static readonly REPEAT: Resource = $r('app.media.ic_repeat'); static readonly SHUFFLE: Resource = $r('app.media.ic_shuffle'); // 其他图标... } ``` #### 技术要点: 1. **资源引用**: 使用$r函数引用应用资源 2. **静态常量**: 使用static readonly定义不可变的图标引用 3. **分类组织**: 按照功能分类组织图标资源 ## ArkTS语言特性 ### 1. 装饰器 ArkTS使用装饰器来扩展组件和属性的功能: - **@Entry**: 标记应用入口组件 - **@Component**: 定义自定义组件 - **@State**: 定义组件内部状态,状态变化会触发UI刷新 - **@Prop**: 定义从父组件接收的只读属性 - **@Link**: 实现与父组件的双向数据绑定 - **@Builder**: 定义UI构建函数,类似于函数式组件 ### 2. 声明式UI ArkTS使用声明式语法描述UI结构,类似于React和SwiftUI: ```typescript build() { Column() { Text('Hello World') .fontSize(20) .fontColor('#FF0000') Button('Click Me') .width('80%') .height(40) .onClick(() => { console.info('Button clicked'); }) } .width('100%') .height('100%') .backgroundColor('#FFFFFF') } ``` ### 3. 状态管理 ArkTS提供了多种状态管理机制: - **@State**: 组件内部状态 - **@Prop**: 父组件传递的只读属性 - **@Link**: 父子组件间的双向绑定 - **@Provide/@Consume**: 跨组件层级的状态共享 ### 4. 生命周期 ArkTS组件有以下生命周期函数: - **aboutToAppear()**: 组件即将出现时调用 - **aboutToDisappear()**: 组件即将消失时调用 - **onPageShow()**: 页面显示时调用 - **onPageHide()**: 页面隐藏时调用 - **onBackPress()**: 用户点击返回按钮时调用