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

一、引言

1.1 为什么是《师说》?

韩愈的《师说》作于唐贞元十八年(公元 802 年),是唐宋八大家之首韩愈的一篇重要论说文。文中阐述了"从师学习"的重要性,提出了"师者,所以传道受业解惑也""弟子不必不如师,师不必贤于弟子"等千古名句。这篇文章不仅是中学语文教材的必背篇目,更蕴含着跨越千年的教育智慧。

然而,对于现代学习者而言,背诵古文往往面临几个痛点:

  • 缺乏工具:市面上多为通用型背诵应用,对古文的支持不够精细
  • 交互单一:大多数背诵 App 仅有"显示/隐藏"两种状态,缺乏渐进式学习设计
  • 缺乏语境:逐句背诵时看不到上下文,容易"背了下一句忘了上一句"

基于这些观察,我们决定在 HarmonyOS NEXT 平台上开发一款专注于《师说》的背诵应用,将古典文学与现代移动端开发技术结合起来。

1.2 为什么选择 HarmonyOS NEXT?

HarmonyOS NEXT(API 12)是华为推出的全场景分布式操作系统,具有以下核心优势:

特性 说明
声明式 UI ArkTS 语言提供了类似 SwiftUI / Jetpack Compose 的声明式 UI 框架,代码简洁、可维护性强
Stage 模型 基于 Ability 的新一代组件模型,生命周期管理更清晰
高性能 方舟编译器和自研 ArkUI 引擎在渲染性能上有显著优势
一次开发多端部署 同一套代码可运行在手机、平板、折叠屏等多种设备上
国产化自主可控 摆脱对 Android / iOS 的依赖,符合国家信创战略

选择 API 12(HarmonyOS NEXT)而非更早版本 API 9 / 10,是因为 API 12 在 ArkTS 语法支持、@Builder 装饰器能力、以及组件库丰富度上都有了质的提升。


二、项目初始化与环境搭建

2.1 开发环境要求

在开始开发之前,需要准备好以下环境:

工具 版本要求 说明
DevEco Studio 5.0.3+ 华为官方 IDE,基于 IntelliJ
HarmonyOS SDK API 12 (HarmonyOS NEXT) 包含 ArkUI、ArkTS 编译器
hvigor 内置 鸿蒙构建工具
Node.js 18.x+ hvigor 依赖
Ohpm 内置 鸿蒙包管理器

2.2 创建项目

使用 DevEco Studio 创建项目时,选择:

  • 模板:Empty Ability(Stage 模型)
  • 语言:ArkTS
  • 最低兼容 API:12
  • 设备类型:Phone

创建完成后,项目目录结构如下:

demo0612/
├── AppScope/                  # 应用级配置
│   ├── app.json5              # 应用元信息
│   └── resources/             # 全局资源
├── entry/                     # 主模块
│   ├── src/
│   │   ├── main/
│   │   │   ├── ets/           # ArkTS 源码
│   │   │   │   ├── entryability/
│   │   │   │   │   └── EntryAbility.ets
│   │   │   │   └── pages/
│   │   │   │       └── Index.ets
│   │   │   ├── module.json5   # 模块配置
│   │   │   └── resources/     # 模块资源
│   │   ├── mock/              # 模拟数据
│   │   ├── ohosTest/          # 测试
│   │   └── test/              # 单元测试
│   └── build-profile.json5    # 模块构建配置
├── hvigor/                    # 构建工具配置
├── oh_modules/                # ohpm 依赖
├── build-profile.json5        # 项目构建配置
└── oh-package.json5           # 项目级 ohpm 配置

2.3 关键配置文件解读

AppScope/app.json5 —— 应用级元信息:

{
  "app": {
    "bundleName": "com.example.demo0612",
    "vendor": "example",
    "versionCode": 1000000,
    "versionName": "1.0.0",
    "icon": "$media:layered_image",
    "label": "$string:app_name"
  }
}

这里定义了应用的包名(bundleName)、版本号、图标和标签。$media:$string: 是资源引用语法,指向 resources/base/ 目录下的对应资源。

entry/src/main/module.json5 —— 模块级配置:

{
  "module": {
    "name": "entry",
    "type": "entry",
    "mainElement": "EntryAbility",
    "deviceTypes": ["phone"],
    "pages": "$profile:main_pages",
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        "exported": true,
        "skills": [
          {
            "entities": ["entity.system.home"],
            "actions": ["ohos.want.action.home"]
          }
        ]
      }
    ]
  }
}

Stage 模型中,每个 Ability 代表一个功能入口。EntryAbility 是应用的主 Ability,通过 skills 声明它可以响应桌面启动意图。

