从零构建鸿蒙古诗文 App:HarmonyOS NEXT 应用开发实战
从零构建鸿蒙古诗文 App:HarmonyOS NEXT 应用开发实战
一、引言
2024年,HarmonyOS NEXT 的正式发布标志着中国自主操作系统生态进入全新阶段。作为一名移动端开发者,我怀着对传统文化的热爱和对新技术的探索欲,决定用 HarmonyOS NEXT 的 ArkTS 语言开发一款古诗文赏析应用。本文将从项目搭建、架构设计、数据模型、页面实现、状态管理到编译打包,完整记录开发全过程,分享在鸿蒙原生应用开发中的实践心得。
古诗文是中华文化的瑰宝,从《诗经》的淳朴到唐诗的恢弘,从宋词的婉约到元曲的率真,每一篇都蕴含着深厚的人文精神。通过这款 App,用户可以浏览分类、阅读全文、查看译文注释、收藏喜欢的作品,并能搜索相关诗文。全文共 19 篇精选作品,涵盖唐诗、宋词、元曲、古文、诗经五大类别。
二、项目概述与技术栈
2.1 项目概况
| 项目 | 说明 |
|---|---|
| 应用名称 | 古诗文 |
| 开发工具 | DevEco Studio 5.0+ |
| 目标系统 | HarmonyOS NEXT (API 12+, SDK 6.1.1) |
| 开发语言 | ArkTS |
| 编程模型 | Stage 模型 |
| 包名 | com.example.myapplication |
2.2 技术栈选型
ArkTS — 鸿蒙原生声明式 UI 框架,基于 TypeScript 语法扩展,提供 @Component、@Entry、@State 等装饰器驱动的响应式编程范式。与 SwiftUI 和 Jetpack Compose 类似,但更贴近 Web 开发者的 TypeScript 使用习惯。
Stage 模型 — HarmonyOS NEXT 推荐的应用开发模型,以 Ability 为基本单元,每个 Ability 有独立的上下文(Context),支持多实例和模块化部署。
AppStorage — 鸿蒙提供的全局应用状态管理方案,数据在应用进程内全局共享,支持跨页面状态同步。类似于 Android 的 SharedPreferences,但类型安全且支持响应式更新。
Router — 页面路由 API,支持 pushUrl(带参数跳转)和 back(返回上一页),是页面级导航的核心工具。
2.3 为什么选择原生开发而非跨平台
对于古诗文 App 这种以文本展示为主的轻量级应用,跨平台方案(如 React Native、Flutter)当然也能实现。但选择 HarmonyOS 原生开发有以下优势:
- 原生性能 — 直接使用系统 API,无桥接开销,长列表滚动更流畅
- 系统集成 — 可直接使用 HarmonyOS 的系统资源(颜色、字体、图标等)
- 生态适配 — 更好地适配鸿蒙设备的折叠屏、平板等形态
- 开发效率 — DevEco Studio 提供可视化预览、热重载等高效开发工具
三、项目架构设计
3.1 整体架构
MyApplication/
├── AppScope/ # 应用级配置
│ ├── app.json5 # 应用元信息(包名、版本号等)
│ └── resources/base/element/string.json # 应用名字符串
├── entry/ # 主模块
│ └── src/main/
│ ├── ets/
│ │ ├── entryability/ # Ability 入口
│ │ ├── entrybackupability/ # 备份扩展能力
│ │ ├── model/ # 数据层
│ │ │ ├── PoemModel.ets # 数据模型定义
│ │ │ └── PoemData.ets # 数据源 + 收藏管理
│ │ └── pages/ # 视图层
│ │ ├── Index.ets # 首页
│ │ ├── DetailPage.ets # 详情页
│ │ ├── FavoritesPage.ets # 收藏页
│ │ └── SearchPage.ets # 搜索页
│ ├── module.json5 # 模块配置
│ └── resources/ # 资源文件
└── build-profile.json5 # 构建配置
整个应用遵循分层架构设计:
- 数据层(model):负责数据定义、数据源提供、状态管理
- 视图层(pages):负责 UI 呈现和用户交互
- 路由层(Router):负责页面间导航和数据传递
3.2 数据流设计
用户点击 → 页面组件(@Component) → 状态更新(@State) → UI 自动刷新
↓
调用 model 层方法 → AppStorage 持久化
↓
Router 跳转传递参数
↓
目标页面读取参数加载数据
ArkTS 的响应式机制使数据流非常清晰:@State 装饰的变量发生变化时,框架自动重新渲染关联的 UI 组件。开发者只需关注数据变化逻辑,无需手动操作 DOM。
四、数据模型设计
4.1 分类枚举与接口定义
数据模型是整个应用的基石。我首先定义了一个 PoemCategory 枚举来表示古诗文的分类:
// PoemModel.ets
export enum PoemCategory {
TANG_SHI = '唐诗',
SONG_CI = '宋词',
YUAN_QU = '元曲',
GU_WEN = '古文',
SHI_JING = '诗经',
CHU_CI = '楚辞'
}
选择枚举而非字符串常量,好处是类型安全——在编译阶段就能发现拼写错误,IDE 也能提供智能补全。
PoemItem 接口定义了每篇诗文的完整数据结构:
export interface PoemItem {
id: number; // 唯一标识
title: string; // 标题
author: string; // 作者
dynasty: string; // 朝代
category: PoemCategory; // 分类
content: string; // 正文
translation?: string; // 译文(可选)
notes?: string; // 注释(可选)
appreciation?: string; // 赏析(可选)
background?: string; // 创作背景(可选)
}
4.2 设计思考
在接口设计中,我将 translation、notes、appreciation、background 等字段设为可选(?),因为并非每篇作品都有完整的赏析或背景资料。这样可以灵活处理数据缺失的情况,前端代码中只需做简单的 if 判断即可。
id 字段使用 number 类型,既可作为数据查找的键,又可作为路由参数传递。相比字符串类型的 ID,数字在序列化和传输中更高效。
4.3 数据源设计
数据源 PoemData.ets 包含了 19 篇精选古诗文,覆盖 5 个主要类别。每篇作品包含完整的正文、译文、注释和赏析。这里以《静夜思》为例展示数据格式:
{
id: 1,
title: '静夜思',
author: '李白',
dynasty: '唐',
category: PoemCategory.TANG_SHI,
content: '床前明月光,\n疑是地上霜。\n举头望明月,\n低头思故乡。',
translation: '明亮的月光洒在床前的窗户纸上...',
notes: '①静夜思:静静的夜里产生的思绪。\n②疑:好像。',
appreciation: '这首诗写的是在寂静的月夜思念家乡的感受...',
background: '李白的《静夜思》创作于唐玄宗开元十四年...'
}
选择硬编码内置数据而非网络请求的原因:
- 开箱即用 — 用户安装后无需联网即可使用
- 启动速度快 — 无网络等待时间
- 内容可控 — 确保数据质量和版权合规
- 架构简单 — 无需搭建后端服务
如果需要扩展到更大规模的内容,可以方便地切换到本地数据库(HarmonyOS 提供了 RelationalStore)或网络 API 方案。
五、收藏系统实现
收藏功能是 App 的核心交互之一。我选择 AppStorage 来实现持久化存储。
5.1 AppStorage 原理
AppStorage 是 HarmonyOS 提供的全局键值存储系统:
- 数据在应用进程内全局共享,所有页面都可访问
- 支持基本类型和
Array、Map等复杂类型 - 数据保存在内存中,应用退出后丢失(配合
PersistentStorage可实现磁盘持久化) - 可以通过
@StorageLink装饰器实现双向绑定
5.2 收藏管理实现
const FAVORITES_KEY = 'poem_favorites';
// 获取收藏集合
export function getFavorites(): Set<number> {
const stored = AppStorage.get<number[]>(FAVORITES_KEY);
return stored ? new Set(stored) : new Set();
}
// 切换收藏状态,返回新的状态
export function toggleFavorite(id: number): boolean {
const favs = getFavorites();
let isNowFav: boolean;
if (favs.has(id)) {
favs.delete(id);
isNowFav = false;
} else {
favs.add(id);
isNowFav = true;
}
AppStorage.set<number[]>(FAVORITES_KEY, Array.from(favs));
return isNowFav;
}
// 检查是否已收藏
export function isFavorite(id: number): boolean {
return getFavorites().has(id);
}
5.3 设计要点
1. Set 与 Array 的转换
AppStorage 不直接支持 Set 类型,所以我将收藏的 ID 集合以 number[](数组)形式存储,读取时再转换为 Set 以利用其 O(1) 的查找性能。写回时使用 Array.from() 将 Set 转回数组。
2. toggle 模式的设计
toggleFavorite 函数实现了"切换"语义——如果已收藏则取消收藏,未收藏则添加收藏。返回布尔值让调用方可以立即知道当前状态,无需再次查询。这种设计在函数式编程中称为"命令查询分离"的变体,一个函数同时完成命令(修改状态)和查询(返回结果)两个职责。
3. 函数式工具方法
将 getFavorites、toggleFavorite、isFavorite 提取为独立的工具函数,而非封装成类或单例,目的是与 ArkTS 的响应式系统更好的配合。在页面组件中可以直接调用这些函数,并将结果赋值给 @State 变量,触发 UI 更新。
六、首页实现详解
首页是用户打开 App 后看到的第一个界面,承载着分类导航和诗文列表两大核心功能。
6.1 页面结构
┌────────────────────────────┐
│ 古诗文 🔍 │ ← 标题栏 + 搜索入口
├────────────────────────────┤
│ [推荐] [唐诗] [宋词] ... │ ← 分类标签(水平滚动)
├────────────────────────────┤
│ ┌──────────────────────┐ │
│ │ 静夜思 →│ │
│ │ 唐·李白 [唐诗]│ │
│ │ 床前明月光... │ │
│ └──────────────────────┘ │
│ ┌──────────────────────┐ │
│ │ 水调歌头 →│ │
│ │ 宋·苏轼 [宋词]│ │
│ │ 明月几时有... │ │
│ └──────────────────────┘ │
│ ... │
├────────────────────────────┤
│ 🏠 首页 ☆ 收藏 │ ← 底部导航
└────────────────────────────┘
6.2 分类标签实现
分类标签使用 List 组件横向排列,实现可水平滚动的标签栏:
List({ space: 10 }) {
ForEach(this.categories, (cat: string) => {
ListItem() {
Text(cat)
.fontSize(15)
.fontColor(this.currentCategory === cat ? '#FFFFFF' : '#6B5B4F')
.padding({ left: 18, right: 18, top: 7, bottom: 7 })
.backgroundColor(this.currentCategory === cat ? '#8B4513' : '#F5F0EB')
.borderRadius(18)
.onClick(() => {
this.filterByCategory(cat);
})
}
})
}
.listDirection(Axis.Horizontal)
关键点:
listDirection(Axis.Horizontal)将列表方向设为水平- 当前选中标签使用棕色背景+白色文字,未选中使用浅米色背景+深灰文字,形成清晰的视觉层级
- 圆角
borderRadius(18)使标签呈现药丸形状,符合当前设计趋势
6.3 分类筛选逻辑
filterByCategory(category: string): void {
this.currentCategory = category;
if (category === '推荐') {
// 从各类别中精选 9 篇代表作
this.poemList = POEM_LIST.filter(
p => [1, 2, 3, 9, 12, 13, 14, 16, 18].includes(p.id)
);
} else if (category === '唐诗') {
this.poemList = POEM_LIST.filter(
p => p.category === PoemCategory.TANG_SHI
);
}
// ... 其他分类类似
}
"推荐"分类通过硬编码 ID 列表选择 9 篇代表作,涵盖各个类别的大师名篇,给首次使用的用户一个绝佳的入门体验。
6.4 诗文卡片设计
每个诗文条目使用白色卡片展示,包含标题、朝代作者、分类标签和首句预览:
Column() {
// 第一行:标题 + 箭头
Row() {
Text(item.title).fontSize(18).fontWeight(FontWeight.Medium)
Blank()
Text('→').fontSize(16).fontColor('#B0A090')
}
// 第二行:朝代·作者 + 分类标签
Row() {
Text(`${item.dynasty} · ${item.author}`).fontSize(13)
Blank()
Text(item.category) // 带背景色的分类标签
}
// 首句预览
Text(item.content.split('\n')[0])
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.shadow({ radius: 4, color: '#1A000000', offsetX: 0, offsetY: 2 })
设计要点:
- 卡片阴影:使用
shadowAPI 添加微妙的投影,创造层次感 - 圆角处理:
borderRadius(12)使卡片边缘柔和 - 文本溢出:首句预览使用
maxLines(1)+textOverflow(Ellipsis)确保单行显示,溢出时自动省略号 - 点击交互:整个卡片可点击,通过
router.pushUrl跳转到详情页
6.5 关于图标选择的教训
在最初的实现中,我使用了系统资源 $r('sys.media.ohos_ic_public_*') 来获取系统图标。编译时报错 Unknown resource name,原因是 SDK 6.1.1 中这些资源名称已变更或不可用。解决方法是改用 Unicode 符号:
| 功能 | 原来 | 修复后 |
|---|---|---|
| 返回 | $r('sys.media.ohos_ic_public_back') |
← (U+2190) |
| 搜索 | $r('sys.media.ohos_ic_public_search') |
🔍 |
| 收藏 | $r('sys.media.ohos_ic_public_star') |
☆/★ |
| 取消 | $r('sys.media.ohos_ic_public_cancel') |
✕ |
使用 Unicode 符号的好处是零依赖、跨平台兼容、无需管理图标资源文件。缺点是不同操作系统和设备的渲染效果略有差异。对于正式项目,建议使用 SVG 矢量图标或 HarmonyOS 的 SymbolGlyph 组件。
七、详情页实现
详情页是用户阅读古诗文的核心页面,需要展示正文并支持多维度内容(译文、注释、赏析、背景)的展开和收起。
7.1 页面结构
┌────────────────────────────┐
│ ← 静夜思 ☆ │ ← 导航栏 + 收藏按钮
├────────────────────────────┤
│ │
│ 静夜思 │ ← 标题
│ 唐·李白 │ ← 作者信息
│ [唐诗] │ ← 分类标签
│ ─────────────────────── │
│ 床前明月光, │
│ 疑是地上霜。 │ ← 正文
│ 举头望明月, │
│ 低头思故乡。 │
│ │
│ [译文] [注释] [赏析] [背景]│ ← 功能按钮
│ │
│ 📖 译文 │
│ ┌────────────────────┐ │
│ │ 明亮的月光洒在... │ │ ← 内容卡片
│ └────────────────────┘ │
│ │
│ 💡 赏析 │
│ ┌────────────────────┐ │
│ │ 这首诗写的是... │ │ ← 内容卡片(可展开多个)
│ └────────────────────┘ │
└────────────────────────────┘
7.2 状态管理
详情页使用 4 个 @State 变量控制各面板的展开状态:
@State showTranslation: boolean = true; // 译文默认展开
@State showNotes: boolean = false;
@State showAppreciation: boolean = false;
@State showBackground: boolean = false;
其中 showTranslation 默认 true,因为译文是用户最常查看的内容,默认展示可以减少一次点击操作。
7.3 关于 Builder 的踩坑
在最初的实现中,我使用了 @Builder 来定义可复用的按钮组件:
// ❌ 错误写法:Builder 上链式调用 onClick
this.buildToggleBtn('译文', this.showTranslation)
.onClick(() => { this.showTranslation = !this.showTranslation; })
编译时报错 Property 'onClick' does not exist on type 'void'。这是因为 ArkTS 的 @Builder 函数返回 void,并非 UI 组件实例,因此不能链式调用属性方法。
解决方案有两种:
方案一(我采用的):将交互逻辑内联到 Builder 内部
// ✅ 直接使用 Text 组件绑定点击事件
Text('译文')
.fontSize(14)
.fontColor(this.showTranslation ? '#FFFFFF' : '#8B4513')
.backgroundColor(this.showTranslation ? '#8B4513' : '#F5EDE4')
.padding({ left: 14, right: 14, top: 6, bottom: 6 })
.borderRadius(16)
.onClick(() => {
this.showTranslation = !this.showTranslation;
})
方案二:将点击事件作为 Builder 参数传入
@Builder
buildToggleBtn(label: string, isActive: boolean, onClick: () => void) {
Text(label)
.onClick(() => { onClick(); })
}
Builder 的正确使用方式是所有 UI 配置和交互逻辑都在 Builder 内部完成,外部调用时不应再链式修改。
7.4 条件渲染处理
详情页需要处理 this.poem 为 null 的情况。在 ArkTS 中,build() 方法内部不能使用 return 提前返回,必须使用 if-else 包裹:
build() {
Column() {
// ... 导航栏始终渲染 ...
if (this.poem) {
// 正文内容
} else {
Text('未找到诗文')
}
}
}
这是 ArkTS 与普通 TypeScript/JavaScript 的重要区别——build() 方法必须始终返回一个完整的组件树,不能提前中断。
7.5 功能按钮区
四个切换按钮(译文、注释、赏析、背景)使用 Row 水平排列,通过 @State 控制颜色和文字变化模拟"选中/未选中"状态:
Text('译文')
.fontColor(this.showTranslation ? '#FFFFFF' : '#8B4513')
.backgroundColor(this.showTranslation ? '#8B4513' : '#F5EDE4')
这种实现方式本质上是一个"开关"按钮,比使用 Toggle 组件更轻量、更易于自定义样式。
八、收藏页实现
收藏页展示用户收藏的所有诗文,数据来源于 AppStorage 持久化的收藏 ID 集合。
8.1 数据加载时机
aboutToAppear(): void {
this.loadFavorites();
}
loadFavorites(): void {
const favIds = getFavorites();
this.favList = POEM_LIST.filter(p => favIds.has(p.id));
}
aboutToAppear 是组件生命周期方法,在页面即将显示时调用,确保每次进入收藏页都能获取最新数据。
8.2 空状态设计
当用户还没有收藏时,显示一个友好的空状态提示:
☆
还没有收藏
在诗文详情页点击☆即可收藏
空状态包含一个视觉元素(大号星号图标)和两行提示文字,引导用户操作。相比直接显示空白页面,空状态设计能显著降低用户的困惑感。
8.3 收藏列表的交互
收藏列表的卡片样式与首页一致,点击跳转到详情页。这种一致性设计使用户在不同页面间切换时能保持熟悉的操作模式。
九、搜索页实现
搜索功能支持按标题、作者、内容、朝代、译文多维度匹配。
9.1 实时搜索
Search({ placeholder: '搜索诗文标题、作者、内容…', value: this.searchText })
.onChange((val: string) => {
this.searchText = val;
if (val.trim() === '') {
this.searchResults = [];
this.hasSearched = false;
} else {
this.onSearch();
}
})
使用 onChange 事件实现实时搜索——用户每输入一个字符,结果列表都立即更新。对于 19 篇数据的轻量级搜索,客户端实时过滤的延迟在毫秒级,体验流畅。
9.2 搜索算法
onSearch(): void {
const query = this.searchText.trim().toLowerCase();
this.searchResults = POEM_LIST.filter(item => {
return item.title.toLowerCase().includes(query)
|| item.author.toLowerCase().includes(query)
|| item.content.toLowerCase().includes(query)
|| item.dynasty.toLowerCase().includes(query)
|| (item.translation && item.translation.toLowerCase().includes(query));
});
}
算法要点:
- 大小写归一化:将查询和内容都转为小写,确保不区分大小写
- 多字段匹配:在标题、作者、内容、朝代、译文五个字段中搜索,提高召回率
- 子串匹配:使用
includes而非精确匹配,支持关键词模糊搜索
这种实现方式简单直接,对于小数据集完全够用。如果需要扩展到成百上千篇诗文,建议使用倒排索引或引入 @kit.ArkData 的全文搜索能力。
9.3 搜索结果呈现
搜索结果页面包含结果数量统计和与首页风格一致的卡片列表:
找到 3 篇相关诗文
┌──────────────────────┐
│ 静夜思 [唐诗]│
│ 唐·李白 │
│ 床前明月光... │
└──────────────────────┘
十、路由与页面导航
10.1 路由配置
页面路由在 main_pages.json 中注册:
{
"src": [
"pages/Index",
"pages/DetailPage",
"pages/FavoritesPage",
"pages/SearchPage"
]
}
所有页面必须在 main_pages.json 中注册,否则运行时会无法找到对应页面。
10.2 带参数跳转
// 跳转到详情页并传递诗文 ID
router.pushUrl({
url: 'pages/DetailPage',
params: { id: item.id }
});
// 详情页接收参数
aboutToAppear(): void {
const params = router.getParams() as Record<string, Object>;
const id = params!['id'] as number;
}
10.3 关于 router 废弃警告
编译时出现了 'pushUrl' has been deprecated 等警告。在 HarmonyOS NEXT 中,@kit.ArkUI 的 router API 已被标记为废弃,推荐使用更新的导航 API。
虽然这些警告不影响编译运行,但在正式项目中应该迁移到新 API:
// 未来推荐方式(伪代码)
import { UIAbilityContext } from '@kit.AbilityKit';
context.router.pushNamedRoute({ name: 'DetailPage', params: { id: 1 } });
不过截至 SDK 6.1.1,旧的 router API 仍然完全可用,且是社区中最常用的方案,短期内不会移除。
十一、样式与主题设计
11.1 色彩体系
App 的视觉风格定位为"暖色调书香风格",色彩设计围绕棕色系展开:
| 用途 | 色值 | 说明 |
|---|---|---|
| 页面背景 | #F8F6F3 |
暖米色,纸书质感 |
| 标题文字 | #3A2A1A |
深棕,沉稳大气 |
| 正文文字 | #3A2A1A |
与标题一致 |
| 辅助文字 | #9A8A7A |
灰棕,用于作者朝代 |
| 强调色 | #8B4513 |
棕色,用于标签活跃态 |
| 卡片背景 | #FFFFFF |
白色,保持内容清晰 |
| 标签背景 | #F5F0EB / #F5EDE4 |
浅米色 |
11.2 字体与排版
- 标题:26-24fp,粗体
- 正文:18fp,2px 字间距,34px 行高,居中对齐
- 辅助信息:13-15fp
- 分类标签:11-12fp,小字标签
古诗文的正文采用居中对齐 + 较大行距的排版方式,模拟传统书法的竖排效果。字间距 letterSpacing(2) 增加了文字的呼吸感,阅读体验更舒适。
11.3 圆角与阴影
- 卡片圆角:12px
- 标签圆角:16-18px(药丸形)
- 卡片阴影:
radius: 4, color: '#1A000000', offsetY: 2 - 底部导航阴影:
radius: 8, color: '#1A000000', offsetY: -2
阴影参数中 color 使用带透明度的十六进制值(#1A000000 表示 α=10% 的黑色),相比 rgba 更简洁,是十六进制颜色的扩展用法。
十二、遇到的问题与解决方案
12.1 编译错误汇总
在开发过程中共遇到 18 个编译错误,按类型分类如下:
| 错误类型 | 数量 | 原因 | 解决方案 |
|---|---|---|---|
Argument of type 'string' is not assignable to parameter of type 'Resource' |
2 | 字符串传递给期望 Resource 类型的参数 | 改用 $r() 或直接使用字符串字面量 |
Property 'onClick' does not exist on type 'void' |
6 | Builder 函数不能链式调用 | 将 onClick 移到 Builder 内部 |
Property 'SemiBold' does not exist on type 'typeof FontWeight' |
1 | FontWeight.SemiBold 在 API 12 中不存在 |
改用 FontWeight.Bold |
Only UI component syntax can be written here |
1 | build() 中使用 return |
改为 if-else 条件渲染 |
Unknown resource name 'ohos_ic_public_*' |
8 | 系统资源名称在 SDK 6.1.1 中变更 | 改用 Unicode 符号 |
12.2 Builder 函数的正确用法
这是本次开发中最大的"坑"。在 ArkTS 中,@Builder 装饰的函数有以下特点:
- 返回类型为
void,不是组件实例 - 不能在 Builder 调用的结果上链式调用属性方法
- Builder 内部可以访问外层组件的
@State变量 - Builder 主要用于提取可复用的 UI 片段
正确示例:
@Builder
buildSection(title: string, content: string) {
Column() {
Text(title).fontColor('#5A4A3A')
Text(content).fontColor('#4A3A2A')
}
.backgroundColor('#FFFFFF')
.borderRadius(12)
}
错误示例:
// ❌ 不能这样用
this.buildSection('标题', '内容')
.backgroundColor('#FFFFFF') // 编译错误
12.3 系统资源的兼容性
HarmonyOS 每个版本的系统资源名称可能不同。SDK 6.1.1(对应 API 12+)中,ohos_ic_public_back、ohos_ic_public_search 等常用的系统图标资源名称已不可用。推荐的做法是:
- 使用
SymbolGlyph组件(如果 SDK 版本支持) - 使用 Unicode 符号作为图标(适合轻量需求)
- 自行准备 SVG/PNG 图标资源放入
resources/base/media/ - 通过
$r('app.media.my_icon')引用自定义资源
十三、编译与打包
13.1 构建流程
使用 hvigorw assembleApp 命令完成应用构建:
PreBuildApp → CreateModuleInfo → ProcessProfile →
ProcessResource → CompileArkTS → GeneratePkgModuleJson →
PackageHap → SignHap → PackageApp → assembleApp
编译流程中最重要的阶段是 CompileArkTS,负责将 ArkTS 源码编译为方舟字节码。
13.2 构建输出
成功构建后的产物包括:
entry/build/default/outputs/default/entry-default-unsigned.hap— 未签名的 HAP 包build/default/outputs/default/MyApplication-default-unsigned.app— 未签名的 APP 包
13.3 签名说明
构建输出中出现了 Will skip sign 的警告,这是因为没有配置签名证书。在 DevEco Studio 中,可以通过以下步骤配置自动签名:
- 打开项目 → Build → Generate Key and CSR
- 配置密钥库和证书
- 在
build-profile.json5中配置signingConfigs
对于开发和测试,也可以使用 DevEco Studio 自动生成的调试证书。
十四、项目扩展方向
目前的古诗文 App 虽然功能完整,但仍有很大的扩展空间:
14.1 内容扩展
- 接入网络 API,获取海量古诗文数据
- 支持用户投稿和社区评论
- 添加 AI 语音朗读功能(HarmonyOS 的 AVPlayer)
14.2 功能增强
- 朗读功能:使用 HarmonyOS 的文本转语音(TTS)能力
- 日推功能:每天推送一篇精选古诗文
- 笔记功能:允许用户在诗文上记录阅读心得
- 分享功能:生成诗文卡片分享到社交媒体
14.3 技术升级
- 迁移到新的导航 API,消除
router废弃警告 - 使用
PersistentStorage实现收藏数据的磁盘持久化 - 引入
RelationalStore(SQLite)存储大量内容数据 - 添加折叠屏适配,利用 HarmonyOS 的窗口多任务能力
十五、总结与感悟
通过本次古诗文 App 的开发实践,我获得了以下收获:
关于 HarmonyOS 开发:
-
ArkTS 的学习曲线:对于有 TypeScript 或 SwiftUI 经验的开发者,ArkTS 的上手非常快速。声明式 UI 的思维方式与 React/Vue 一脉相承,但语法更简洁、装饰器机制更强大。
-
生态成熟度:HarmonyOS NEXT 的 API 覆盖度已经相当完善,从 UI 组件到数据存储再到设备能力,基本满足常见应用开发需求。但部分 API 的文档和社区资源仍有提升空间。
-
开发工具:DevEco Studio 提供了完整的 IDE 体验,包括代码补全、实时预览、性能分析等。但在插件生态和稳定性方面,与 Android Studio 和 Xcode 相比还有一定差距。
关于工程实践:
-
分层架构的重要性:将数据模型、业务逻辑和 UI 分离,让项目结构清晰,修改一处不会牵动全局。对于小型项目也许显得"杀鸡用牛刀",但为后续扩展打下了良好基础。
-
响应式编程的优雅:
@State驱动的 UI 更新让开发变得简单——不需要手动操作 DOM,不需要管理复杂的回调链条,只需要关注数据变化即可。 -
错误处理的心态:开发过程中遇到 18 个编译错误,每一个都是学习的机会。从"Builder 不能链式调用"到"系统资源名称变更",这些踩坑经验比官方文档更有记忆点。
关于产品设计:
-
细节决定体验:空状态引导、卡片圆角阴影、标签选中态反馈——这些"小"设计共同塑造了用户对 App 的整体印象。好的产品体验往往藏在细节里。
-
传统文化与技术的结合:古诗文是中华文化的瑰宝,通过移动 App 这种现代载体传播,既保留了传统文化的内涵,又赋予了其新的生命力。技术服务于文化,文化赋予技术温度——这是作为一名开发者最有价值感的事情。
附:完整源码结构
entry/src/main/ets/
├── model/
│ ├── PoemModel.ets # 数据模型(37行)
│ └── PoemData.ets # 19篇诗文数据 + 收藏管理(266行)
├── pages/
│ ├── Index.ets # 首页(176行)
│ ├── DetailPage.ets # 详情页(215行)
│ ├── FavoritesPage.ets # 收藏页(118行)
│ └── SearchPage.ets # 搜索页(162行)
└── entryability/
└── EntryAbility.ets # 应用入口
资源文件:
├── entry/resources/base/profile/main_pages.json # 路由配置
├── entry/resources/base/element/string.json # 字符串资源
├── entry/resources/base/element/color.json # 颜色资源
├── entry/resources/base/element/float.json # 字号资源
└── AppScope/resources/base/element/string.json # 应用名资源

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



所有评论(0)