《鸿蒙纪元》 是 张风捷特烈 计划打造的一套 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
状态表示动画的数值;fontSize
、translate
、opacity
三个属性都基于 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 脑图电子版,让我们一起成长,变得更强。我们下次再见~