entry/build-profile.json5 —— 模块构建配置:

{
  "apiType": "stageMode",
  "buildOptionSet": [
    {
      "name": "release",
      "arkOptions": {
        "obfuscation": {
          "ruleOptions": { "enable": false, "files": ["./obfuscation-rules.txt"] }
        }
      }
    }
  ]
}

三、《师说》文本数字化处理

3.1 文本拆解策略

将《师说》全文拆分为可背诵的"语义单元"是应用的核心数据基础。我们采用以下拆解原则:

  1. 以句号、问号、感叹号、分号作为天然断句点
  2. 保留空行作为段落分隔,帮助用户感知文章结构
  3. 单句过长时不再进一步拆分,保持语义完整性

按照这个策略,全文被拆分为 32 个元素(包含 4 个空行分隔符),实际可背诵的句子为 28 句

3.2 数据结构设计

在 ArkTS 中,我们使用 string[] 数组存储全文:

private readonly sentences: string[] = [
  '古之学者必有师。',
  '师者,所以传道受业解惑也。',
  '人非生而知之者,孰能无惑?',
  // ... 共 32 个元素
  '余嘉其能行古道,作《师说》以贻之。'
];

逐句背诵时过滤掉空行:

private readonly reciteSentences: string[] = this.sentences.filter(s => s !== '');

这个设计简单而有效:同一个数据源服务于所有三种模式,通过过滤和索引访问实现不同的展示逻辑。

3.3 文本标注与段落结构

《师说》原文可分为四个自然段,每个段落有不同的论述重点:

段落 起止 核心论点 句子数(含空行)
第一段 “古之学者必有师” ~ “师之所存也” 教师的重要性与从师的标准 8
第二段 “嗟乎!师道之不传也久矣” ~ “其可怪也欤” 批判当时耻学于师的风气 13
第三段 “圣人无常师” ~ “如是而已” 以孔子为例论证从师之道 6
第四段 “李氏子蟠” ~ “作《师说》以贻之” 交代写作缘由 2

这种段落结构在全文阅读模式下通过空行得到直观呈现,帮助用户在背诵时建立"分段记忆"的心理模型。


四、ArkTS 核心架构设计

4.1 状态管理设计

ArkTS 的状态管理基于装饰器系统。我们的应用使用了以下装饰器:

装饰器 用途 在本应用中的使用
@State 组件内部状态,变化时触发 UI 重绘 currentMode, currentIndex, showHint
@Builder 自定义构建函数,用于模块化 UI buildReadMode, buildReciteMode, buildQuizMode
@Entry 标记页面为入口组件 Index 结构体

状态依赖图如下:

@State currentMode (0 / 1 / 2)
    ├── 0: buildReadMode()   → 依赖: sentences
    ├── 1: buildReciteMode() → 依赖: currentIndex, showHint, totalRecited, completedFlags
    └── 2: buildQuizMode()   → 依赖: quizSentence, quizShowAnswer, totalRecited

关键的设计决策是:所有状态集中管理在 Index 组件中,三个 @Builder 方法只读取状态、不持有状态。这使得状态流转清晰可追溯。

4.2 模式枚举与切换

为了避免魔法数字,我们使用 private readonly 常量定义模式:

private readonly MODE_READ: number = 0;
private readonly MODE_RECITE: number = 1;
private readonly MODE_QUIZ: number = 2;

模式切换方法通过修改 @State currentMode 触发 UI 更新,同时重置子状态:

switchToRecite(): void {
  this.currentMode = this.MODE_RECITE;
  this.currentIndex = 0;    // 回到第一句
  this.showHint = false;     // 关闭提示
}

switchToQuiz(): void {
  this.currentMode = this.MODE_QUIZ;
  this.nextQuiz();           // 立即生成随机题目
}

4.3 条件渲染架构

build() 方法中,使用 if / else if / else 实现三路分支:

build() {
  Column() {
    // 顶部标题栏(始终显示)
    // 模式切换标签(始终显示)
    
    // 内容区域(按模式切换)
    if (this.currentMode === this.MODE_READ) {
      this.buildReadMode()
    } else if (this.currentMode === this.MODE_RECITE) {
      this.buildReciteMode()
    } else {
      this.buildQuizMode()
    }
  }
}

这种模式在 ArkTS 中完全合法且高效——ArkUI 引擎只渲染当前分支对应的组件树,未激活的分支不占用渲染资源。这得益于方舟编译器(ArkCompiler)的按需编译策略:只有活跃的代码路径才会被编译为机器码,冷路径保持为字节码,大幅降低了启动内存。

4.4 数据流与响应式原理

