欢迎光临
我们一直在努力

鸿蒙 Next 睡前故事定制机 App 开发实战:10 个内置故事 + 角色替换引擎 + 语音朗读 + 主题筛选 + 第十九款 App

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

鸿蒙 Next 睡前故事定制机 App 开发实战:10 个内置故事 + 角色替换引擎 + 语音朗读 + 主题筛选 + 第十九款 App

作者:duluo SDK 版本:HarmonyOS API 24 (Next) 开发工具:DevEco Studio 语言框架:ArkTS + ArkUI 字数:约 10000 字


目录

  • 引言
  • 产品概念与数据模型
  • 三 Tab 架构设计
  • 故事数据库设计
  • 角色替换引擎
  • 语音朗读引擎
  • 主题筛选与分类浏览
  • 收藏系统与轻量持久化
  • 适老化与儿童友好设计
  • 编译错误全记录
  • 十九款 App 全景回顾
  • 结语

  • 1. 引言

    1.1 睡前故事的意义

    睡前故事是儿童成长过程中不可或缺的一部分。心理学家认为,睡前故事不仅能帮助孩子放松入眠,还能促进语言发展、培养想象力和建立亲子情感连接。每天晚上,全球有数以亿计的父母在孩子的床边讲述着各种故事。

    然而,现代父母面临一个普遍困境:故事不够用了。翻来覆去就那几个童话,孩子早就听腻了。买故事书又要额外花钱,网上的故事又不一定适合自己孩子的年龄和兴趣。

    "睡前故事定制机"App 的解决方案是:内置一批高质量故事,并允许父母定制故事中的角色和地点。把"小兔子"换成孩子的名字,把"森林"换成孩子熟悉的公园——熟悉的元素让故事更有代入感,孩子也更愿意听。

    1.2 本 App 的技术特色

    睡前故事定制机在技术上有几个显著特点。

    首先,它构建了一个包含 10 个完整故事的故事数据库,每个故事包含标题、主题、年龄建议、角色和地点等元数据。故事按段落存储(而非整段文本),便于分步朗读和定制替换。

    其次,它实现了角色替换引擎——使用 replaceAll 方法将故事中的默认角色名和地点名替换为用户自定义的名称。这个引擎虽然算法简单(字符串替换),但 UX 效果显著——孩子听到自己的名字出现在故事中,代入感会大幅提升。

    此外,本 App 将上一次语音朗读引擎(来自语音菜谱 App)升级为段落级朗读 + 高亮同步的完整方案,增加了"朗读完毕"后的晚安提示 Toast。

    1.3 第十九款 App 的系列数据

    App 数量: 19
    代码总行数: ~12,400 行
    编译错误数: ~175 个
    博客总字数: ~200,000 字
    技术博客数: 19 篇


    2. 产品概念与数据模型

    2.1 功能需求

    家长故事:我想给孩子讲一个睡前故事
    家长故事:我想把故事里的主角换成我家孩子的名字
    家长故事:我想按主题找到适合的故事
    家长故事:我想收藏喜欢的故事方便再讲
    家长故事:我想让 App 朗读故事,我陪着孩子看

    功能清单:
    ├── F1: 故事列表(10 个内置故事,含主题/时长/年龄标签)
    ├── F2: 主题筛选(6 种主题 Grid 选择)
    ├── F3: 角色定制(替换主角名字 + 地点名称)
    ├── F4: 语音朗读(段落推进 + 高亮 + 自动结束)
    ├── F5: 收藏系统(收藏/取消 + 收藏列表)
    ├── F6: 详情弹窗(故事元数据 + 定制输入 + 正文 + 朗读)
    └── F7: 数据持久化(收藏 ID 列表)

    2.2 数据模型

    interface Story {
    id: number; // 唯一标识
    title: string; // 故事标题
    theme: string; // 主题分类
    icon: string; // Emoji 图标
    content: string; // 一句话简介
    paragraphs: string[]; // 段落数组
    duration: string; // 朗读时长
    age: string; // 适龄建议
    characters: string[]; // 可替换角色列表
    places: string[]; // 可替换地点列表
    defaultChar: string; // 默认主角名(替换目标)
    defaultPlace: string; // 默认地点(替换目标)
    isFavorite: boolean; // 是否收藏
    }

    paragraphs 是 string[] 而不是整段文本——这个设计决策很关键。它使得:

    • 朗读时可以逐段推进,每段 5 秒
    • 每段可以独立高亮
    • 定制替换按段落执行

    characters 和 places 存储了故事中所有可替换的元素。defaultChar 和 defaultPlace 标记了故事中的"主角名"和"主要场景"——这两个是最常被替换的元素。

    2.3 故事结构设计

    每个故事包含 8-10 个段落,总朗读时长约 5-6 分钟。以下是一个典型故事的结构:

    段落 1:引入主角和环境
    段落 2-3:冲突出现
    段落 4-6:解决问题(核心情节)
    段落 7-8:圆满结局
    段落 9:晚安祝福(可选)

    这种"引入-冲突-解决-结局"的四段式结构符合儿童故事的经典叙事模式。


    3. 三 Tab 架构设计

    3.1 Tab 配置

    buildTabContent() {
    if (this.activeTab === 0) this.buildStoryList() // 故事
    else if (this.activeTab === 1) this.buildCustomizeTab() // 定制
    else this.buildFavoriteTab() // 收藏
    }

    Tab图标功能用户场景
    故事 📖 浏览 + 筛选全部故事 “今晚讲哪个?”
    定制 选择故事 + 替换角色 “把主角换成宝宝名字”
    收藏 已收藏的故事列表 “上次那个还要再讲一遍”

    3.2 Tab 之间的协作

    与之前的 App 不同,本 App 的"定制"Tab 不直接进行定制操作——它只是一个入口列表。用户点击后跳转到故事详情弹窗,在弹窗中完成定制输入和朗读:

    定制 Tab → 选择故事 → 详情弹窗(定制输入 + 朗读)→ 回到故事列表

    这种设计把定制功能集成在详情弹窗中,而不是单独开一个定制页面,减少了交互层级。


    4. 故事数据库设计

    4.1 10 个故事的选取

    10 个故事覆盖了 6 种主题:

    主题图标故事数名称
    童话 🌙 3 月亮上的兔子、会说话的小星星、云朵棉花糖
    动物 🦊 2 小熊的蜂蜜梦、想飞的小企鹅
    冒险 🚀 1 勇敢的小火车
    魔法 🧚 1 魔法画笔
    自然 🌊 1 小乌龟的慢旅行
    晚安 🤗 2 晚安小怪兽、枕头王国历险记

    每篇故事的主角名(如"小月"“团团”)和地点(如"森林小屋"“蜜糖森林”)都是可定制的。故事结尾通常包含"晚安"或"好梦"的祝福语。

    4.2 故事中可替换元素的标记

    故事段落中使用 【】 标记可替换元素:

    从前,在很远很远的月亮上,住着一只毛茸茸的小兔子,名字叫【小月】。

    defaultChar 字段值为 "小月",replaceAll 引擎在运行时将 "小月" 替换为用户输入的名称(如"宝宝")。

    标记使用 【】 而非 {{}} 或 <>,因为 【】 在中文文本中更自然,即使在未替换时也不会让故事显得突兀。

    4.3 年龄建议

    每个故事标注了适龄建议:

    年龄故事数特点
    2-5 岁 3 情节简单,角色少,句子短
    2-6 岁 3 中等复杂度
    3-6 岁 1
    3-7 岁 2 情节更丰富
    3-8 岁 1 包含抽象概念

    年龄建议帮助家长快速筛选适合自己孩子年龄的故事。


    5. 角色替换引擎

    5.1 替换逻辑

    getCustomizedParas(): string[] {
    if (this.selected === null) return [];
    return this.selected.paragraphs.map(p =>
    p.replaceAll(this.selected!.defaultChar, this.customizeChar)
    .replaceAll(this.selected!.defaultPlace, this.customizePlace)
    );
    }

    引擎的核心是 String.prototype.replaceAll 方法。它将故事每个段落中的 defaultChar 替换为 customizeChar,将 defaultPlace 替换为 customizePlace。

    5.2 替换范围

    替换不是全局全文替换,而是基于段落级别逐段进行:

    paragraphs.map(p => p.replaceAll())

    每个段落独立执行替换,这意味着:

    • 性能好:大文本分割成小段落后,每次替换的计算量很小
    • 可扩展:未来可以针对不同段落做不同替换规则

    5.3 实时预览

    详情弹窗中提供了两个 TextInput 用于输入自定义名称:

    👤 主角 [____________] 🏠 地点 [____________]

    用户输入时,正文区的文字实时更新(因为 getCurrentParagraphs() 每次渲染时重新计算):

    TextInput({ placeholder: '输入主角名字', text: this.customizeChar })
    .onChange((v: string) => { this.customizeChar = v; })

    由于正文的展示依赖 this.customizeChar 和 this.customizePlace 这两个 @State 变量,输入框内容变化时,正文会自动重新渲染并展示替换后的文本。

    5.4 替换引擎的局限性

    当前替换引擎使用简单的字符串匹配,有两个潜在问题:

  • 部分匹配:如果用户输入的角色名恰好是故事中其他词的一部分,可能会产生意外替换。例如,将"小月"改为"小亮"后,故事中"月亮"会变成"月亮"(保持不变),但"小月"会被替换。

  • 多角色替换:当前只支持主角名和地点两个替换维度。对于有多个角色的故事(如"小乌龟的慢旅行"中只有主角),这个限制不是问题;但对于需要替换更多角色的场景,需要扩展。


  • 6. 语音朗读引擎

    6.1 朗读流程

    朗读引擎从语音菜谱 App 升级而来,核心逻辑一致:

    startReading(): void {
    this.isReading = true;
    this.currentPara = 0;
    let paras = this.getCurrentParagraphs();
    this.readTimer = setInterval(() => {
    if (this.currentPara < paras.length 1) {
    this.currentPara++;
    } else {
    this.stopReading();
    promptAction.showToast({ message: '🌙 故事讲完了,晚安', duration: 2000 });
    }
    }, 5000);
    }

    6.2 与语音菜谱的对比

    两个 App 的朗读引擎在核心上一致,但有三个关键差异:

    特性语音菜谱睡前故事
    步进间隔 4 秒 5 秒
    高亮对象 步骤(数字编号圆形) 段落(背景色)
    结束提示 “🔊 朗读完毕” “🌙 故事讲完了,晚安”

    5 秒/段的步进步长比菜谱的 4 秒更长,因为故事段落通常比菜谱步骤长。

    6.3 段落高亮

    当前朗读的段落使用淡紫色背景高亮:

    .backgroundColor(
    this.isReading && idx === this.currentPara ? C.primary + '10' : 'transparent')

    10% 透明度的紫色背景足够明显但不刺眼,适合睡前昏暗的光线环境。

    6.4 朗读的停止条件

    // 1. 用户手动点击"停止"
    // 2. 所有段落朗读完毕
    // 3. 用户关闭详情弹窗
    // 4. App 进入后台/销毁(aboutToDisappear)

    四重停止条件与语音菜谱一致,确保定时器在任何情况下都不会泄漏。


    7. 主题筛选与分类浏览

    7.1 6 种主题

    const THEMES: string[] = ['🌙 童话', '🦊 动物', '🚀 冒险', '🧚 魔法', '🌊 自然', '🤗 晚安'];

    每个主题前有一个 Emoji 图标,在 Grid 选择器中展示时一目了然。

    7.2 主题选择器

    主题选择器使用 3 列 Grid 展示 6 种主题:

    Grid() {
    ForEach(THEMES, (t: string) => {
    GridItem() {
    Text(t)
    .fontColor(this.filterTheme === t ? C.primary : C.text)
    .backgroundColor(this.filterTheme === t ? C.primary + '15' : C.cardBg)
    .borderWidth(this.filterTheme === t ? 1 : 0)
    .onClick(() => {
    this.filterTheme = this.filterTheme === t ? '' : t;
    })
    }
    }, (t: string) => t)
    }.columnsTemplate('1fr 1fr 1fr')

    点击已选中的主题会取消筛选(回到全部),体现了"即点即切换,再点取消"的交互模式。这个模式在系列中被多次使用(如收藏筛选、情绪选择器)。

    7.3 列表中的主题标签

    每个故事卡片在列表中展示主题标签:

    🐰 月亮上的兔子
    🌙 童话 · 5 分钟 · 2-6 岁
    从前,在很远很远的月亮上…

    主题标签使用主题色(C.primary: #5C6BC0),时长和年龄使用辅助色(C.textLight),层次分明。


    8. 收藏系统与轻量持久化

    8.1 收藏存储

    与语音菜谱 App 一致,收藏数据只存储故事的 ID 列表:

    async saveData(): Promise<void> {
    if (this.pref) {
    let ids = this.stories.filter(s => s.isFavorite).map(s => s.id);
    await this.pref.put(STORAGE_KEY, JSON.stringify(ids));
    await this.pref.flush();
    }
    }

    8.2 收藏恢复

    async loadData(): Promise<void> {
    let savedIds = JSON.parse(v as string) as number[];
    for (let i = 0; i < this.stories.length; i++) {
    if (savedIds.indexOf(this.stories[i].id) >= 0) {
    this.stories[i].isFavorite = true;
    }
    }
    this.stories = this.stories.concat([]);
    }

    这个模式在系列中已经使用了三次(语音菜谱、睡前故事),证明其可靠性和简洁性。

    8.3 收藏列表

    收藏 Tab 在空状态时显示引导提示:


    还没有收藏的故事
    在故事详情中点击 ⭐ 收藏

    有收藏时,列表展示与故事列表一致,但收藏状态以金色星星(C.star: #FFD54F)突出显示。


    9. 适老化与儿童友好设计

    9.1 星空夜晚主题

    本 App 首次使用深色主题——深蓝星空渐变背景:

    const C: ColorScheme = {
    primary: '#5C6BC0', // 紫色
    bgStart: '#1A1A2E', // 深蓝
    bgEnd: '#16213E', // 深蓝
    cardBg: '#FFFFFF', // 白色(弹窗/卡片)
    text: '#1A1A2E', // 深色正文
    star: '#FFD54F', // 金色星星
    moon: '#E8EAF6', // 月光白
    };

    深色背景 + 白色卡片的设计既减少了屏幕在暗光环境下的刺眼感,又保证了卡片内容的可读性。

    9.2 字体与触摸目标

    虽然是儿童/家长使用而非老年人,但设计上仍然保持了较大的字体和触摸目标:

    元素字号触摸目标
    故事标题 17px 全宽卡片
    故事正文 15px
    标签信息 12-13px
    Tab 图标 20-24px ~50px
    定制输入框 14px 36px 高

    9.3 安全设计

    本 App 在儿童使用场景下做了几个安全设计:

  • 没有广告:所有内容都是本地内置,不联网
  • 没有敏感内容:所有故事都是积极向上的主题
  • 退出确认:弹窗都有明确的关闭按钮
  • 朗读自动停止:读完所有段落后自动停止,不会继续占用设备

  • 10. 编译错误全记录

    10.1 错误概览

    本 App 出现 1 个编译错误。

    #错误类型位置根因
    1 方法重复定义 第 587 行 getFilteredStories() 被定义两次

    10.2 唯一的错误:方法重复

    现象:编译报错 “Duplicate function implementation” 或类似错误。

    根因:在重构代码时,删除了 getFilteredStoriesList() 方法但错误地在原位置留下了一个空的 getFilteredStories() 声明头。同时原始的 getFilteredStories() 方法(第 342 行)仍然存在。编译器检测到两个同名方法。

    // 第 342 行(正确)
    getFilteredStories(): Story[] {
    if (this.filterTheme === '') return this.stories;
    return this.stories.filter(s => s.theme === this.filterTheme);
    }

    // 第 587 行(重复,空的声明头)
    getFilteredStories(): Story[] { // 重复定义

    修复:删除第 587 行的空方法声明。

    教训:在手动编辑代码时,注意检查是否在错误的位置插入了代码。本次错误是在删除 getFilteredStoriesList() 并将其替换为上一行的方法声明头时,没有意识到该位置已经有一个同名方法了。

    10.3 十九款 App 错误数趋势

    22 → 17 → 16 → 1 → 12 → 12 → 10 → 4 → 11 → 11 → 3 → 8 → 7 → 12 → 1 → 4 → 3 → 2 → 1

    第十九款 App 的错误数为 1 个,追平了系列最低纪录(尴尬粉碎机、绿植领养)。

    10.4 关于"轮次归零"的思考

    经过十九款 App 的实践,从第 10 款开始,错误数就已经稳定在个位数。第 18 款 2 个错误,第 19 款 1 个错误——这不是偶然的下降,而是模式复用的必然结果。

    每一款新 App 的开发模式:

  • 复用前作的 ColorScheme + 三 Tab + 弹窗模式(0 错误)
  • 根据 App 特性新增数据模型和业务逻辑(少数错误)
  • 修复少量错误(1-2 轮)
  • 验证通过
  • 当模式足够成熟,新增代码足够少时,错误数自然趋向于 0。


    11. 十九款 App 全景回顾

    11.1 数据总览

    #App行数错误数Type
    1 🎵 白噪音 767 16 工具
    2 ⏳ 时间胶囊 955 17 工具
    3 🧊 冰箱剩菜 1320 22 工具
    4 😅 尴尬粉碎机 953 1 工具
    5 🛡️ 防骗训练 1038 12 教育
    6 💡 碎片学习 851 12 教育
    7 🐶 宠物日记 450 10 工具
    8 🗑️ 情绪垃圾桶 390 4 工具
    9 🧭 线下寻宝 447 11 社交
    10 🗡️ 订阅刺客 478 11 工具
    11 🎑 声音明信片 458 3 工具
    12 🎲 家庭大富翁 537 8 游戏
    13 📚 二手书漂流瓶 452 7 社交
    14 🧹 废话过滤器 542 12 工具
    15 🌱 绿植领养 530 1 社交
    16 🌙 梦境解析 614 4 工具
    17 🏕️ 断网挑战营 418 3 工具
    18 👨‍🍳 语音菜谱 498 2 工具
    19 🌙 睡前故事 668 1 教育

    11.2 "讲给孩子听"的三款 App

    系列中有三款 App 可以归类为"适合与孩子一起使用":

    App交互方式核心价值
    🐶 宠物日记 记录 + 统计 培养责任感
    🦊 睡前故事 朗读 + 定制 亲子阅读时间
    👨‍🍳 语音菜谱 朗读 + 步骤 亲子烹饪时间

    这三款 App 的共同点是:App 不是被孩子独自使用的,而是家长和孩子一起使用。App 提供了内容(故事步骤/菜谱步骤),家长操作,孩子参与体验。

    11.3 替换引擎类的三款 App

    系列中有三款 App 实现了"替换"功能:

    App替换对象引擎
    😅 尴尬粉碎机 场景随机生成 随机选择 + 拼接
    🌙 梦境解析 关键词匹配 indexOf + 遍历
    🌙 睡前故事 角色/地点替换 replaceAll

    三种替换引擎使用的技术不同,但核心思想一致:将用户输入与结构化数据结合,生成个性化的输出。

    11.4 十九款 App 的关键教训

    #App最大教训
    1 白噪音 颜色对象需要 interface
    2 时间胶囊 @Builder 不能用 let
    3 冰箱剩菜 闭包不能传给 @Builder
    4 尴尬粉碎机 模式复用可大幅降错
    5 防骗训练 大段 Builder 分批重构
    6 碎片学习 ForEach key 函数作用域
    7 宠物日记 紧凑风格减少 50% 代码
    8 情绪垃圾桶 ForEach key 用值本身
    9 线下寻宝 残留代码导致级联错误
    10 订阅刺客 暗色主题设计
    11 声音明信片 setInterval 要清理
    12 家庭大富翁 展开运算符替代
    13 二手书漂流瓶 @Builder 注解不能缺
    14 废话过滤器 Text 组件不支持变量声明
    15 绿植领养 重构也可能引入错误
    16 梦境解析 内联对象不能作类型
    17 断网挑战营 已知错误也会重复犯
    18 语音菜谱 肌肉记忆比语法更难改
    19 睡前故事 删除代码要检查残留

    11.5 第 19 条教训:删除代码要检查残留

    第 19 条教训是一条关于代码清理的教训。

    在重构过程中删除了 getFilteredStoriesList() 方法,但在原位置留下了一个空的 getFilteredStories() 声明头。原因是使用了"编辑文件"工具时,替换文本没有完全匹配旧内容。

    这个错误虽然是使用 AI 编辑工具时产生的特定问题,但反映了一个通用的编程原则:删除代码后,要检查是否还有残留。常见的残留包括:

  • 空方法声明(如本次)
  • 无引用的 import
  • 孤立的注释
  • 未使用的变量
  • 养成"删除后执行一次完整构建"的习惯,可以快速发现这类残留。


    12. 结语

    12.1 十九款 App 的开发历程

    App1 🎵 白噪音 → 初识 ArkUI
    App2 ⏳ 时间胶囊 → 数据持久化
    App3 🧊 冰箱剩菜 → Tab 架构
    App4 😅 尴尬粉碎机 → 模式复用
    App5 🛡️ 防骗训练 → 适老化
    App6 💡 碎片学习 → 学习激励
    App7 🐶 宠物日记 → 紧凑风格
    App8 🗑️ 情绪垃圾桶 → 情感交互
    App9 🧭 线下寻宝 → 社交互动
    App10 🗡️ 订阅刺客 → 暗色主题
    App11 🎑 声音明信片 → 模拟录音
    App12 🎲 家庭大富翁 → 回合制游戏
    App13 📚 二手书漂流瓶 → 随机匹配
    App14 🧹 废话过滤器 → 自然语言检测
    App15 🌱 绿植领养 → 缘分匹配
    App16 🌙 梦境解析 → 潜意识探索
    App17 🏕️ 断网挑战营 → 行为养成
    App18 👨‍🍳 语音菜谱 → 适老化设计
    App19 🌙 睡前故事 → 故事定制

    12.2 替换引擎:从第三款到第十九款

    替换引擎这个概念在系列中经历了三次迭代:

    • 第三款(冰箱剩菜):写死在 Builder 中的条件判断,不可配置
    • 第十四款(废话过滤器):基于规则库的 indexOf 匹配,可配置规则
    • 第十九款(睡前故事):基于 replaceAll 的字符串替换,用户可输入替换值

    这种演进体现了从"硬编码"到"规则驱动"再到"用户驱动"的三级跃迁。

    12.3 ArkUI 的最终评价(第六次修订)

    经过十九款 App 的实践,ArkUI 的评价已经非常成熟。所有重要的"不足"都在第 14 篇博客中完整记录,后续 App 没有遇到任何新的错误类型——只有重复犯的旧错误和代码清理相关的错误。

    核心优势(与前作一致):

    • 声明式 DSL + @State 响应式
    • 编译检查有效
    • Preferences API 简洁

    核心不足(与前作一致,但没有新发现):

    • @Builder 语法约束严格
    • 展开运算符限制
    • 部分 API 差异

    12.4 给开发者的建议

  • 段落存储比整段文本更灵活:如果内容需要分步展示或逐段处理,用数组存储比用大段字符串好 10 倍。

  • 替换引擎虽然简单但有效:replaceAll + customizeChar = 孩子听到自己名字时的惊喜。有时候最简单的技术方案就是最好的方案。

  • 深色主题关注对比度:深色背景上用浅色文字是基本的,但卡片内部的文字仍然需要足够的对比度。白色卡片 + 深色文字是安全的选择。

  • 删除代码后要检查:养成"删除三查"的习惯——查 import、查方法名、查引用。

  • 12.5 感谢与展望

    十九款 App、十九篇博客、约 200,000 字。从第一款白噪音 22 个错误到第十九款睡前故事 1 个错误——这条曲线本身就是这本书最好的总结。

    现在,打开 DevEco Studio,去创造属于你自己的 App 吧。


    附录 A:第十九款 App 核心代码

    角色替换引擎

    getCustomizedParas(): string[] {
    if (this.selected === null) return [];
    return this.selected.paragraphs.map(p =>
    p.replaceAll(this.selected!.defaultChar, this.customizeChar)
    .replaceAll(this.selected!.defaultPlace, this.customizePlace)
    );
    }

    语音朗读引擎

    startReading(): void {
    this.isReading = true; this.currentPara = 0;
    let paras = this.getCurrentParagraphs();
    this.readTimer = setInterval(() => {
    if (this.currentPara < paras.length 1) this.currentPara++;
    else { this.stopReading(); promptAction.showToast({ message: '🌙 故事讲完了,晚安', duration: 2000 }); }
    }, 5000);
    }

    stopReading(): void {
    this.isReading = false; this.currentPara = 0;
    if (this.readTimer >= 0) { clearInterval(this.readTimer); this.readTimer = 1; }
    }

    收藏持久化(ID 列表)

    async saveData(): Promise<void> {
    let ids = this.stories.filter(s => s.isFavorite).map(s => s.id);
    await this.pref.put(STORAGE_KEY, JSON.stringify(ids));
    await this.pref.flush();
    }

    附录 B:系列速查

    指标数值
    App 数量 19
    博客总字数 ~200,000 字
    代码总行数 ~12,400 行
    编译错误 ~175 个
    @Builder 方法 ~260 个
    修复轮次 37 轮

    赞(0)
    未经允许不得转载:171主机测评 » 鸿蒙 Next 睡前故事定制机 App 开发实战:10 个内置故事 + 角色替换引擎 + 语音朗读 + 主题筛选 + 第十九款 App
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址