鸿蒙纪·梦始卷#08 | 电子木鱼 - 音效播放与动画

408 阅读5分钟

《鸿蒙纪元》张风捷特烈 计划打造的一套 HarmonyOS 开发系列教程合集。致力于创作优质的鸿蒙原生学习资源,帮助开发者进入纯血鸿蒙的开发之中。本系列的所有代码将开源在 HarmonyUnit 项目中:

github: github.com/toly1994328…
gitee: gitee.com/toly1994328…

鸿蒙纪元 系列文章列表可在《文章总集》 或 【github 项目首页】 查看。


上一篇,我们进入了 电子木鱼 的功能需求,并实现了基本的静态界面布局。了解了 position 绝对定位和 borderRadius 圆角半径两个公共组件方法。

本文,我们将实现 电子木鱼 的基本功能,完成 音效播放+1 动画 的需求。


1. 音效初始化和加载

音效有别于普通的音乐文件,它们一般比较短小,而且需要频繁播放。鸿蒙提供了 SoundPool 相关类,实现音频池的功能。官方文档 《SoundPool (音频池)》

音效播放时,需要: 初始化 SoundPool --> 加载资源 --> 播放资源。对于 业务逻辑 的处理这里希望在一开始就和视图的构建逻辑分离。这里称业务逻辑 (Business Logic) 对象为 BLoc,你可以将其视为 MVVM 中的 ViewModel。

如上所示,MuyuBloc 中将负责维护业务层的数据和功能实现,视图层的组件将依赖于 MuyuBloc 的数据和行为能力。如果业务逻辑非常复杂,还可以进一步根据功能进行拆分,不过目前这样就够用了。


关于音频的播放,MuyuBloc 中维护三个成员:

  • player: SoundPool 对象,音频池核心对象
  • soundId: 音频资源 id ,播放资源的依据。
  • ready : 记录 player 和 soundId 是否以及初始、加载完毕。

constructor 函数会在对象初始化时调用,这里触发了 initAndLoad 方法负责初始化和加载资源。处理完毕后,会将 ready 置为 true 表示准备完毕:

import { BusinessError } from '@kit.BasicServicesKit';
import { audio } from '@kit.AudioKit';
import { media } from '@kit.MediaKit';

export class MuyuBloc {

  private player?: media.SoundPool;
  private soundId?: number;
  private ready = false;

  constructor() {
    this.initAndLoad();
  }

  async initAndLoad() {
    await this.initSoundPool();
    await this.loadSounds();
    this.ready = true;
  }
}

initSoundPool 方法负责调用 media.createSoundPool 方法,对 player 对象进行初始化:

async initSoundPool() {
  this.player = await media.createSoundPool(1, {
    usage: audio.StreamUsage.STREAM_USAGE_GAME,
    rendererFlags: 1
  },)
}

播放音效,需要通过 SoundPool 加载资源,得到对应的 id 作为播放的标识。在 rawfile 中的文件,可以通过 getContext().resourceManager.getRawFd 获取资源的文件描述信息。
SoundPool 通过 load 方法,根据文件描述信息,将音频加入到音频池中,并通过回调得到音频 id。为了更方便通过异步得到 soundId ,这里封装了 loadSoundId 方法,将回调通过结果 Promise 对象返回:

async loadSounds() {
  this.soundId = await this.loadSoundId("muyu_3.mp3");
}

async loadSoundId(rawFile: string): Promise<number> {
  let res = await getContext().resourceManager.getRawFd(rawFile);
  return new Promise<number>((resolve, reject) => {
    this.player!.load(res.fd, res.offset, res.length, (error, id: number) => {
      if (!error) {
        resolve(id);
      } else {
        reject(error);
      }
    })
  });
}

2. 功德增加与音频播放、销毁

功德每次点击都会增加,只个功能点本质上和计数器并没有什么不同。这里介绍一个 @Track 的状态管理修饰:

@Track装饰类属性,当属性变化时,只会触发该属性关联的UI更新。

所以它,一方面可以减少不必要的构建被触发,另一方面,在 MuyuBloc 中也可以很明确哪些是和构建相关的数据:

export class MuyuBloc {
  @Track counter: number = 0;

  private player?: media.SoundPool;
  private ready = false;
  private soundId?: number;

使用 SoundPool#play 方法可以播放指定的音频 id,在 MuyuBloc 中定义 tick 方法,触发播放以及计数器增加的逻辑。另外,当 ready 为 fasle 时,不应该播放,可以提示用户资源正在加载。一般来说短音效的加载是很快的,这里也就不做额外处理了。视图层,在点击图片时,触发 MuyuBloc#tick 即可:

tick(): void {
  if (!this.ready) {
    return;
  }
  this.player?.play(this.soundId);
  this.counter++;
}

在界面退出后,一般会销毁资源以免内存的浪费,这里定义 release 方法,触发 SoundPool#release 销毁资源。在视图层的 aboutToDisappear 中可以监听到组件被销毁的时机。

---->[muyu/bloc/MuyuBloc.ets]----
async release() {
  await this.player?.release();
  this.ready = false;
}

---->[muyu/bloc/MuyuPage.ets]----
aboutToDisappear(): void {
  this.model.release();
}

这样就是闲聊敲击木鱼时,播放音效,并且功德增加的需求。可以看出 业务逻辑视图构建逻辑 分离之后。静态界面到功能实现,仅需要依赖一下 MuyuBloc 的数据和行为,代码上基本没有什么变动。这里提交一个小里程碑: v13-电子木鱼-音效与计数器

--

3. 功德 +1 动画的处理

最后,来处理一下点击时 功德 +1 的文字动画效果,动画效果包括三个属性:字体逐渐变大、偏移量变大、透明度增加:

--

《鸿蒙纪·梦始卷#06》 介绍过属性动画的使用,这里就不赘述了。我们封装一个 AnimaValue 组件单独处理动画的文字,其中 rate 状态表示动画的数值;fontSizetranslateopacity 三个属性都基于 rate 值进行计算:
另外,监听 counter 参数,触发 startAnimation 方法执行动画,这样点击时数字的变化,就会让动画启动,实现期望的效果:

@Component
export struct AnimaValue {
  @Prop @Watch('startAnimation') counter: number;
  @State rate: number = 0;

  build() {
    Text('功德 +1')
      .fontSize(8 + this.rate * 8)
      .translate({ y: -60 - this.rate * 64 })
      .opacity(this.rate * 0.7)
  }

  startAnimation(): void {
    this.rate = 0;
    let param: AnimateParam = {
      curve: Curve.LinearOutSlowIn, duration: 400, onFinish: () => this.reset()
    };
    this.getUIContext()?.animateTo(param, () => this.onEvent());
  }

  onEvent(): void {
    this.rate = 1;
  }

  reset(): void {
    this.rate = 0;
  }
}

4. 尾声

到这里,电子木鱼的基本功能完成了,提交一个小里程碑:v14-电子木鱼-基本功能。下一篇将带来电子木鱼的丰富功能,支持多个音频和木鱼类型的切换。
更多文章和视频知识资讯,大家可以关注我的公众号、掘金和 B 站 。关注 公众号 并回复 鸿蒙纪元 可领取最新的 xmind 脑图电子版,让我们一起成长,变得更强。我们下次再见~