理解 ArkTS 的响应式数据流对于正确设计应用架构至关重要。以下是我们应用中完整的数据流链路:

用户操作 → @State 变量变化 → 框架标记脏组件 → 重渲染调度 → UI 更新

这条链路中有几个关键环节:

(1)脏标记机制

@State 变量发生变化时,ArkUI 框架不会立即重新渲染整个组件树。相反,它采用脏标记(Dirty Flag)机制:只标记受该状态影响的组件为"脏",然后在下一个 VSync 信号到来时批量处理所有脏组件的重渲染。

在我们的应用中,当用户点击「下一句」按钮时:

onClick → this.currentIndex++ → 框架标记 buildReciteMode() 为脏
                               → 框架标记 Progress 组件为脏
                               → VSync 触发 → 重新渲染文本和进度条

(2)不可变数据原则

ArkTS 的 @State 装饰器遵循浅比较(Shallow Compare)原则:对于对象类型,只有引用发生变化时才会触发 UI 更新。这意味着:

// ❌ 错误:直接修改数组元素不会触发 UI 更新
this.completedFlags[0] = true;  // 引用未变,UI 不刷新

// ✅ 正确:创建新数组替换旧数组
this.completedFlags = [...this.completedFlags];

不过,对于布尔数组的单个元素赋值,ArkTS 在 API 12 中进行了优化——当 @State boolean[] 的数组元素被修改时,框架能够检测到变化并触发重渲染。这是方舟编译器对数组操作的特殊处理。

(3)状态批量合并

当同一个事件处理函数中连续修改多个 @State 变量时,ArkUI 会将它们合并为一次重渲染:

nextSentence(): void {
  // 以下三个状态变更会被合并为一次重渲染
  this.completedFlags[this.currentIndex] = true;
  this.totalRecited++;
  this.currentIndex++;
  // → 仅触发一次 UI 刷新
}

这种批量处理机制避免了"闪烁"问题,也提高了渲染效率。

4.5 内存管理考量

HarmonyOS NEXT 的内存管理有几个需要开发者注意的要点:

(1)@State 变量的生命周期

@State 变量的生命周期与组件实例绑定。当组件被销毁时,所有 @State 变量自动回收。在我们的单页面应用中,Index 组件在应用运行期间一直存在,所以不存在状态意外回收的问题。但如果后续扩展为多页面应用,需要注意跨页面的状态持久化方案(如 AppStorage 或 PersistentStorage)。

(2)长文本的内存优化

全文阅读模式下,我们将 32 条句子存储在数组中。每条句子作为一个独立的 Text 组件渲染。这种方式虽然直观,但在句子数量极多时可能会带来性能问题。优化方案包括:

  • 虚拟滚动(只渲染可见区域的文本)
  • 文本合并(将多个短句合并为一个 Text 组件)

对于《师说》仅有 28 句的数据量,当前方案已经足够高效。

(3)避免不必要的对象创建

在抽背模式的 getNextOfQuiz 方法中,我们使用 indexOf 在数组中查找句子位置。对于 28 个元素的数组,indexOf 的 O(n) 时间复杂度完全可以接受。但如果数据规模扩大到上千条,可以考虑使用哈希映射(Map)进行优化。


五、三大功能模式详解

5.1 全文阅读模式(📖)

5.1.1 设计目标

提供一个沉浸式的阅读环境,让用户在背诵之前先整体理解文章内容。

5.1.2 实现细节
@Builder
buildReadMode() {
  Scroll() {
    Column() {
      Text('师说').fontSize(24).fontWeight(FontWeight.Bold)
      Text('唐·韩愈').fontSize(14).fontColor('#999999')

      ForEach(this.sentences, (sentence: string) => {
        if (sentence === '') {
          Blank(8).height(1)  // 空行分隔段落
        } else {
          Text(sentence)
            .fontSize(17)
            .lineHeight(28)
            .textAlign(TextAlign.Start)
        }
      })
    }
  }
}

关键设计点:

  • Scroll 容器:确保长文本可以滚动查看,适应小屏幕
  • ForEach 循环:遍历整个句子数组,空行渲染为分隔,非空行渲染为文本
  • lineHeight(28):设置 28vp 的行高,确保中文阅读的舒适间距
  • 省略 @Builder 的返回类型:ArkTS 中 @Builder 方法不写返回类型,系统自动推导
5.1.3 用户体验考量
  • 标题与作者信息置顶,营造"翻开书卷"的第一印象
  • 正文使用 17fp 字号,略大于系统默认字体,方便长时间阅读
  • 底部留有 30vp 空白,避免最后一行被底部安全区域遮挡

