赞
踩
永远记住,最好的学习方法就是实战,今天和大家分享的是鸿蒙开发的实战项目:音乐播放器。
话不多说,先看项目演示效果:
这个项目开始是在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对象并重新初始化即可。
有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。
这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。
希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!
如果你是一名有经验的资深Android移动开发、Java开发、前端开发、对鸿蒙感兴趣以及转行人员,可以直接领取这份资料
获取这份完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料
HarmonOS基础技能
有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。
获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料
OpenHarmony北向、南向开发环境搭建
获取以上完整鸿蒙HarmonyOS学习资料,请点击→纯血版全套鸿蒙HarmonyOS学习资料
总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,他们才能在这个变革的时代中立于不败之地。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。