HarmonyOS实战开发案列-如何开发一个音乐播放器

567 篇文章 8 订阅
555 篇文章 0 订阅

在这里插入图片描述

这个项目开始是在api11版本下开发的,但是为了更多的友友学习,我还是把它向下适配到了api9,所以升级到api11或者api12的友友也可以瞅瞅,改动不大。

下面为大家讲解项目中的一些要点:

布局

页面布局比较简单,一眼望去纵向布局,中间穿插着一些横向布局,从上往下排就可以了。

在这里插入图片描述
要注意的是,页面底部还有个音乐列表弹窗,弹窗与主页面是层叠布局,所以主页面和弹窗要用Stack包裹。
在这里插入图片描述
因为弹窗是默认隐藏的,所以设置列表弹窗的初始y值为屏幕高度,布局基本代码如下:

@State screenWidth:number = px2vp(display.getDefaultDisplaySync().width)
@State screenHeight:number = px2vp(display.getDefaultDisplaySync().height)
@State listViewPosition:number = this.screenHeight

Stack({alignContent:Alignment.Bottom}){
      //主页面
      Flex({direction:FlexDirection.Column,justifyContent:FlexAlign.SpaceBetween}){
        
      }
      .width('100%')
      .height('100%')
      .padding({left:20,right:20})
      //背景色渐变
      .radialGradient({
        center: [this.screenWidth/2, this.screenHeight/2],
        radius: this.screenWidth*2,
        colors: [[0xE0EEFF, 0.0], [0xE4F2FF, 0.3], [0xFBD4FF, 1]]
      })

      //底部弹窗
      ListView({musicList:$musicList)
          //设置初始位置
        .position({y:this.listViewPosition})

    }

关于弹窗的弹出和隐藏动画,会在下面动画部分讲解。

动画

本项目中的动画主要有两个,首先是音乐封面的匀速旋转,我选择使用计时器setInterval()来改变图片的角度:

 //初始角度为0
  @State angle: number = 0;

  //点击播放按钮执行动画
  startRotate() {
    this.timer = setInterval(() => {
      //保留2位小数
      this.angle = this.angle + 0.005
    }, 30);
  }

  //音乐封面
  Image($rawfile(this.musicList[this.currentIndex].cover))
            .width(this.screenWidth - 50)
            .height(this.screenWidth - 50)
            .borderRadius((this.screenWidth - 50)/2)
            .objectFit(ImageFit.Fill)
            .rotate({ x: 0, y: 0, z: 1, angle: this.angle*360 })

接下来是弹窗的出现和隐藏,前面说了弹窗一开始在屏幕底部看不到的位置,点击列表按钮时使用animateTo给它设置一个正确的y值即可实现由下向上的动画效果:

animateTo({
            //动画时间
           duration: 200,
       }, () => {
             //计算弹窗高度
            let y = 20 + 40 + 62*this.musicList.length
            //设置y值
            this.listViewPosition = this.screenHeight - y
       })

隐藏弹窗的动画相对麻烦一些,我做了一个类似手机屏幕底部的横条,在下拉横条的时候隐藏动画。
图片

所以要给图中横条添加下滑手势,在下滑手势结束时使用刚才的方法将y值恢复初始值:

private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Down })

Row(){
   }
   .width(40)
   .height(4)
   .backgroundColor('rgb(234,235,237)')
   .borderRadius(2)
   .gesture(
        PanGesture(this.panOption)
          .onActionStart((event?: GestureEvent) => {
            //识别到下拉手势
            console.info('Pan start')
          })
          .onActionUpdate((event?: GestureEvent) => {
            if (event) {
             //下拉手势执行中
              console.info('event:',JSON.stringify(event))
            }
          })
          .onActionEnd(() => {
            console.info('Pan end')
             //下拉手势结束
             animateTo({
                duration: 200,
              }, () => {
                this.listViewPosition = this.screenHeight
              })
          })
      )

播放音频

本文使用AVPlayer来播放音频,音频素材是本地rawfile中的文件,您也可以选择使用网络音频,注意播放网络音频要申请网络权限,它们的创建方法分别如下:
播放rawfile文件:

// 创建avPlayer实例对象
let avPlayer: media.AVPlayer = await media.createAVPlayer();
// 创建状态机变化回调函数
this.setAVPlayerCallback(avPlayer);
// 通过UIAbilityContext的resourceManager成员的getRawFd接口获取媒体资源播放地址
// 返回类型为{fd,offset,length},fd为HAP包fd地址,offset为媒体资源偏移量,length为播放长度
let context = getContext(this) as common.UIAbilityContext;
let fileDescriptor = await context.resourceManager.getRawFd(this.musicList[this.currentIndex].url);
let avFileDescriptor: media.AVFileDescriptor =
     { fd: fileDescriptor.fd, offset: fileDescriptor.offset, length: fileDescriptor.length };