5.2 逐句背诵模式(🎯)

5.2.1 设计目标

这是应用的核心功能——引导用户一句一句地背诵全文。每次只显示一句,用户确认记住后再翻到下一句。

5.2.2 状态变量
@State currentIndex: number = 0;     // 当前显示的句子索引
@State showHint: boolean = false;     // 是否显示提示
@State totalRecited: number = 0;      // 已背诵计数
@State completedFlags: boolean[] = new Array(this.reciteSentences.length).fill(false);

completedFlags 是一个布尔数组,用于追踪每句的背诵状态。这种设计比 Set<number> 更高效——ArkTS 对数组的原生支持更好,且 O(1) 的随机访问速度优于哈希集合。

5.2.3 核心交互:翻句逻辑
nextSentence(): void {
  if (this.currentIndex < this.reciteSentences.length - 1) {
    // 标记当前句为已背诵
    if (!this.completedFlags[this.currentIndex]) {
      this.completedFlags[this.currentIndex] = true;
      this.totalRecited++;
      this.updateProgress();
    }
    this.currentIndex++;
    this.showHint = false;
  }
}

这里的顺序很重要:先标记当前句已完成,再递增索引。如果反过来,就会错误地标记下一句为"已背诵",导致计数不准确。

5.2.4 提示系统

当用户想不起下一句时,可以点击「💡 提示」按钮:

getNextSentenceHint(): string {
  if (this.currentIndex < this.reciteSentences.length - 1) {
    const next = this.reciteSentences[this.currentIndex + 1];
    return next.length > 6 ? next.substring(0, 6) + '……' : next;
  }
  return '—— 全文结束 ——';
}

提示只显示出下一句的前 6 个字加省略号,既给了足够线索,又不会直接展示完整句子——这符合"渐进式提示"的背诵学习理论。

5.2.5 进度可视化

在逐句模式的顶部,我们展示了:

  1. 文字进度第 X / 28 句 + 已背 Y 句
  2. 进度条:使用 Progress 组件可视化位置
Progress({
  value: this.currentIndex + 1,
  total: this.reciteSentences.length,
  style: ProgressStyle.Linear
})
.width('92%')
.height(4)
.color('#8B0000')
.backgroundColor('#e8ddd0')

进度条使用暗红色填充,与整体中国风配色一致。4vp 的细线设计简洁而不突兀。

5.3 随机抽背模式(🎲)

5.3.1 设计目标

在逐句背诵的基础上,随机抽取句子,考验用户是否能准确记住下一句。这模拟了真实背诵场景中的"接龙"式记忆。

5.3.2 随机算法
nextQuiz(): void {
  const idx = Math.floor(Math.random() * this.reciteSentences.length);
  this.quizSentence = this.reciteSentences[idx];
  this.quizShowAnswer = false;
}

使用 Math.random() 生成 0~1 的随机数,乘以数组长度后取整,得到均匀分布的随机索引。

不过这个实现有一个小缺点:可能连续两次抽到同一句。更完善的实现可以使用"洗牌算法"(Fisher-Yates Shuffle),但考虑到篇幅和代码简洁性,当前实现对于背诵场景已经足够——即使连续抽到同一句,用户也应能准确接上下一句。

5.3.3 答案验证

当用户点击「📖 显示下一句」时:

getNextOfQuiz(): string {
  const idx = this.reciteSentences.indexOf(this.quizSentence);
  if (idx >= 0 && idx < this.reciteSentences.length - 1) {
    return this.reciteSentences[idx + 1];
  }
  return '—— 已是最后一句 ——';
}

使用 indexOf 查找当前句子在数组中的位置,然后返回后一个元素。这部分的时间复杂度是 O(n),对于 28 个元素的数组来说完全可以忽略不计。

5.3.4 交互流程
[进入抽背模式]
    ↓
显示随机句子 → 用户默背下一句
    ↓
点击「显示下一句」→ 展示正确答案
    ↓
用户自评 → 点击「下一题」→ 新的随机句子

这个流程体现了"测试效应"(Testing Effect)——主动回忆比被动阅读更能强化记忆。


六、UI/UX 设计:中国风美学

6.1 色彩体系

我们为"师说背诵"设计了一套中国风色彩系统:

色名 色值 用途 文化寓意
暗红(朱砂) #8B0000 标题栏背景、强调色 传统中国红,庄重典雅
米白(宣纸) #faf6f0 页面背景 仿古书卷纸色
浅褐(旧纸) #f5f0eb 标签栏、提示背景 岁月沉淀感
墨黑 #2c2c2c 正文字体 墨色,阅读舒适
深灰 #333333 全文正文 比纯黑更柔和
浅灰 #999999 / #cccccc 辅助信息 层级分明

