鸿蒙高仿苹果音乐

HarmonyOS-Music 项目技术文档

项目概述

点击下载项目源码

HarmonyOS-Music是一款基于HarmonyOS平台开发的音乐应用,采用ArkTS语言和HarmonyOS的声明式UI开发框架,为用户提供音乐播放、搜索、收藏等功能。项目展示了HarmonyOS应用开发的核心技术和最佳实践,包括组件化设计、状态管理和页面导航等。

技术栈

  • ArkTS: HarmonyOS的声明式UI开发语言,基于TypeScript扩展
  • HarmonyOS UI框架: 提供丰富的UI组件和布局能力
  • 状态管理: 使用@State、@Prop、@Link等装饰器管理组件状态
  • 组件化开发: 采用自定义组件封装复用逻辑

项目结构

bash 复制代码
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(): 用户点击返回按钮时调用
相关推荐
别说我什么都不会8 分钟前
OpenHarmony解读之设备认证:sts协议-客户端发起start请求
物联网·嵌入式·harmonyos
聆听秋天的风8 分钟前
HarmonyOS NEXT练习:跑马灯
harmonyos
二流小码农22 分钟前
鸿蒙开发:使用Circle绘制圆形
android·ios·harmonyos
东林知识库41 分钟前
鸿蒙NEXT小游戏开发:垃圾分类
harmonyos
东林知识库41 分钟前
鸿蒙NEXT小游戏开发:区字棋
harmonyos
余多多_zZ42 分钟前
HarmonyOSNext_API16_媒体查询
笔记·学习·华为·harmonyos·媒体
东林知识库42 分钟前
鸿蒙NEXT小游戏开发:数字华容道
harmonyos
东林知识库43 分钟前
鸿蒙NEXT小游戏开发:推箱子
harmonyos
东林知识库1 小时前
鸿蒙NEXT小游戏开发:拼图
harmonyos
东林知识库1 小时前
鸿蒙NEXT小游戏开发:打地鼠
harmonyos