学习例程6.2:HarmonyOS 鸿蒙Next 一个带语音的故事板(优化并添加注释)
学习例程6.2:HarmonyOS 鸿蒙Next 一个带语音的故事板(优化并添加注释) 对《学习例程6.1》的代码优化,并添加注释。
// 导入媒体模块,用于音频播放
import { media } from "@kit.MediaKit";
// 导入Ability模块,用于获取上下文
import { common } from "@kit.AbilityKit";
// 音频播放服务类,封装音频播放逻辑
class AudioPlayerService {
private avPlayer: media.AVPlayer | null = null; // AVPlayer实例,用于播放音频
// 播放音频文件
async play(soundRaw: string, onComplete: () => void, onError: (error: string) => void) {
try {
// 如果已有AVPlayer实例,先释放资源
if (this.avPlayer) {
this.avPlayer.release();
}
// 创建AVPlayer实例
this.avPlayer = await media.createAVPlayer();
// 监听状态变化
this.avPlayer.on('stateChange', async (state: string) => {
switch (state) {
case 'initialized': // 初始化完成
this.avPlayer!.prepare(); // 准备播放
break;
case 'prepared': // 准备完成
this.avPlayer!.play(); // 开始播放
break;
case 'completed': // 播放完成
onComplete(); // 执行完成回调
this.avPlayer!.release(); // 释放资源
this.avPlayer = null; // 清空实例
break;
case 'error': // 播放错误
onError('音频播放失败'); // 执行错误回调
break;
}
});
// 获取上下文并加载音频文件
let context = getContext(this) as common.UIAbilityContext;
let fileDescriptor = await context.resourceManager.getRawFd(soundRaw);
let avFileDescriptor: media.AVFileDescriptor = {
fd: fileDescriptor.fd, // 文件描述符
offset: fileDescriptor.offset, // 文件偏移量
length: fileDescriptor.length, // 文件长度
};
this.avPlayer.fdSrc = avFileDescriptor; // 设置音频源
} catch (error) {
onError(`音频加载失败: ${error}`); // 捕获并处理错误
}
}
}
// 角色类,表示故事中的角色
@ObservedV2 // 标记为可观察类,用于UI更新
export class Role {
public name: string; // 角色名称
public introduce: string; // 角色介绍
public headPicture: Resource | string; // 角色头像
constructor(
name: string,
introduce: string,
headPicture: Resource | string
) {
this.name = name;
this.introduce = introduce;
this.headPicture = headPicture;
}
}
// 故事板单元类,表示故事中的一个单元(如一段对话)
@ObservedV2 // 标记为可观察类,用于UI更新
export class StoryBoardUnit {
public name: string; // 单元名称
public role: Role; // 关联的角色
public words: string; // 对话内容
public sound: string; // 音频文件路径
public picture: Resource | string; // 单元图片
constructor(
name: string,
role: Role,
words: string,
sound: string,
picture: Resource | string = ""
) {
this.name = name;
this.role = role;
this.words = words;
this.sound = sound;
this.picture = picture;
}
}
// 故事板帧类,表示故事中的一个帧(包含多个单元)
@ObservedV2 // 标记为可观察类,用于UI更新
export class StoryBoardFrame {
public units: StoryBoardUnit[]; // 包含的故事单元
public backPicture: Resource | string; // 背景图片
constructor(
units: StoryBoardUnit[] = [],
backPicture: Resource | string = ""
) {
this.units = units;
this.backPicture = backPicture;
}
}
// 逐字显示组件,用于逐字显示对话内容
@ComponentV2 // 标记为组件
export struct ViewTxtShow {
@Param Words: string = ""; // 接收外部传入的对话内容
@Local private wordList: string[] = []; // 将对话内容拆分为字符数组
@Local private wordOpacity: number[] = []; // 每个字符的透明度
// 组件加载时调用
aboutToAppear(): void {
this.updateWordList(); // 初始化字符数组
this.animateText(); // 开始逐字显示动画
}
// 将对话内容拆分为字符数组
private updateWordList() {
this.wordList = Array.from(this.Words); // 将字符串转换为字符数组
this.wordOpacity = new Array(this.wordList.length).fill(0.3); // 初始化透明度数组
}
// 逐字显示动画
private animateText(idx: number = 0) {
if (idx >= this.wordList.length) {
return; // 如果所有字符都已显示,结束动画
}
setTimeout(() => {
this.wordOpacity[idx] = 1; // 设置当前字符的透明度为1(完全显示)
this.animateText(idx + 1); // 递归显示下一个字符
}, 100); // 每100毫秒显示一个字符
}
// 构建UI
build() {
Grid() {
ForEach(this.wordList, (char: string, idx: number) => {
GridItem() {
Text(this.Words[idx]) // 显示字符
.opacity(this.wordOpacity[idx]) // 设置透明度
.animation({ curve: Curve.Ease }) // 添加动画效果
.fontColor('white') // 设置字体颜色
}
}, (item: string, idx: number) => idx.toString()); // 使用索引作为键值
}
.direction(Direction.Auto) // 设置布局方向
.width("100%"); // 设置宽度
}
}
// 故事板主组件,用于展示故事板内容
@ComponentV2 // 标记为组件
export struct ViewStoryBoard {
@Local private nowStoryFrame: StoryBoardFrame | undefined = undefined; // 当前故事帧
@Local private nowStoryUnit: StoryBoardUnit | undefined = undefined; // 当前故事单元
@Local private headOpacity: number = 0.1; // 角色头像的透明度
private audioPlayer: AudioPlayerService = new AudioPlayerService(); // 音频播放服务实例
// 组件加载时调用
aboutToAppear(): void {
this.initializeStory(); // 初始化故事内容
}
// 初始化故事内容
private initializeStory() {
this.nowStoryFrame = new StoryBoardFrame(
[
new StoryBoardUnit(
"曹操说",
new Role("曹操", "魏国丞相", $r("app.media.caocaoHeadRight")),
"呵呵!吾不笑别人,单笑周瑜无谋,诸葛亮少智。\n若是吾用兵之时,预先在这里伏下一军,如之奈何?",
"caocao_01.mp3"
),
new StoryBoardUnit(
"赵云说",
new Role("赵云", "蜀国大将", $r("app.media.zhaoyun_head")),
"我赵子龙奉军师将令,在此等候多时了!",
"zhaoyun_01.mp3"
),
],
$r("app.media.huarongdao") // 设置背景图片
);
this.playUnit(0); // 开始播放第一个单元
}
// 播放指定索引的故事单元
private async playUnit(idx: number) {
if (!this.nowStoryFrame || idx >= this.nowStoryFrame.units.length) {
return; // 如果索引无效,直接返回
}
this.nowStoryUnit = this.nowStoryFrame.units[idx]; // 设置当前单元
this.headOpacity = 0.1; // 初始化头像透明度
if (this.nowStoryUnit.sound) {
// 播放音频
await this.audioPlayer.play(
this.nowStoryUnit.sound,
() => this.playUnit(idx + 1), // 播放完成后播放下一个单元
(error) => console.error(error) // 错误处理
);
}
this.headOpacity = 1; // 显示角色头像
}
// 构建UI
build() {
Stack() {
Image(this.nowStoryFrame?.backPicture) // 显示背景图片
.opacity(1);
Stack() {
Row()
.width('100%')
.height(80)
.backgroundColor('black')
.opacity(0.8);
Row() {
Column() {
Image(this.nowStoryUnit?.role?.headPicture) // 显示角色头像
.width("20%")
.backgroundColor("#aaaaaa")
.borderRadius('50%')
.animation({ curve: Curve.Ease })
.opacity(this.headOpacity);
Text(this.nowStoryUnit?.role?.name) // 显示角色名称
.fontColor("#aabbcc")
.fontSize(10);
}
if (this.nowStoryUnit?.words) {
ViewTxtShow({ Words: this.nowStoryUnit.words }) // 显示对话内容
.width('80%')
.margin(5);
}
}
}
.width('100%')
.alignContent(Alignment.Start);
}
.width('100%')
.height('300')
.alignContent(Alignment.BottomStart);
}
}
// 入口组件
@ComponentV2 // 标记为组件
@Entry // 标记为入口组件
export struct TestStoryBoard {
build() {
Column() {
ViewStoryBoard(); // 加载故事板主组件
}
}
}
优化/注释 by DeepSeek
更多关于学习例程6.2:HarmonyOS 鸿蒙Next 一个带语音的故事板(优化并添加注释)的实战教程也可以访问 https://www.itying.com/category-93-b0.html
1 回复
更多关于学习例程6.2:HarmonyOS 鸿蒙Next 一个带语音的故事板(优化并添加注释)的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
HarmonyOS鸿蒙Next的带语音故事板例程6.2主要涉及ArkUI框架的使用,通过声明式UI和状态管理实现交互式界面。故事板功能通过@State
和@Prop
装饰器管理状态,@Builder
用于构建可复用的UI组件。语音功能通过@ohos.multimedia.audio
模块实现,包括音频播放和语音识别。代码结构分为视图层和逻辑层,视图层使用ArkTS编写,逻辑层处理数据和业务逻辑。注释部分解释了关键代码的作用,如状态管理、UI构建和音频处理流程。优化部分包括性能提升和代码简洁性改进。