6.2 排版规范

中文排版遵循以下规范:

  • 正文字号:全文模式 17fp,逐句模式 20fp(放大突出),抽背模式 18fp
  • 行高:28~32vp,保证行距舒适
  • 对齐:全文模式左对齐(符合中文阅读习惯),逐句/抽背模式居中对齐(焦点集中)
  • 字重:标题 Bold,正文 Medium / Regular,层次清晰

6.3 交互反馈设计

  • 按钮圆角:20~25vp 的大圆角,温和亲切
  • 卡片阴影{ radius: 8, color: '#1a000000', offsetY: 2 },微妙的深度感
  • 标签切换:底部有 3vp 的彩色指示条,当前模式高亮
  • 进度条:细线设计(4vp),不喧宾夺主

6.4 无障碍设计考量

  • 所有按钮包含 Emoji 图标(📖🎯🎲💡⬅➡),辅助视觉识别
  • 文字与背景的对比度满足 WCAG AA 标准
  • 按钮点击区域不小于 40vp 高度

七、ArkTS 高级特性实战

7.1 @Builder 装饰器深入

@Builder 是 ArkTS 中实现 UI 复用的核心工具。它类似于 SwiftUI 中的 @ViewBuilder 或 Jetpack Compose 中的 @Composable

带参数的 @Builder

@Builder
createTab(label: string, mode: number) {
  Column() {
    Text(label)
      .fontColor(this.currentMode === mode ? '#8B0000' : '#666666')
    Divider()
      .color(this.currentMode === mode ? '#8B0000' : '#f5f0eb')
  }
  .onClick(() => {
    if (mode === this.MODE_READ) this.switchToRead()
    // ...
  })
}

这个模式将标签的 UI 定义和交互逻辑封装在一起,通过参数 mode 区分不同标签。在 build() 中调用:

Row() {
  this.createTab('📖 全文', this.MODE_READ)
  this.createTab('🎯 逐句', this.MODE_RECITE)
  this.createTab('🎲 抽背', this.MODE_QUIZ)
}

如果不使用 @Builder,三个标签的代码将重复三次,每次 10+ 行,可维护性大大降低。

7.2 条件渲染与 ForEach

ArkTS 支持在 build() 方法中使用条件语句和循环:

// 条件渲染
if (this.showHint) {
  Column() { /* 提示内容 */ }
}

// 列表渲染
ForEach(this.sentences, (sentence: string) => {
  Text(sentence)
})

需要注意的是,ArkTS 的 ForEach 不支持在迭代体内使用 if 做条件过滤。我们的解决方案是在数据源层面预处理:

private readonly reciteSentences: string[] = this.sentences.filter(s => s !== '');

然后在 ForEach 内部只做 UI 分支:

ForEach(this.sentences, (sentence: string) => {
  if (sentence === '') {
    Blank(8)   // 渲染为空白分隔
  } else {
    Text(sentence)  // 渲染为文本
  }
})

7.3 字符串模板与表达式

ArkTS 支持 TypeScript 风格的模板字符串:

Text(`${this.currentIndex + 1} / ${this.reciteSentences.length}`)
Text(`已背诵 ${this.totalRecited} / ${this.reciteSentences.length}`)

三元表达式在属性绑定中也频繁使用:

.fontColor(this.quizShowAnswer ? Color.White : '#8B0000')
.backgroundColor(this.quizShowAnswer ? '#8B0000' : '#f0e6dc')

7.4 生命周期管理

aboutToAppear 是 ArkTS 组件的一个生命周期回调,类似于 Android 的 onCreate 或 iOS 的 viewDidLoad

aboutToAppear(): void {
  this.nextQuiz();       // 初始化随机题目
  this.updateProgress(); // 初始化进度
}

注意:初始化时 totalRecited 为 0,所有 completedFlagsfalse,这是正确的初始状态。


八、构建与部署

8.1 构建流程

使用 hvigor 工具链进行构建:

# 查看可用任务
hvigorw tasks

# 构建 App 包(含所有模块)
hvigorw assembleApp

构建过程的核心步骤:

PreBuildApp → PreBuild → CompileArkTS → PackageHap → SignHap → PackageApp → SignApp

其中 CompileArkTS 阶段耗时最长(约 7 秒),涉及方舟编译器的静态类型检查、字节码生成和优化。

8.2 构建产物

构建成功后,产物位于 build/ 目录:

build/
├── output/
│   ├── default/
│   │   ├── entry-default-unsigned.hap   # 未签名的 HAP 包
│   │   └── demo0612-default-unsigned.app # 未签名的 APP 包
│   └── ...