this.isSeek = false; // 支持seek操作
// 为fdSrc赋值触发initialized状态机上报
avPlayer.fdSrc = avFileDescriptor;
this.avPlayer = avPlayer

播放网络音频:

// 创建avPlayer实例对象
let avPlayer: media.AVPlayer = await media.createAVPlayer();
// 创建状态机变化回调函数
this.setAVPlayerCallback(avPlayer);
this.isSeek = false; // 不支持seek操作
avPlayer.url = 'https://cdn.soundstripe.com/uploads/audio_file/381e129db0964d5289ebdaec01f61bf1/mp3_PALA_OneMoreDance_Full.mp3?token=1712968809_026183eab08f6d0f3a7aa5d422212a980252cc82b135e6a9950ee00fc8d74b73';
this.avPlayer = avPlayer

第二步,为avPlayer设置回调函数:

// 注册avplayer回调函数
  setAVPlayerCallback(avPlayer: media.AVPlayer) {

    avPlayer.on('timeUpdate', (seekDoneTime: number) => {

      if(this.duration > 0){
        let progress = seekDoneTime/this.duration
        this.progressNow = progress*100
      }
      this.progressTimeString = this.durationToTimeString(seekDoneTime)
    })
    // seek操作结果回调函数
    avPlayer.on('seekDone', (seekDoneTime: number) => {
      console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
    })

    // 状态机变化回调函数
    avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => {
      switch (state) {
        case 'idle': // 成功调用reset接口后触发该状态机上报
          console.info('AVPlayer state idle called.');
          avPlayer.release(); // 调用release接口销毁实例对象
          break;
        case 'initialized': // avplayer 设置播放源后触发该状态上报
          console.info('AVPlayer state initialized called.');
          avPlayer.prepare();
          break;
        case 'prepared': // prepare调用成功后上报该状态机
          console.info('AVPlayer state prepared called.');
          if(this.isPlay){
            avPlayer.play();
          }
          this.duration = avPlayer.duration
          this.durationTimeString = this.durationToTimeString(this.duration)
          console.log('duration:',avPlayer.duration)

          break;
        case 'playing': // play成功调用后触发该状态机上报
          console.info('AVPlayer state playing called.');
          if (this.count !== 0) {
            if (this.isSeek) {
              console.info('AVPlayer start to seek.');
              avPlayer.seek(avPlayer.duration); //seek到音频末尾
            } else {
              // 当播放模式不支持seek操作时继续播放到结尾
              console.info('AVPlayer wait to play end.');
            }
          } else {
            // avPlayer.pause(); // 调用暂停接口暂停播放
          }
          this.count++;
          break;
        case 'paused': // pause成功调用后触发该状态机上报
          console.info('AVPlayer state paused called.');
        // avPlayer.play(); // 再次播放接口开始播放
          break;
        case 'completed': // 播放结束后触发该状态机上报
          console.info('AVPlayer state completed called.');
        // avPlayer.stop(); //调用播放结束接口
        // this.endRotate();
          if(this.currentIndex < this.musicList.length - 1){
            this.currentIndex += 1
            this.changeSong()
          }else {
            this.endRotate();
          }
          break;
        case 'stopped': // stop接口成功调用后触发该状态机上报
          console.info('AVPlayer state stopped called.');
        // avPlayer.reset(); // 调用reset接口初始化avplayer状态
          break;
        case 'released':
          console.info('AVPlayer state released called.');
          break;
        default:
          console.info('AVPlayer state unknown called.');
          break;
      }
    })
  }

下面是AVPlayer的常用方法:

//播放
this.avPlayer.play() 
//暂停
this.avPlayer.pause() 
//结束
this.avPlayer.stop() 
//重置
this.avPlayer.reset() 
//销毁释放
this.avPlayer.release()

对于如何进行切换歌曲,只需要重置当前AVPlayer对象并重新初始化即可。

以上就是对音乐播放器项目的讲解,由于涉及到播放音频和获取设备屏幕尺寸,请使用模拟器或真机运行项目。

如果大家还没有掌握鸿蒙,现在想要在最短的时间里吃透它,我这边特意整理了《鸿蒙语法ArkTS、TypeScript、ArkUI等…视频教程》以及《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

鸿蒙语法ArkTS、TypeScript、ArkUI等…视频教程:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

在这里插入图片描述

OpenHarmony APP应用开发教程步骤:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

在这里插入图片描述

《鸿蒙开发学习手册》:

如何快速入门:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

1.基本概念
2.构建第一个ArkTS应用
3.……

在这里插入图片描述

开发基础知识:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

在这里插入图片描述

基于ArkTS 开发:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

在这里插入图片描述

鸿蒙生态应用开发白皮书V2.0PDF:https://docs.qq.com/doc/DZVVkRGRUd3pHSnFG

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值