鸿蒙原生应用实战:用 ArkTS 开发《孔雀东南飞》诗词鉴赏 App

一、引言
1.1 项目背景
在中国古典文学的璀璨星河中,《孔雀东南飞》以其凄美动人的爱情故事和精湛的艺术成就,被誉为"古今第一长诗"。这篇长达三百五十七句、一千七百八十五字的叙事诗,最早收录于南朝徐陵编纂的《玉台新咏》,题名为《古诗为焦仲卿妻作》。诗歌讲述了汉末建安年间庐江府小吏焦仲卿与妻子刘兰芝在封建礼教压迫下的爱情悲剧,最终双双殉情、化鸟双飞的传奇故事。诗中「孔雀东南飞,五里一徘徊」的开篇起兴,「君当作磐石,妾当作蒲苇」的生死誓言,以及「举身赴清池」、「自挂东南枝」的悲壮结局,千百年来感动了无数读者。
作为鸿蒙生态的开发者,笔者一直在思考:如何将中华传统文化的精髓与现代移动端技术相结合?在 DevEco Studio 的开发环境下,我们能否用 ArkTS 这门声明式语言打造一款既有文化底蕴、又具备良好用户体验的诗词鉴赏应用?开发过程中会遇到哪些技术挑战?需要关注哪些性能优化点?这些问题都值得深入探讨。
本文以《孔雀东南飞》App 的完整开发过程为主线,从工程架构设计到资源管理,从组件化开发到性能优化,深入剖析 HarmonyOS 应用开发的完整技术栈。无论你是刚接触鸿蒙开发的新手,还是有一定经验的进阶开发者,都能从本文中获得有价值的实践参考。
1.2 技术选型与开发环境
本项目的技术栈如下:
| 技术 | 版本或说明 |
|---|---|
| 开发工具 | DevEco Studio 基于 IntelliJ IDEA |
| 开发语言 | ArkTS |
| UI 框架 | ArkUI 声明式 UI 框架 |
| 目标平台 | HarmonyOS Phone |
| 构建工具 | Hvigor |
| SDK 版本 | API 10 以上 Target API 24 |
| 包管理 | ohpm |
在开始之前,需要确保开发环境已正确配置。DevEco Studio 的安装包可以从华为开发者官网下载,安装后需要配置 SDK 路径和 Node.js 环境。建议使用最新稳定版本,以确保 API 兼容性。
1.3 应用效果预览
应用整体采用中国传统美学风格,以暗红色搭配金色点缀,配合宣纸米白的底色,营造沉浸式的古典阅读体验。页面采用滚动长布局,顶部展示标题和孔雀装饰,主体按章节分段展示全诗,每段以中式符号分隔,底部以「千古绝唱」收尾。整个应用在视觉上追求「古雅」二字——古色古香,雅致脱俗。
二、HarmonyOS 应用工程结构深度解析
2.1 项目目录总览
在开始编码之前,理解 HarmonyOS 应用的项目结构至关重要。一个标准的 Stage 模型应用包含以下核心目录结构:
MyApplication/
├── AppScope/ # 应用级配置目录
│ ├── app.json5 # 应用基本信息
│ └── resources/
│ └── base/
│ ├── element/ # 基础资源
│ └── media/ # 媒体资源
├── entry/ # entry 类型模块
│ ├── src/
│ │ └── main/
│ │ ├── ets/ # TypeScript 源码
│ │ │ ├── entryability/ # Ability 层
│ │ │ ├── entrybackupability/ # 备份扩展
│ │ │ └── pages/ # 页面组件
│ │ ├── module.json5 # 模块配置
│ │ └── resources/ # 模块级资源
│ ├── build-profile.json5 # 构建配置
│ ├── hvigorfile.ts # Hvigor 插件配置
│ ├── obfuscation-rules.txt # 代码混淆规则
│ └── oh-package.json5 # ohpm 依赖
├── hvigor/
│ ├── hvigor-config.json5
│ └── ...
├── oh_modules/ # 依赖模块
├── build-profile.json5 # 项目级构建
├── hvigorfile.ts # 项目级 Hvigor
├── oh-package.json5
└── local.properties # 本地 SDK 路径
这个结构体现了 HarmonyOS Stage 模型的核心设计理念:应用与模块分离。AppScope 是应用级别的配置,而 entry 是具体的功能模块。一个应用可以包含多个模块(如 entry、feature、har 等),模块之间可以共享资源但拥有独立的生命周期。
2.1.1 AppScope/app.json5 — 应用身份标识
这是应用的身份证,定义了全局唯一的标识信息:
{
"app": {
"bundleName": "com.example.myapplication",
"vendor": "example",
"versionCode": 1000000,
"versionName": "1.0.0",
"icon": "$media:layered_image",
"label": "$string:app_name"
}
}
这里有几个值得注意的技术细节:
- bundleName:全局唯一标识,通常采用反域名命名法。一旦发布到应用市场将不可更改。
- versionCode:是一个整数,用于市场检测版本更新。每次发布递增即可。
- versionName:展示给用户的版本名,采用语义化版本号。
- label:引用了
$string:app_name,这意味着应用名从资源文件中读取,支持国际化。
2.1.2 module.json5 — 模块运行时配置
每个 HAP 模块都有一个 module.json5,它定义了模块的入口、设备类型、页面路由等核心信息。我们来逐段分析:
{
"module": {
"name": "entry", // 模块名
"type": "entry", // 模块类型:entry/har/hsp
"mainElement": "EntryAbility", // 主 Ability
"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"]
}]
}]
}
}
重点解读:
type: "entry"表示该模块是应用的主入口模块。此外还有feature类型的功能模块和har类型的静态共享库。skills中注册了entity.system.home和ohos.want.action.home,这表示该 Ability 是桌面启动入口。HarmonyOS 通过这种隐式意图机制实现模块间解耦。pages指向$profile:main_pages,该文件位于resources/base/profile/main_pages.json,注册了所有页面路由。
2.1.3 main_pages.json — 页面路由注册
{
"src": ["pages/Index"]
}
所有页面都需要在此注册。如果需要添加新页面,例如 pages/Detail,只需要在数组中追加 "pages/Detail" 即可。注意这里只写路径,不需要 .ets 后缀。
2.2 资源管理系统深度解析
HarmonyOS 的资源管理是其核心优势之一,采用了限定词加资源引用的双层机制。
2.2.1 资源目录结构
resources/
├── base/ # 基础资源
│ ├── element/
│ │ ├── color.json # 颜色定义
│ │ ├── float.json # 浮点数尺寸
│ │ └── string.json # 字符串
│ ├── media/
│ │ ├── background.png # 背景图片
│ │ ├── foreground.png # 前景图标
│ │ ├── startIcon.png # 启动图标
│ │ └── layered_image.json # 自适应图标
│ └── profile/
│ ├── main_pages.json # 页面路由
│ └── backup_config.json # 备份配置
├── dark/ # 深色模式限定
│ └── element/
│ └── color.json # 深色模式颜色覆盖
└── en_US/ # 英文语言限定
└── element/
└── string.json
限定词匹配机制:当应用运行时,系统会根据当前设备的语言、屏幕密度、深色模式等限定条件,自动选择最匹配的资源文件。例如在深色模式下,系统会优先读取 dark/element/color.json 中的颜色值,覆盖 base 中的默认值。这种机制让多主题支持变得非常优雅。
2.2.2 $r() 资源引用函数
在 ArkTS 代码中,资源通过 $r() 函数引用,这是 HarmonyOS 资源管理的核心 API:
$r('app.type.name')
其中 type 可以是 color、string、float、media、boolean、integer、array 等。
实际使用示例:
// 引用颜色
.fontColor($r('app.color.poem_primary'))
// 引用字符串
Text($r('app.string.poem_subtitle'))
// 引用浮点数(文字大小必须使用 fp 单位)
.fontSize($r('app.float.title_font_size'))
// 引用图片
.backgroundImage($r('app.media.background'))
这种引用方式的好处:
- 运行时自适应:系统当前的语言、屏幕密度、深色模式等,自动加载匹配的资源
- 编译时校验:资源名编译时校验,拼写错误会直接报编译错误,避免运行时崩溃
- 类型安全:
ResourceStr、ResourceColor等类型确保正确使用 - 统一管理:所有可变的文字、颜色、尺寸集中在 JSON 文件中修改,便于维护
2.2.3 ResourceStr 类型
在 ArkTS 中,ResourceStr 是一个联合类型,定义如下:
type ResourceStr = string | Resource;
这意味着组件属性可以接受普通字符串(如 '标题')或资源引用(如 $r('app.string.title'))。在本应用的 SectionData 接口中,我们使用 ResourceStr 作为属性类型:
interface SectionData {
title: ResourceStr;
content: ResourceStr;
isPrologue?: boolean;
}
这使得组件既可以接受硬编码字符串,也可以接受资源引用,极大增强了灵活性。
三、中国传统风格主题设计
3.1 色彩体系分析
对于一款中国古典诗词应用,色彩的设计决定了用户的第一印象。中国传统色彩体系博大精深,从《周礼·考工记》中的「五色观」到明清时期的宫廷配色,都有丰富的参考价值。
本应用借鉴了中国传统美学中的红金配色体系,同时做了数字化适配:
{
"color": [
{ "name": "poem_primary", "value": "#8B0000" },
{ "name": "poem_gold", "value": "#D4A745" },
{ "name": "poem_bg", "value": "#FFF8F0" },
{ "name": "poem_text", "value": "#2C1810" },
{ "name": "poem_subtitle", "value": "#8B4513" },
{ "name": "poem_light_bg", "value": "#FFF0E0" },
{ "name": "poem_dark_red", "value": "#5C0000" }
]
}
每款颜色的设计意图:
-
poem_primary 暗红色 8B0000:中国传统正红经过暗化处理,保留了喜庆庄重的意味,但不会过于刺眼。在传统中国画中,朱砂红常用于落款印章,取其「画龙点睛」之意。这里用于主标题,配合 letterSpacing 拉开字间距后,呈现出类似匾额的视觉效果。
-
poem_gold 金色 D4A745:暖金色源于中国传统的「描金」工艺,常用于寺庙壁画和宫廷器物。在 UI 中作为点缀色,用于分隔符号 ✦ 和 ◈ 以及章节标题装饰,营造「金镶玉」的质感。透明度控制在 0.5 到 0.8 之间,避免过于张扬。
-
poem_bg 宣纸米白 FFF8F0:模拟古书的宣纸色调。宣纸的颜色不是纯白,而是略带暖黄的米白色,这种颜色比纯白更温和,能够减少长时间阅读带来的视觉疲劳。在设计中,我们用白色卡片承载正文内容,背景则使用这个宣纸色,形成层次感。
-
poem_text 深褐色 2C1810:接近传统墨色的深褐。真正的墨色并非纯黑,浓墨呈现深褐偏黑,淡墨则泛灰蓝。选用接近墨色的深褐而非纯黑,是为了模拟印刷在宣纸上的文字效果,使阅读体验更接近纸质书。
3.2 字号系统设计原则
合理的字号层级是良好阅读体验的基础。我们定义了从 14fp 到 40fp 的多级字号系统:
{
"float": [
{ "name": "title_font_size", "value": "40fp" },
{ "name": "subtitle_font_size", "value": "18fp" },
{ "name": "poem_font_size", "value": "20fp" },
{ "name": "annotation_font_size", "value": "14fp" },
{ "name": "poem_line_spacing", "value": "12vp" },
{ "name": "section_spacing", "value": "24vp" }
]
}
单位说明:
- fp:Font Pixel,字体专用单位。与设备无关,但会跟随系统字体缩放设置。对于有视力障碍的用户,将系统字体调大后,使用 fp 单位的文字也会相应放大,保证可访问性。
- vp:Virtual Pixel,虚拟像素。相当于 Android 的 dp,在不同屏幕密度下保持物理尺寸一致。
层级关系:
- 40fp 的标题与 20fp 的正文形成 2:1 的黄金比例
- 14fp 的注释文字用于序言,与正文形成 1.43:1 的差异比
- 行高设置为 32vp(基于 20fp 的约 1.6 倍行距),符合中文阅读的舒适行距
3.3 装饰符号的选择与运用
在 UI 细节上,笔者使用了 Unicode 符号作为装饰元素,避免额外引入图标资源:
| 符号 | Unicode 码点 | 用途 | 透明度 |
|---|---|---|---|
| 🦚 | U+1F99A | 页面主体装饰 | 0.8 |
| ✦ | U+2726 | 四角星装饰 | 1.0 |
| ◈ | U+25C8 | 章节标题分隔 | 1.0 |
| ❀ | U+2740 | 章节间分隔花 | 0.5 |
| ━━━ | U+2501×3 | 顶部底部横线 | 1.0 |
这些符号配合 letterSpacing 属性调整间距后,形成了具有中国古典韵味的分隔样式。例如 ✦ ✦ ✦ 通过设置 letterSpacing(8) 让三个星号之间产生均匀的间距,形成规律的装饰带。
四、ArkUI 声明式 UI 开发实战
4.1 页面架构设计
主页面 Index.ets 是整个应用的核心。我们采用 ArkUI 的 Stack 布局实现层叠效果,这种布局方式在 ArkUI 中非常常用,尤其适合需要背景叠加的场景:
Stack ← 层叠容器
├── Column (背景层) ← 纯色背景
│ .backgroundColor(poem_bg)
├── Column (装饰层) ← 宣纸纹理叠加
│ .backgroundImage(background.png)
│ .opacity(0.03)
└── Column (主内容层) ← 所有可交互内容
├── Column (标题区)
│ ├── Row (装饰线 ━━━ ✦ ━━━)
│ ├── Text (孔雀东南飞)
│ ├── Text (汉乐府·长篇叙事诗)
│ ├── peacockDecoration()
│ └── Text (◈ 分隔)
├── Scroll (滚动内容区)
│ └── Column
│ └── ForEach × 10 章节
│ ├── prologueSection (第0章)
│ └── [章节标题 + poemContentSection] (第1-9章)
└── Column (底部信息栏)
├── Row (━━━ ✦ 千古绝唱 ✦ ━━━)
└── Text (「孔雀东南飞,五里一徘徊」)
为什么选择 Stack 而非 Column 或 Flex 来实现背景叠加?
Stack 是 ArkUI 中唯一支持子元素层叠排列的容器。如果使用 Column 或 Flex,子元素会依次排列而非重叠,无法实现背景纹理半透明叠加的效果。Stack 的特性类似于 CSS 中的 position: absolute 布局,后添加的子元素会覆盖在先添加的子元素之上。
4.2 @Component 与 @Entry 装饰器详解
在 ArkTS 中,组件通过装饰器来标记其角色:
@Entry
@Component
struct Index {
@State currentSection: number = -1;
// ...
}
装饰器说明:
-
@Entry:标记该组件为页面的入口点。一个页面的顶层组件必须有 @Entry 装饰器,否则路由系统无法识别它。@Entry 组件可以通过
@State、@Prop、@Link等装饰器管理状态。 -
@Component:声明这是一个自定义组件。ArkUI 应用由组件树构成,每个组件负责渲染一部分 UI。自定义组件可以包含状态、构建函数和生命周期回调。
-
struct:ArkTS 中组件使用结构体(struct)而非 class 定义。struct 比 class 更轻量,不支持继承但支持组合,这符合声明式 UI 的组合优于继承的设计理念。
-
@State:声明式状态变量。当 @State 变量的值发生变化时,ArkUI 框架会自动重新渲染依赖于该变量的 UI 部分。这与 React 的
useState和 Vue 的ref概念类似,但 @State 是装饰器语法,属于 ArkTS 的元编程特性。
4.3 @Builder 构建函数详解
对于重复使用的 UI 片段,ArkTS 提供了 @Builder 装饰器来封装。@Builder 有两种形式:自定义构建函数和组件内构建函数。
4.3.1 组件内 @Builder
@Builder
peacockDecoration() {
Column() {
Text('🦚')
.fontSize(48)
.opacity(0.8)
Text('✦ ✦ ✦')
.fontSize(16)
.fontColor($r('app.color.poem_gold'))
.letterSpacing(8)
.margin({ top: 4 })
}
}
@Builder 的调用方式是 this.peacockDecoration(),它返回的是 UI 描述对象而非实际的视图实例。ArkUI 框架在渲染阶段会将其展开为实际的组件树。这与 React 中的 JSX 函数组件类似,但语法上更像 SwiftUI 的 @ViewBuilder。
4.3.2 @Builder 带参数
@Builder
prologueSection(content: ResourceStr) {
Column() {
Text('· 序 ·')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor($r('app.color.poem_subtitle'))
.letterSpacing(6)
.margin({ bottom: 12 })
Text(content)
.fontSize($r('app.float.annotation_font_size'))
.fontColor($r('app.color.poem_text'))
.lineHeight(22)
.textAlign(TextAlign.Center)
.opacity(0.8)
.padding({ left: 12, right: 12, top: 16, bottom: 16 })
.backgroundColor(Color.White)
.borderRadius(8)
.shadow({ radius: 4, color: 'rgba(0,0,0,0.06)', offsetY: 2 })
}
.width('100%')
.padding({ top: 8, bottom: 8 })
}
这里使用了 ResourceStr 类型的参数,可以接受 $r('app.string.xxx') 资源引用,也可以接受普通字符串。
4.4 数据驱动与 ForEach 列表渲染
诗词的十个章节通过数组驱动渲染,这是声明式 UI 的核心理念——数据驱动视图:
private poemSections: SectionData[] = [
{ title: '', content: $r('app.string.poem_opening'), isPrologue: true },
{ title: $r('app.string.section1_title'), content: $r('app.string.section1_content') },
{ title: $r('app.string.section2_title'), content: $r('app.string.section2_content') },
// ... 第 3 到第 9 章节
];
// build 方法中使用 ForEach
ForEach(
this.poemSections,
(section: SectionData, index: number) => {
// 渲染每一项
},
(item: SectionData, index?: number) => JSON.stringify(index)
)
ForEach 的三个参数:
- 数据源:一个数组,可以是任何类型
- 内容生成器:一个回调函数,接收当前项和索引,返回 UI 描述
- 键值生成器:可选参数,用于为每个列表项生成唯一标识
为什么需要键值生成器?
这是 ArkUI 性能优化的关键。当数据源变化时,框架需要知道哪些项是新增的、哪些是移除的、哪些只是顺序变化。如果没有 key,框架只能通过索引来追踪,这会导致不必要的全量重渲染。key 必须是字符串类型,且在同一列表内唯一。
4.5 条件渲染
在 ForEach 内部,我们根据 isPrologue 标志来决定渲染风格:
if (section.isPrologue) {
// 序言:带「序」标签,较小字号
this.prologueSection(section.content)
} else {
// 正文:显示章节标题
if (section.title) {
Row() {
Text('◈').fontSize(12).fontColor($r('app.color.poem_gold'))
Text(section.title)
.fontSize(20).fontWeight(FontWeight.Bold)
.fontColor($r('app.color.poem_primary')).letterSpacing(6)
Text('◈').fontSize(12).fontColor($r('app.color.poem_gold'))
}
.width('100%').justifyContent(FlexAlign.Center)
.padding({ top: 20, bottom: 16 })
}
this.poemContentSection(section.content)
}
ArkUI 中的条件渲染直接使用标准的 if/else 语法。当条件变化时,框架会自动增删对应的组件节点,这个过程称为 reconciliation(协调)。这与 React Fiber 的调度机制类似,但 ArkUI 的实现更加轻量。
4.6 Scroll 滚动容器详解
由于全诗包含序言加九个章节共十个段落,内容量大,必须使用 Scroll 容器实现滚动:
Scroll() {
Column() {
ForEach(this.poemSections, /* ... */)
}
.width('100%')
.padding({ left: 20, right: 20, bottom: 40 })
}
.layoutWeight(1)
Scroll 容器的关键规则:
- 单一子节点:Scroll 只能包含一个直接子组件,通常用 Column 或 Row 包裹多个子元素
- 溢出方向:Scroll 默认垂直滚动。如果需要水平滚动,需指定
ScrollDirection.Horizontal - 布局权重:
layoutWeight(1)让 Scroll 填充父容器剩余空间,这是 Flex 布局中的一个属性 - 嵌套注意:Scroll 不能嵌套另一个同方向的 Scroll
内边距的技巧:
left: 20, right: 20让内容两侧留白,不紧贴屏幕边缘bottom: 40底部额外留白,避免最后的内容被底部信息栏遮挡
五、资源文件深度解析与实战技巧
5.1 string.json 的设计考量
《孔雀东南飞》全诗约一千八百字,加上序言,如何合理地组织这些文本数据是一个值得思考的问题。
为什么选择 string.json 而非其他方案?
| 方案 | 优点 | 缺点 |
|---|---|---|
| string.json | 统一管理、支持 $r() 引用、国际化 | 单个字符串长度有限制 |
| 硬编码在 .ets 中 | 简单直接 | 污染代码、不支持国际化 |
| 外部 JSON 文件 | 结构化、可更换 | 需手动解析、加载延迟 |
| Data 层 Model | 类型安全 | 过度设计 |
对于本应用而言,所有文本内容在编译时就已经确定,无需运行时动态加载。因此 string.json 是最合适的选择——它集中管理所有文本,支持国际化扩展,且通过 $r() 引用开箱即用。
分章设计:
全诗按照故事情节自然划分为九个章节:
- 孔雀起兴:开篇起兴,刘兰芝自述身世与委屈
- 夫妇盟誓:焦仲卿求母,母子激烈冲突
- 忍痛离别:夫妇临别互赠信物、立下誓言
- 誓死相守:兰芝还家,与母亲相拥而泣
- 太守提亲:县令遣媒提亲,兰芝含泪拒绝
- 兄逼改嫁:兄长逼迫,兰芝被迫应允
- 鸳鸯双殉:仲卿赶来,二人诀别相约黄泉
- 孔雀双飞:兰芝投水,仲卿自挂东南枝
- 化鸟双飞:合葬华山,鸳鸯双飞鸣彻夜
每个章节的内容约一百到三百字不等,既保持了段落的完整性,又不会让单次阅读负担过重。
5.2 关于引号的坑——一次真实排错记录
在开发过程中,笔者遇到了一个典型的 JSON 解析问题:
报错信息:
Error: Config Error
Error Message: Failed to parse the JSON file: incorrect format.
At file: entry/src/main/resources/base/element/string.json
排查过程:
- 使用标准 JSON 校验工具检查文件,显示格式正确
- 逐段注释内容,缩小问题范围
- 发现在某段包含中文弯引号的文字附近开始报错
- 进一步验证是弯引号 \u201C 和 \u201D 导致的问题
技术原理解析:
HarmonyOS 的资源编译器 restool 在处理 JSON 文件时,其 Unicode 字符处理逻辑与标准 JSON 解析器存在差异。标准 JSON 规范中,字符串内的 \u201C(左弯引号)和 \u201D(右弯引号)作为普通 Unicode 字符,不应影响 JSON 解析。但 restool 在特定版本中将其识别为特殊的引号标记,导致字符串结束判定错误,从而报 “incorrect format”。
解决方案:
将所有弯引号替换为中文标准角引号 「」(U+300C/U+300D)。这不仅解决了编译问题,也更符合古典中文的排版规范——古籍中常用「」表示引述或对话。
// 修复前(含弯引号,restool 解析失败)
"value": "府吏得闻之,堂上启阿母:\n「儿已薄禄相…」"
// 修复后(使用角引号,restool 解析正常)
"value": "府吏得闻之,堂上启阿母:\n「儿已薄禄相…」"
经验教训:
在 HarmonyOS 的资源文件中,尽量避免使用非标准 Unicode 字符。如果确需使用,建议先在小范围内验证兼容性。
5.3 float.json 的单位转换与最佳实践
float.json 中的值可以是 fp 或 vp 单位,两者的选择有讲究:
{
"float": [
{ "name": "title_font_size", "value": "40fp" },
{ "name": "poem_font_size", "value": "20fp" },
{ "name": "section_spacing", "value": "24vp" }
]
}
- fp 用于字体大小:跟随系统字体缩放,确保用户自定义字体大小时应用正常自适应
- vp 用于间距、边距:在不同屏幕密度下保持视觉尺寸一致,但不跟随字体缩放
5.4 媒体资源的最佳实践
HarmonyOS 对媒体资源的处理有几个关键点:
- 图片格式:支持 PNG、JPG、SVG、WEBP 等格式。推荐使用 PNG 避免压缩失真。
- 自适应图标:使用
layered_image.json配置前后景分层图标,系统会根据设备形状自动裁剪。 - 资源引用:通过
$r('app.media.xxx')引用,编译时会校验文件是否存在。 - 启动窗口:
startWindowIcon和startWindowBackground用于配置启动时的占位界面。
六、Ability 生命周期管理深度解读
6.1 Stage 模型与 Ability
HarmonyOS 从 API 9 开始全面推行 Stage 模型,替代了早期的 FA(Feature Ability)模型。在 Stage 模型中,Ability 是应用的基本组成单元,类似于 Android 的 Activity 但定位更高——一个 Ability 可以包含多个页面。
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
this.context.getApplicationContext().setColorMode(
ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET
);
}
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(0x0000, 'testTag',
'Failed to load content. Cause: %{public}s', JSON.stringify(err));
}
});
}
}
核心 API 说明:
-
setColorMode:设置颜色模式。
COLOR_MODE_NOT_SET表示跟随系统,COLOR_MODE_LIGHT固定浅色,COLOR_MODE_DARK固定深色。 -
loadContent:加载页面。第一个参数是页面路径,对应
main_pages.json中注册的路径。路径不含.ets后缀。 -
hilog:HarmonyOS 日志系统。支持分级输出(INFO、DEBUG、ERROR),并支持隐私标签
%{public}s(公开输出)和%{private}s(脱敏输出)。
6.2 完整生命周期流程图
应用启动
│
▼
onCreate() ← Ability 对象创建
│ 适合:初始化全局变量、注册事件
▼
onWindowStageCreate() ← 窗口创建完毕
│ 适合:加载 UI 页面
▼
onForeground() ← 进入前台(可见)
│ 适合:开始动画、申请传感器
│
┌─────────────────────┐
│ 应用正常运行 │
└─────────────────────┘
│
▼
onBackground() ← 进入后台(不可见)
│ 适合:释放资源、暂停动画
▼
onWindowStageDestroy() ← 窗口即将销毁
│ 适合:保存用户数据
▼
onDestroy() ← Ability 销毁
适合:资源清理
开发者注意事项:
onCreate中不要执行耗时操作,否则会影响启动速度loadContent是异步操作,回调中处理加载结果onWindowStageCreate到onForeground之间不要执行 UI 操作,窗口可能尚未完全就绪
七、ArkUI 布局系统进阶指南
7.1 布局容器对比与选型
ArkUI 提供了多种布局容器,理解它们的特性对于设计高效 UI 至关重要:
| 容器 | 排列方向 | 子元素对齐方式 | 适用场景 |
|---|---|---|---|
| Column | 垂直(从上到下) | alignItems(水平)、justifyContent(垂直) | 列表、表单、页面主体 |
| Row | 水平(从左到右) | alignItems(垂直)、justifyContent(水平) | 工具栏、标签栏、装饰条 |
| Stack | 层叠(Z 轴) | alignContent 控制对齐 | 背景叠加、悬浮按钮 |
| RelativeContainer | 相对定位 | alignRules 锚点约束 | 复杂自适应布局 |
| Flex | 弹性布局 | flex 属性控制空间分配 | 灵活的空间分配 |
| Grid | 网格排列 | rowsTemplate、columnsTemplate | 网格视图 |
| List | 虚拟列表 | 类似 Column 但支持回收 | 长列表性能优化 |
在本应用中,我们主要使用了 Stack、Column、Row 和 Scroll 四种容器。选择依据如下:
- Stack:需要背景纹理层叠效果
- Column:页面主体是纵向排列内容
- Row:标题装饰线和章节分隔符需要水平排列
- Scroll:内容超出屏幕高度时需要滚动
7.2 链式调用 API 风格详解
ArkUI 采用链式调用 API 风格,每个组件方法都返回组件本身,允许连续调用:
Text('孔雀东南飞')
.fontSize(40)
.fontWeight(FontWeight.Bold)
.fontColor($r('app.color.poem_primary'))
.letterSpacing(12)
链式调用的内部实现:ArkUI 的每个组件实际上是 Builder 模式的变体。每个 .method() 调用修改组件内部的属性集合并返回 this,最终在渲染时统一应用所有属性。
推荐的属性链顺序:
- 内容设置(Text 的文本、Image 的 src)
- 尺寸设置(width、height、constraintSize)
- 样式设置(backgroundColor、fontSize、fontWeight)
- 边距设置(padding、margin)
- 对齐设置(alignRules、justifyContent)
- 事件绑定(onClick、onAppear)
- 手势绑定(gesture)
7.3 对齐与布局控制深入
在 ArkUI 中,子元素的对齐行为由父容器控制:
Row() {
Text('◈')
Text(section.title)
Text('◈')
}
.width('100%')
.justifyContent(FlexAlign.Center)
justifyContent vs alignItems:
-
justifyContent:控制主轴方向的对齐
- Column 的主轴是垂直方向 →
Center表示垂直居中 - Row 的主轴是水平方向 →
Center表示水平居中 - 可选值:Start、Center、End、SpaceBetween、SpaceAround、SpaceEvenly
- Column 的主轴是垂直方向 →
-
alignItems:控制交叉轴方向的对齐
- Column 的交叉轴是水平方向 →
Center表示水平居中 - Row 的交叉轴是垂直方向 →
Center表示垂直居中 - 可选值:Start、Center、End、Stretch
- Column 的交叉轴是水平方向 →
7.4 阴影与圆角效果
为了让诗文卡片呈现纸质感,我们使用了 shadow 和 borderRadius 属性:
Text(content)
.padding({ left: 8, right: 8, top: 12, bottom: 12 })
.backgroundColor(Color.White)
.borderRadius(8)
.shadow({
radius: 4,
color: 'rgba(0,0,0,0.06)',
offsetY: 2
})
shadow 参数的物理含义:
radius:阴影模糊半径,值越大阴影越模糊、扩散范围越大color:阴影颜色,带透明度叠加更自然offsetX/offsetY:阴影偏移量,模拟光源方向
八、性能优化与最佳实践
8.1 资源编译与打包优化
HarmonyOS 的资源系统在编译时进行多层优化:
- 资源索引化:所有资源编译时生成二进制索引文件,运行时通过 ID 查找,速度远快于字符串匹配
- 按需打包:未使用的资源不会被打包到 HAP 中
- 图片压缩:PNG 图片在打包时会进行无损压缩
- 资源去重:相同内容的资源自动合并
8.2 渲染性能优化
在诗词页面中,由于包含大量文本,渲染性能是需要关注的重点:
- 减少组件层级:每个嵌套层级都会增加布局计算时间。我们的页面 Stack 嵌套深度控制在 4 层以内。
- 使用正确的 Key:ForEach 的 key 帮助框架在数据变化时精确定位需要更新的节点。
- 避免不必要重渲染:@State 变量只在变化时触发更新,不参与 UI 的数据用普通属性存储。
- 轻量装饰元素:装饰符号使用 Text 组件而非 Image,一个 Text 组件的渲染开销远小于 Image。
8.3 内存管理
- 资源自动管理:通过
$r()引用的资源由框架自动管理和释放 - 组件销毁:当组件被移除时,框架自动清理所有绑定的事件和状态
- @State 变量的生命周期:@State 变量随组件创建而创建,随组件销毁而释放
8.4 启动速度优化
// EntryAbility.ets
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index', (err) => {
// 页面的 onCreate 中只做必要的初始化
});
}
loadContent 是异步操作,不会阻塞主线程。为了进一步优化启动速度,可以在 onCreate 阶段预加载数据,在 onWindowStageCreate 中仅负责页面展示。
九、构建与部署实战
9.1 Hvigor 构建流程详解
HarmonyOS 的构建系统基于 Hvigor,构建过程分为三个阶段:
阶段一:配置阶段
- 读取 app.json5、module.json5、build-profile.json5
- 解析依赖关系,下载 oh_modules
- 初始化任务队列
阶段二:任务执行阶段
PreBuild → CreateModuleInfo → MergeProfile → CreateBuildProfile
→ PreCheckSyscap → GeneratePkgContextInfo → ProcessProfile
→ ProcessRouterMap → ProcessShareConfig → ProcessStartupConfig
→ PreviewProcessResource → PreviewCompileResource (restool)
→ CompileETS → PackageHap → SignHap
阶段三:输出阶段
生成 HAP 包(Huawei Application Package),这是 HarmonyOS 的可执行文件格式。
9.2 常见构建错误及解决方案
| 错误码 | 错误类型 | 典型原因 | 解决方案 |
|---|---|---|---|
| 11203002 | Config Error | JSON 格式错误 | 检查 JSON 引号、逗号、括号 |
| 11200101 | Resource Error | 资源引用未找到 | 检查 $r() 资源名是否定义 |
| 11200203 | Module Error | 模块配置错误 | 检查 module.json5 结构 |
| 11200301 | Build Error | 签名配置缺失 | 配置调试或发布证书 |
9.3 签名与调试
真机运行需要正确的签名配置。DevEco Studio 提供了自动签名方案:
- 点击 File → Project Structure → Signing
- 勾选 Automatically generate signature
- 工具会自动创建并管理证书文件
十、总结与展望
10.1 技术要点回顾
通过《孔雀东南飞》App 的开发实践,我们系统地掌握了 HarmonyOS 应用开发的核心技术:
- 工程结构:Stage 模型下的 AppScope、entry 模块、资源配置三层的项目组织方式
- 声明式 UI:ArkUI 的 @Component、@Builder、@State 装饰器体系,以及链式调用的 API 风格
- 布局系统:Stack、Column、Row、Scroll 等布局容器的特性和选型原则
- 资源管理:$r() 资源引用机制的完整工作流,以及限定词匹配的自适应原理
- 主题设计:中国传统色彩体系在移动端的数字化适配
- 构建工程:Hvigor 构建流程和常见问题的排查方法
10.2 可扩展方向
当前应用实现了基础的诗词展示功能,未来可以在以下方向扩展:
- 交互增强:章节快捷导航、书签收藏、字体缩放
- 多媒体融合:名家朗诵音频、古琴配乐、诗词吟唱
- 知识延伸:诗中人物关系图谱、历史背景介绍、地理信息标注
- 社区互动:用户评论、读后感分享、诗词接龙
- 多语言国际化:基于现有 string.json 结构轻松扩展英文版、日文版
- 动画特效:使用 ArkUI 动画 API 添加翻页转场、孔雀飞翔粒子效果
- 无障碍:添加屏幕阅读支持、对比度调节、大字版模式
10.3 对鸿蒙生态的思考与展望
从此次开发体验来看,HarmonyOS 的 ArkTS 加 ArkUI 技术栈整体设计向现代化看齐,声明式 UI 的开发体验与 SwiftUI、Jetpack Compose 类似,学习曲线相对平缓。但仍有几个值得改进的方向:
-
工具链成熟度:DevEco Studio 的预览器偶尔出现版本兼容问题,真机调试需要稳定的网络连接。构建缓存偶尔也需要手动清理,这是开发效率的瓶颈之一。
-
文档完备性:部分 API 的文档示例不够丰富,例如
@Builder参数传递的约束条件、ForEach的 key 生成规则等,开发者需要结合官方 Demo 源码和社区讨论才能完全理解正确用法。 -
社区生态的壮大:目前 HarmonyOS 的第三方组件库数量有限,大量通用 UI 场景需要从零实现。相比之下,Android 有 Material Design 组件库,iOS 有 UIKit 和 SwiftUI 丰富的内置组件。期待 HarmonyOS 社区贡献更多开源组件,降低开发者的重复劳动。
-
restool 工具的 Unicode 兼容性:本文遇到的 JSON 引号问题就是一个典型案例,说明资源编译器在非标准 Unicode 字符的兼容性处理上还有优化空间。笔者已将问题反馈给官方,期待后续版本修复。
-
调试与排查工具:UI 布局的调试目前主要依赖 log 输出,缺少像 Android Studio 的 Layout Inspector 那样的可视化层级查看工具。对于复杂布局问题,排查效率有待提升。
-
跨端开发的复杂度:「一次开发,多端部署」的理念很好,但实践中不同设备类型(手机、平板、手表、智慧屏)的 UI 适配仍然需要大量条件判断和配置工作,这部分自动化程度还有提升空间。
尽管如此,HarmonyOS 作为面向万物互联时代的操作系统,其微内核架构、分布式软总线和统一生态理念具有长远的战略价值。从版本迭代速度来看,华为对开发者体验的重视程度在持续提升——API 的稳定性和向后兼容性越来越好,开发工具的完善度越来越高。
10.4 最后的话
开发《孔雀东南飞》这样一款传统文化题材的应用,让笔者深刻体会到技术与人文学科的结合之美。当我们用 ArkTS 的声明式语法描绘「孔雀东南飞,五里一徘徊」的诗意画面时,代码不再只是代码,而是一种承载文化的媒介。技术与传统文化的碰撞,往往能激发出意想不到的创意火花和设计灵感。
随着鸿蒙生态的逐步完善,相信会有越来越多有创意、有温度的文化类应用在这片新天地中绽放光彩。如果你正在阅读本文并打算在 HarmonyOS 上开发你的下一个项目,无论它是一个工具类应用、社交平台还是文化产品,希望这篇文章能为你提供一份有价值的参考和启发。让我们一起,用代码点亮传统文化的传承之路,让古老的诗词在新时代的屏幕中焕发出崭新的生命力。
附录
A. 完整代码索引
| 文件路径 | 行数 | 说明 |
|---|---|---|
| entry/src/main/ets/pages/Index.ets | 288 | 主页面组件 |
更多推荐



所有评论(0)