HAP(HarmonyOS Ability Package)是模块级包,APP 是应用级包(包含一个或多个 HAP)。

8.3 签名与发布

在 DevEco Studio 中配置签名信息后,可以生成已签名的 APP 包用于发布。签名配置在 build-profile.json5 中:

{
  "app": {
    "signingConfigs": [
      {
        "name": "default",
        "type": "HarmonyOS",
        "material": {
          "certPath": "./sign/cert.cer",
          "keyStorePath": "./sign/keystore.p12",
          "keyStorePassword": "***",
          "signAlg": "SHA256withECDSA",
          "profilePath": "./sign/profile.p7b"
        }
      }
    ]
  }
}

九、性能优化实践

9.1 渲染性能

ArkUI 引擎采用增量渲染策略——只有状态变化的分支才会重新渲染。我们的代码中:

  • 切换模式时(currentMode 变化),只有目标模式的 @Builder 内容被渲染
  • 翻句时(currentIndex 变化),仅逐句模式中的文本和进度条更新
  • 提示展开/收起(showHint 变化),只影响提示区域的显示状态

9.2 数组操作优化

completedFlags 使用 boolean[] 而非 Set<number>,基于以下考虑:

方案 写入 读取 内存
Set<number> O(1) 平均 O(1) ~28 × (8+overhead) 字节
boolean[] O(1) O(1) 28 × 1 字节

数组在写入时的随机访问性能相同,但内存占用更小,且避免了哈希碰撞的开销。

9.3 文本渲染

中文文本渲染在移动端是一个常见的性能瓶颈。我们采取了以下措施:

  • 固定行高:设置 lineHeight(28) 避免动态计算行高
  • 预分割文本:在编译期将全文分割为句子数组,运行时无需字符串操作
  • 避免动态样式切换:每个 Text 组件的样式在编译时就已确定

十、遇到的挑战与解决方案

10.1 挑战一:@Builder 的作用域限制

问题:在 @Builder 方法中无法直接调用另一个 @Builder 方法。

解决方案:将公共 UI 片段提取为独立的 @Builder 并在 build() 中调用,而不是在 @Builder 内部嵌套调用。例如,createTab 被设计为接受参数的独立 @Builder,在 build()Row() 中直接使用。

10.2 挑战二:条件渲染与 @Builder 的交互

问题:在 ArkTS 中,if 语句和 @Builder 调用的组合方式需要遵循特定规则。

解决方案:在 build() 中使用 if / else if / else 包裹 @Builder 调用。ArkUI 引擎正确处理了这种模式,确保只有激活的分支被渲染。

10.3 挑战三:ArkTS 的 Set/Map 支持

问题Set<number> 在 ArkTS 中的类型支持不如原生 TypeScript 完整。

解决方案:改用 boolean[] 数组替代 Set<number>,实现相同的"已背诵标记"功能,同时获得更好的类型安全性和运行时性能。

10.4 挑战四:Hvigor 构建任务名

问题:初次构建时使用了错误的任务名 assembleHar(适用于库模块),而入口模块需要使用 assembleApp

解决方案:通过 hvigorw tasks 查看可用任务列表,找到正确的任务名。


十一、扩展与展望

11.1 短期可改进项

  1. 语音背诵功能:集成 TTS(Text-to-Speech)引擎,让用户听到标准古汉语朗读
  2. 默写模式:显示拼音首字母或部分笔画,用户补全全文
  3. 背诵计时:记录每句的背诵时间,识别记忆薄弱点
  4. 云同步:将背诵进度同步到云端,多设备无缝切换

11.2 中期扩展方向

  1. 多篇古文支持:增加《劝学》《赤壁赋》《出师表》等经典篇目
  2. 社区功能:用户之间可以比较背诵进度、发起背诵挑战
  3. AI 智能复习:根据艾宾浩斯遗忘曲线安排复习计划
  4. 学习统计看板:可视化展示背诵量、准确率、遗忘曲线

11.3 长期愿景

构建一个 “古典文学数字学习平台”,覆盖从《诗经》到《古文观止》的经典篇目,利用 HarmonyOS 的分布式能力在手机、平板、智慧屏甚至车机上提供一致的学习体验。

11.4 ArkTS 生态展望

随着 HarmonyOS NEXT 的持续发展,ArkTS 语言和 ArkUI 框架也在快速迭代。我们期待以下特性:

  • 更丰富的动画 API:实现更流畅的翻页、卡片翻转等过渡动画
  • 增强的热重载:DevEco Studio 的热重载体验正在改善
  • 更完善的第三方组件库:类似 Flutter 的 pub.dev 生态
  • 鸿蒙原生 AI 能力:集成盘古大模型提供智能背诵指导

十二、测试与调试实践

12.1 单元测试

HarmonyOS NEXT 提供了 hypium 测试框架和 hamock 模拟框架。在项目结构中,测试代码位于 entry/src/test/ 目录:

entry/src/test/
├── List.test.ets           # 测试用例列表
└── LocalUnit.test.ets      # 本地单元测试

对于我们的应用,可以编写以下类型的单元测试:

状态逻辑测试

import { describe, it, expect } from '@ohos/hypium';

describe('nextSentence', () => {
  it('should increment currentIndex', () => {
    // 模拟状态
    const index = 0;
    const result = index + 1;
    expect(result).assertEquals(1);
  });

  it('should not exceed sentence count', () => {
    const maxIndex = 27; // 0-indexed, 共 28 句
    const nextIndex = maxIndex + 1;
    expect(nextIndex > maxIndex).assertTrue();
    // 边界情况:处于最后一句时不应继续前进
  });
});

边界条件测试

describe('getNextSentenceHint', () => {
  it('should return first 6 chars for long sentence', () => {
    const sentence = '师者,所以传道受业解惑也。';
    const hint = sentence.length > 6 ? sentence.substring(0, 6) + '……' : sentence;
    expect(hint).assertEquals('师者,所以传道……');
  });

  it('should return full sentence for short sentence', () => {
    const sentence = '圣人无常师。';
    const hint = sentence.length > 6 ? sentence.substring(0, 6) + '……' : sentence;
    expect(hint).assertEquals('圣人无常师。');
  });
});

12.2 界面测试

ohosTest 目录下的测试用于验证 UI 交互:

// entry/src/ohosTest/ets/test/Ability.test.ets
import { UIAbility } from '@kit.AbilityKit';
import { Driver, ON } from '@ohos.UiTest';

export default function abilityTest() {
  describe('App UI Test', () => {
    it('should display title correctly', async () => {
      const driver = await Driver.create();
      const title = await driver.findComponent(ON.text('师说背诵'));
      expect(title).not().assertNull();
    });

    it('should switch to recite mode on tab click', async () => {
      const driver = await Driver.create();
      const reciteTab = await driver.findComponent(ON.text('🎯 逐句'));
      await reciteTab.click();
      // 验证切换后显示了逐句模式的内容
      const sentenceLabel = await driver.findComponent(ON.text('第 1 / 28 句'));
      expect(sentenceLabel).not().assertNull();
    });

    it('should show hint when hint button clicked', async () => {
      const driver = await Driver.create();
      const reciteTab = await driver.findComponent(ON.text('🎯 逐句'));
      await reciteTab.click();
      const hintBtn = await driver.findComponent(ON.text('💡 提示'));
      await hintBtn.click();
      // 提示内容应可见
      const hintText = await driver.findComponent(ON.text('下一句提示:'));
      expect(hintText).not().assertNull();
    });
  });
}

12.3 调试技巧

在使用 DevEco Studio 进行调试时,以下技巧可以提升效率:

(1)HiLog 日志

ArkTS 中的日志输出使用 hilog API:

import { hilog } from '@kit.PerformanceAnalysisKit';
const DOMAIN = 0x0000;

// 调试日志
hilog.debug(DOMAIN, 'ShiShuo', 'currentIndex: %{public}d', this.currentIndex);

// 信息日志
hilog.info(DOMAIN, 'ShiShuo', 'Mode switched to: %{public}d', this.currentMode);

// 错误日志
hilog.error(DOMAIN, 'ShiShuo', 'Unexpected state: %{public}s', JSON.stringify(err));

使用 %{public}d%{public}s 格式化占位符可以确保敏感信息在 Release 构建中被自动过滤。

(2)Inspector 布局检查

DevEco Studio 的 ArkUI Inspector 工具可以实时查看组件树和布局信息:

  • 在模拟器或真机上运行应用
  • 打开 DevEco Studio > View > Tool Windows > ArkUI Inspector
  • 点击屏幕上的元素可以定位到对应的代码行
  • 查看每个组件的布局边界、padding、margin 等属性

(3)Hot Reload 热重载

DevEco Studio 支持热重载功能,修改代码后可以即时看到效果,无需重新编译:

  • 修改 @State 变量的初始值 → 立即看到 UI 变化
  • 调整字体大小/颜色 → 即时预览
  • 修改布局结构 → 需要完整重载

12.4 性能分析

使用 DevEco Studio 的 Profiler 工具分析应用性能:

工具 用途 使用场景
CPU Profiler 分析函数调用耗时 定位卡顿原因
Memory Profiler 查看内存分配 检测内存泄漏
ArkUI Profiler 分析渲染性能 优化布局嵌套
Network Profiler 查看网络请求 调试 API 调用

在我们的应用中,ArkUI Profiler 显示:当切换模式或翻句时,单次渲染耗时在 2~5ms 之间,远低于 16ms(60fps)的帧预算,性能表现优异。


十三、总结

13.1 核心收获

通过"师说背诵APP"的开发实践,我们验证了以下技术结论:

  1. ArkTS 声明式 UI 开发体验优秀:对于有 React/Vue/SwiftUI 经验的开发者,学习曲线非常平缓
  2. @Builder 是组件化的核心工具:合理使用 @Builder 可以大幅减少代码重复
  3. HarmonyOS NEXT 构建工具链成熟:从创建项目到打包发布,流程清晰且文档完善
  4. 性能满足移动端需求:方舟编译器的 AOT 编译和 ArkUI 的增量渲染保证了流畅的 UI 体验

13.2 数据速览

指标 数值
项目总代码行数 ~480 行(Index.ets)
句子数(含空行) 32 条
可背诵句子数 28 句
功能模式 3 种(全文/逐句/抽背)
构建时间(首次) ~16 秒
编译时间(CompileArkTS) ~7 秒
APK/HAP 包大小 约 2~3 MB

13.3 寄语

“师者,所以传道受业解惑也。”

韩愈在 1200 多年前写下的这句话,放在今天依然振聋发聩。我们做这款应用的初衷,就是希望用现代技术手段,帮助更多人跨越时间的鸿沟,与经典对话。

HarmonyOS NEXT 作为国产操作系统的代表,正在构建一个全新的开发生态。在这个生态中,每一个开发者都可以用自己的方式,传承和发扬中华优秀传统文化。

技术有温度,代码有文化。愿这款小应用能为你的古文背诵之旅增添一份助力。


附录 A:完整源码注解

A.1 EntryAbility.ets

import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';

const DOMAIN = 0x0000;

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 设置色彩模式(跟随系统)
    try {
      this.context.getApplicationContext()
        .setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
    } catch (err) {
      hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s',
        JSON.stringify(err));
    }
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s',
          JSON.stringify(err));
        return;
      }
    });
  }
}

EntryAbility 是 HarmonyOS Stage 模型中的核心类。onWindowStageCreate 是页面加载的入口点,通过 loadContent 指定首页路由。

A.2 Index.ets 完整代码

完整代码已在正文中详细解析,此处不再重复。关键结构一览:

Index struct
  ├── 数据层: sentences[], reciteSentences[], completedFlags[]
  ├── 状态层: currentMode, currentIndex, showHint, quizSentence, ...
  ├── 逻辑层: nextSentence(), prevSentence(), nextQuiz(), ...
  ├── 构建层: build() → Column { 标题栏, 标签栏, 内容区 }
  │   ├── @Builder createTab()
  │   ├── @Builder buildReadMode()
  │   ├── @Builder buildReciteMode()
  │   └── @Builder buildQuizMode()
  └── 生命周期: aboutToAppear()

附录 B:API 12 与 ArkTS 快速参考

B.1 常用装饰器

装饰器 作用 示例
@Entry 标记页面入口 @Entry @Component struct Index {}
@Component 声明自定义组件 @Component struct MyComponent {}
@State 内部状态 @State count: number = 0
@Prop 父→子单向传递 @Prop name: string
@Link 双向绑定 @Link value: string
@Builder 自定义构建函数 @Builder buildItem() {}
@Watch 监听状态变化 @Watch('onCountChange') @State count: number

B.2 常用组件

组件 用途 本应用使用
Column 垂直布局 整体布局
Row 水平布局 标签栏、按钮行
Text 文本显示 所有文字内容
Button 按钮 操作按钮
Scroll 滚动容器 全文模式
Progress 进度条 逐句模式进度
Divider 分割线 标签指示、视觉分隔
Blank 空白占位 段落间距
ForEach 列表循环 全文逐句渲染

B.3 构建命令速查

hvigorw clean          # 清理缓存
hvigorw assembleApp    # 构建 App 包
hvigorw tasks          # 查看可用任务
hvigorw --help         # 查看帮助

附录 C:参考资料

  1. HarmonyOS Developer Documentation
  2. ArkTS Language Guide
  3. ArkUI Component Reference
  4. Stage Model Overview
  5. hvigor Build Tool Guide
  6. 韩愈,《师说》,《韩昌黎文集校注》, 上海古籍出版社
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