从零构建鸿蒙NEXT ArkUI应用:每日夸夸神器开发实战
从零构建鸿蒙NEXT ArkUI应用:每日夸夸神器开发实战
一、开篇:鸿蒙生态与ArkUI宣言

1.1 鸿蒙NEXT的时代意义
2025年,HarmonyOS NEXT正式面向开发者全面开放,标志着华为彻底摆脱Android底层依赖,拥有了完全自主研发的操作系统内核、编程框架和编译工具链。HarmonyOS NEXT 6.1.1(API 24)作为当前最新的稳定版本,带来了更成熟的ArkUI声明式UI框架、更完善的跨设备协同能力、更强大的安全隐私保护机制,以及统一的全场景生态体验。
对于前端开发者、移动端开发者乃至传统后端开发者而言,鸿蒙NEXT提供了一个全新的应用开发入口。它不仅仅是一个手机操作系统,更是一个面向1+8+N全场景生态的统一开发平台——从手机、平板、穿戴设备到智慧屏、车机、PC,一套代码可以灵活适配多端运行。
本文将通过一个实际的ArkUI应用——“每日夸夸神器”(Daily Praise Tool)的完整开发过程,深入浅出地讲解HarmonyOS NEXT应用开发的核心知识点,包括工程结构、ArkUI声明式语法、状态管理、Ability生命周期、页面路由、样式设计、调试与打包等。无论你是刚接触鸿蒙开发的新手,还是已有Android/iOS开发经验想转型鸿蒙的工程师,这篇文章都将为你提供一份可参照的实战指南。
1.2 每日夸夸神器:应用简介
"每日夸夸神器"是一款极简但温暖的轻量级应用。它的核心功能非常简洁:每次点击按钮,随机生成一句充满正能量的夸奖文案,为用户带来片刻的鼓励与治愈。整个应用的UI包含一个标题、一个展示夸奖文案的卡片、一个"生成新夸奖"按钮和一个"自定义文案"按钮。
虽然功能简单,但这个应用麻雀虽小五脏俱全,它涵盖了ArkUI应用的典型开发流程:
- 工程创建与项目配置(AppScope、module.json5、build-profile.json5)
- 声明式UI组件树的构建(Column、Text、Button)
- 状态驱动渲染(@State装饰器)
- 事件处理(onClick回调)
- Ability生命周期管理(UIAbility)
- 资源文件管理(字符串、颜色、图片)
- 备份扩展能力(BackupExtensionAbility)
- 调试与构建
接下来,我们就从零开始,一步步拆解这个应用的每个技术细节。
二、鸿蒙NEXT应用工程结构深度解析
2.1 项目概览:从根目录说起
一个标准的HarmonyOS NEXT工程,其根目录结构如下(以"每日夸夸神器"项目为例):
zikua/ # 项目根目录
├── AppScope/ # 应用全局配置
│ ├── app.json5 # 应用级别的配置信息
│ └── resources/ # 应用级资源
├── entry/ # 模块目录(entry类型模块)
│ ├── src/main/ets/ # ArkTS源代码目录
│ │ ├── entryability/ # Ability层代码
│ │ ├── entrybackupability/ # 备份扩展能力
│ │ └── pages/ # 页面代码
│ ├── src/main/resources/ # 模块级资源
│ ├── build-profile.json5 # 模块构建配置
│ └── oh-package.json5 # 模块级依赖管理
├── hvigor/ # 构建工具配置
├── build-profile.json5 # 项目级构建配置
├── oh-package.json5 # 项目级依赖管理
└── oh-package-lock.json5 # 依赖锁定文件
鸿蒙的工程结构采用了 “项目(Project)— 模块(Module)” 两层架构。一个项目可以包含多个模块,每个模块可以是一个独立的功能单元。最常见的模块类型是 entry 类型——它是应用的入口模块,也是应用的主HAP包。一个应用至少需要一个 entry 模块。
这种分层结构与Android的"Project-Module"模式类似,但对于从Web/H5转向鸿蒙的开发者来说,需要理解"Ability"这一核心概念——它是鸿蒙应用的基本调度单元,相当于Android中的Activity或iOS中的ViewController,但在设计粒度上更加灵活。
2.2 AppScope:应用全局配置
AppScope目录下存放的是整个应用的全局配置,其中最重要的文件是 app.json5:
{
"app": {
"bundleName": "com.example.zikua",
"vendor": "example",
"versionCode": 1000000,
"versionName": "1.0.0",
"buildVersion": "1",
"icon": "$media:layered_image",
"label": "$string:app_name"
}
}
| 配置项 | 说明 | 示例值 |
|---|---|---|
bundleName |
应用的唯一标识(包名),遵循反向域名命名规则 | com.example.zikua |
vendor |
应用供应商名称 | example |
versionCode |
内部版本号(递增的整数),用于版本升级判断 | 1000000 |
versionName |
向用户展示的版本名称 | 1.0.0 |
icon |
应用图标,引用资源文件中的媒体资源 | $media:layered_image |
label |
应用名称,引用字符串资源,支持国际化 | $string:app_name |
需要注意的是,鸿蒙NEXT中的资源引用使用 $ 语法,格式为 $资源类型:资源名称,开发者不需要关心资源文件的具体路径,框架会自动按设备类型、语言、分辨率等维度匹配最佳资源。
2.3 module.json5:模块描述文件的秘密
entry/src/main/module.json5 是每个模块的核心配置文件,它的作用类似于Android的AndroidManifest.xml。让我们逐字段解析:
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": ["phone"],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:layered_image",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": ["entity.system.home"],
"actions": ["ohos.want.action.home"]
}
]
}
],
"extensionAbilities": [
{
"name": "EntryBackupAbility",
"srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets",
"type": "backup",
"exported": false,
"metadata": [
{
"name": "ohos.extension.backup",
"resource": "$profile:backup_config"
}
]
}
]
}
}
关键字段解读:
-
type:模块类型。entry表示应用的主入口模块;除entry外还有feature(功能模块,用于动态特性拆分)和har(静态共享库,相当于Android的AAR或iOS的Framework)。 -
mainElement:指定应用启动时的默认Ability,这里是EntryAbility。 -
deviceTypes:数组类型,声明该模块支持哪些设备类型。当前项目仅支持phone。你可以将其扩展为["phone", "tablet", "car"]等来支持多设备。 -
abilities:Ability配置数组。每个Ability可以理解为一个"可调度的功能入口"。EntryAbility配置中:skills:与Android的IntentFilter类似,声明该Ability能响应哪些系统请求。entity.system.home+ohos.want.action.home的组合,表示它是桌面启动器(Launcher)启动的入口Ability。exported:是否允许被其他应用调用。入口Ability通常设置为true。startWindowBackground:启动窗口的背景色,用于在Ability初始化完成前展示,避免白屏。
-
extensionAbilities:扩展Ability,用于提供系统级能力的扩展点。例如type: "backup"表示该模块支持数据备份与恢复。
2.4 构建配置:build-profile.json5
项目根目录的 build-profile.json5 定义了签名配置与应用编译参数:
{
"app": {
"signingConfigs": [],
"compileSdkVersion": 24,
"compatibleSdkVersion": 24,
"products": [
{
"name": "default",
"signingConfig": "default"
}
]
}
}
compileSdkVersion:编译时使用的SDK版本(对应API 24)。compatibleSdkVersion:应用兼容的最低SDK版本。products:产品风味配置,支持不同渠道/环境的差异化构建。
三、Ability:鸿蒙应用的调度核心
3.1 UIAbility生命周期详解
Ability是鸿蒙应用最基本的调度单元。对于带有UI界面的应用,我们使用 UIAbility。每一个UIAbility都会经历从创建到销毁的完整生命周期。
我们的 EntryAbility 代码如下:
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));
}
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
}
onDestroy(): void {
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage): void {
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(DOMAIN, 'testTag',
'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
hilog.info(DOMAIN, 'testTag',
'Succeeded in loading the content.');
});
}
onWindowStageDestroy(): void {
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground(): void {
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground(): void {
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground');
}
}
生命周期流程图:
用户启动应用
│
▼
onCreate() ← Ability对象创建,可在此处做全局初始化
│
▼
onWindowStageCreate() ← 窗口创建,加载页面内容
│
▼
onForeground() ← Ability进入前台(用户可见)
│
├──→ 用户按Home键
│ │
│ ▼
│ onBackground() ← Ability进入后台(不可见)
│ │
│ ├──→ 系统资源紧张 → onDestroy()
│ │
│ └──→ 用户返回应用 → onForeground()
│
└──→ 用户退出应用
│
▼
onWindowStageDestroy()
│
▼
onDestroy()
各生命周期回调的最佳实践:
-
onCreate(want, launchParam)want:启动参数对象,包含目标设备、包名、ability名称以及自定义参数(类似Intent的Extra)。launchParam:启动原因(如冷启动、热启动等)。- 应用场景:全局数据初始化、数据库连接、SDK初始化。
- 在我们的应用中,我们在
onCreate中设置了颜色模式为COLOR_MODE_NOT_SET(跟随系统),避免强制指定亮色/暗色模式。
-
onWindowStageCreate(windowStage)- 窗口创建后的回调,此时UI可以进行页面加载。
windowStage.loadContent是加载页面的核心API,它接受两个参数:页面路径(相对于pages目录的路径)和回调函数。- 回调函数中的
err对象包含code(错误码)和message(错误信息)。需要检查err.code来判断加载是否成功。鸿蒙中,err.code为0表示成功,非0表示错误。
-
onForeground()与onBackground()- 分别对应Ability进入前台(用户可见可交互)和进入后台(被切换或按Home键)。
- 应用场景:在前台时启动动画/传感器/定时器,在后台时释放资源以节省电量。
3.2 扩展Ability:BackupExtensionAbility
我们的项目中还有一个 EntryBackupAbility,它继承自 BackupExtensionAbility:
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit';
const DOMAIN = 0x0000;
export default class EntryBackupAbility extends BackupExtensionAbility {
async onBackup() {
hilog.info(DOMAIN, 'testTag', 'onBackup ok');
await Promise.resolve();
}
async onRestore(bundleVersion: BundleVersion) {
hilog.info(DOMAIN, 'testTag',
'onRestore ok %{public}s', JSON.stringify(bundleVersion));
await Promise.resolve();
}
}
BackupExtensionAbility 是鸿蒙NEXT提供的系统级数据备份/恢复扩展能力。当用户在设置中触发应用备份或恢复时,系统会自动调用对应的回调:
onBackup():执行备份操作,可以将应用的关键数据(如SharedPreferences、数据库文件、沙箱文件)复制到备份目录。onRestore(bundleVersion):执行恢复操作,bundleVersion参数携带了备份时的应用版本信息,可用于数据迁移与兼容性处理。
在 module.json5 中通过 metadata 关联了备份配置描述文件 $profile:backup_config,该文件定义了具体哪些文件或数据库需要被备份。
四、ArkUI声明式UI:构建夸夸神器的界面
4.1 什么是声明式UI
ArkUI是鸿蒙自研的声明式UI框架。所谓"声明式",是指开发者通过声明"UI应该是什么样子的"来构建界面,而不是通过一系列命令式的操作(如 createTextView()、setText()、addView())来逐步构建。
声明式 vs 命令式的对比:
| 维度 | 命令式(传统Android View系统) | 声明式(ArkUI / SwiftUI / Jetpack Compose) |
|---|---|---|
| 状态管理 | 手动调用 setText()、setVisibility() 等 |
状态变量自动驱动UI重绘 |
| 界面构建 | inflate XML + findViewById |
纯代码或DSL声明组件树 |
| UI刷新 | 需要开发者精确控制 | 框架自动计算出最小刷新范围 |
| 可读性 | 逻辑与UI分离,跳转查找 | UI与逻辑在同一作用域,上下文清晰 |
4.2 页面入口:@Entry与@Component
我们的主页面代码位于 pages/Index.ets。首先来看整体结构:
@Entry
@Component
struct Index {
// 组件的状态变量和成员方法
build() {
// 声明UI组件树的描述
}
}
装饰器说明:
@Entry:标记当前组件是页面的入口组件。只有添加了@Entry的组件才会被Ability的loadContent加载。一个页面文件中可以有多个@Component修饰的组件,但只能有一个@Entry。@Component:将自定义结构体标记为ArkUI组件。被@Component修饰的struct必须实现build()方法,该方法返回组件树的描述。struct:ArkUI使用TypeScript/ArkTS中的struct来定义组件,这与传统的class相比更轻量,且天然支持值语义。
4.3 @State:驱动UI的状态变量
@State currentWord: string = "点击下方按钮,接收专属夸奖✨"
@State wordList: string[] = [
"你真的超级优秀,所有努力都会开花结果!",
"你的眼光和能力,远超身边大多数人。",
"保持自信,你值得世间所有美好。",
"今天的你依旧闪闪发光,继续向前走吧!",
"不用和别人对比,做自己就足够耀眼。",
"头脑灵活执行力强,没有你办不成的小事。",
"温柔又强大,你真的太棒了!"
]
@State 是ArkUI中最基础的状态装饰器。它的核心工作原理是:
- 监听:框架会追踪
@State修饰的变量。 - 关联:当组件的
build()方法中使用了该状态变量时,框架自动建立依赖关系。 - 触发:当状态变量的值发生变化时,框架自动重新执行
build()方法,只更新受影响的部分,而非全量刷新。 - 最小化更新:ArkUI采用脏检查机制,只对UI树中与变化状态相关的节点进行局部更新,性能优异。
在我们的场景中:
currentWord:当前展示的夸奖文案,初始值为引导文案。每次点击按钮更新它,Text组件自动更新显示。wordList:夸奖文案列表,是一个字符串数组。它作为数据源,不会被修改,但仍然用@State标记(当然,对于纯数据源也可以用普通变量,但@State可以保证未来如果动态增删条目时UI自动响应)。
@State的初始化规则:
// 方式一:在声明时直接初始化(推荐)
@State count: number = 0
// 方式二:在struct的构造函数中初始化
@State count: number
constructor(count: number) {
this.count = count;
}
// 注意:@State不支持复杂对象的深拷贝监听,
// 对于深层次嵌套对象,需要配合 @Observed 和 @ObjectLink 使用
4.4 build():声明组件树
build() 是ArkUI组件的核心方法,它必须返回一个合法的组件树描述。我们的代码:
build() {
Column() {
// 标题
Text("每日夸夸神器")
.fontSize(30)
.fontWeight(FontWeight.Bold)
.margin({ top: 40, bottom: 40 })
// 夸奖文本卡片
Text(this.currentWord)
.fontSize(20)
.textAlign(TextAlign.Center)
.padding(30)
.width("90%")
.backgroundColor("#FFF0F5")
.borderRadius(20)
.margin({ bottom: 40 })
// 生成按钮
Button("生成新夸奖")
.width(180)
.height(50)
.fontSize(18)
.backgroundColor("#FF69B4")
.fontColor("#FFFFFF")
.onClick(() => {
let randomIndex = Math.floor(Math.random() * this.wordList.length)
this.currentWord = this.wordList[randomIndex]
})
Button("自定义文案")
.width(180)
.height(50)
.fontSize(18)
.margin({ top: 20 })
.backgroundColor("#87CEFA")
}
.width("100%")
.height("100%")
.backgroundColor("#ffffff")
.padding(20)
}
4.4.1 Column:垂直线性布局
Column 是ArkUI最基本的布局容器之一,它将子组件沿垂直方向依次排列,类似于CSS Flexbox中 flex-direction: column。
Column的关键属性:
Column() {
// 子组件按顺序垂直排列
}
.width("100%") // 宽度撑满父容器
.height("100%") // 高度撑满父容器
.backgroundColor("#ffffff") // 背景色:纯白
.padding(20) // 内边距:20vp(视觉像素单位)
ArkUI中的布局单位是 vp(virtual pixel,虚拟像素),它是一种与设备密度无关的单位,类似于Android中的dp或iOS中的pt。1vp在不同设备上对应的物理像素数不同,但视觉大小保持基本一致。此外还有 lpx(逻辑像素),通常用于响应式设计。
4.4.2 Text:文本组件
Text 组件用于展示文本内容。我们的代码中使用了两种样式的Text:
标题文本:
Text("每日夸夸神器")
.fontSize(30) // 字号:30vp
.fontWeight(FontWeight.Bold) // 字重:粗体
.margin({ top: 40, bottom: 40 }) // 上下外边距各40vp
内容文本(带状态绑定):
Text(this.currentWord) // 绑定状态变量
.fontSize(20)
.textAlign(TextAlign.Center) // 文本居中
.padding(30) // 内边距30vp
.width("90%") // 宽度为父容器90%
.backgroundColor("#FFF0F5") // 背景色:lavender blush(淡紫红)
.borderRadius(20) // 圆角半径20vp
.margin({ bottom: 40 }) // 底部外边距40vp
Text组件的常用属性一览:
| 属性API | 作用 | 示例 |
|---|---|---|
.fontSize(value) |
设置字号,支持number(vp)和string(百分比) | .fontSize(20) |
.fontWeight(value) |
设置字重,支持 FontWeight 枚举或number |
.fontWeight(FontWeight.Bold) |
.fontColor(value) |
设置字体颜色 | .fontColor(Color.Blue) 或 .fontColor("#FF0000") |
.textAlign(value) |
设置文本对齐方式 | .textAlign(TextAlign.Center) |
.lineHeight(value) |
设置行高 | .lineHeight(30) |
.textOverflow(value) |
文本超出时的处理方式 | .textOverflow({overflow: TextOverflow.Ellipsis}) |
.maxLines(value) |
最大行数 | .maxLines(2) |
.decoration(value) |
文字装饰(下划线/删除线) | .decoration({type: TextDecorationType.Underline}) |
4.4.3 Button:按钮组件
Button组件用于触发交互操作。我们的页面中有两个按钮:
主按钮——生成新夸奖:
Button("生成新夸奖")
.width(180) // 宽度180vp
.height(50) // 高度50vp
.fontSize(18) // 按钮文字字号
.backgroundColor("#FF69B4") // 热粉色背景
.fontColor("#FFFFFF") // 白色文字
.onClick(() => { // 点击回调
let randomIndex = Math.floor(Math.random() * this.wordList.length)
this.currentWord = this.wordList[randomIndex]
})
次按钮——自定义文案:
Button("自定义文案")
.width(180)
.height(50)
.fontSize(18)
.margin({ top: 20 }) // 与上方按钮的间隔
.backgroundColor("#87CEFA") // 浅天蓝色背景
Button的不同创建形式:
ArkUI的Button支持多种创建方式:
// 形式一:带文本标签(最常用)
Button("点击我")
.onClick(() => { /* ... */ })
// 形式二:无参数,通过子组件自定义内容
Button() {
Image($r("app.media.icon"))
.width(24)
.height(24)
Text("带图标的按钮")
}
.onClick(() => { /* ... */ })
// 形式三:带样式参数
Button({ type: ButtonType.Capsule, stateEffect: true }) {
Text("胶囊按钮")
}
Button类型(ButtonType):
ButtonType.Capsule:胶囊形状(两端半圆)ButtonType.Circle:圆形ButtonType.Normal:默认直角矩形
点击事件处理:onClick与手势系统
onClick 是最基础的事件监听器。但在ArkUI中,交互事件远不止于点击。ArkUI提供了丰富的手势系统:
// 基础点击
Button("点击").onClick(() => { })
// 长按
Button("长按").onLongPress(() => { })
// 拖拽
Column()
.gesture(
PanGesture()
.onActionStart(() => { })
.onActionUpdate((event) => { })
.onActionEnd(() => { })
)
// 组合手势(同时识别点击和长按)
Column()
.gesture(
GestureGroup(GestureMode.Exclusive,
TapGesture().onAction(() => { }),
LongPressGesture().onAction(() => { })
)
)
4.5 随机算法:简洁但完整的逻辑实现
生成新夸奖的核心逻辑只有一行:
let randomIndex = Math.floor(Math.random() * this.wordList.length)
this.currentWord = this.wordList[randomIndex]
逐层拆解:
Math.random():返回[0, 1)范围内的伪随机浮点数。Math.random() * this.wordList.length:将随机数映射到[0, wordList.length)范围。Math.floor(...):向下取整,得到[0, wordList.length - 1]范围内的整数。this.wordList[randomIndex]:从数组中取出对应索引的文案。this.currentWord = ...:赋值给状态变量,触发UI刷新。
避免重复的扩展思路:
当前实现每次独立随机,可能出现连续两次显示相同文案的情况。如果需要避免重复,可以这样改进:
@State lastIndex: number = -1
generatePraise(): void {
let newIndex: number
do {
newIndex = Math.floor(Math.random() * this.wordList.length)
} while (newIndex === this.lastIndex && this.wordList.length > 1)
this.lastIndex = newIndex
this.currentWord = this.wordList[newIndex]
}
五、ArkUI中的状态管理进阶
5.1 状态装饰器家族
@State 只是ArkUI状态管理体系中的冰山一角。ArkUI提供了多层次的状态管理方案,来应对从简单到复杂的各种场景:
| 装饰器 | 作用域 | 用途 |
|---|---|---|
@State |
组件内部 | 组件内部状态,变化触发本组件刷新 |
@Prop |
父→子(单向) | 父组件传递给子组件的状态,子组件可以修改但不会影响父组件 |
@Link |
父↔子(双向) | 父组件与子组件共享状态,任何一方的修改都会同步 |
@Provide / @Consume |
祖先↔后代 | 跨层级组件间的状态共享(无需逐层传递) |
@Observed / @ObjectLink |
复杂对象 | 深度监听嵌套对象的属性变化 |
@StorageProp / @StorageLink |
App全局 | 跨Ability/跨页面的全局状态存储 |
LocalStorage / AppStorage |
页面级/应用级 | 独立于组件树之外的存储实例 |
5.2 父子组件通信示范
假设我们要将"夸夸神器"中的文案列表抽取成独立的子组件,展示父子组件之间的状态传递:
父组件(Index.ets):
@Entry
@Component
struct Index {
@State currentWord: string = "点击下方按钮,接收专属夸奖✨"
@State wordList: string[] = [
"你真的超级优秀,所有努力都会开花结果!",
"你的眼光和能力,远超身边大多数人。",
"保持自信,你值得世间所有美好。",
"今天的你依旧闪闪发光,继续向前走吧!",
"不用和别人对比,做自己就足够耀眼。",
"头脑灵活执行力强,没有你办不成的小事。",
"温柔又强大,你真的太棒了!"
]
@State wordCount: number = 7
build() {
Column() {
// 将currentWord以@Prop方式传递给子组件
PraiseCard({ message: this.currentWord })
// 将wordCount以@Link方式共享
PraiseCounter({ count: this.wordCount })
Button("新增一条夸奖")
.onClick(() => {
this.wordList.push("新加的一句夸奖")
this.wordCount = this.wordList.length
})
}
}
}
子组件——PraiseCard(单向@Prop):
@Component
struct PraiseCard {
@Prop message: string // 父组件传入,只读但可本地修改
build() {
Text(this.message)
.fontSize(20)
.padding(20)
.backgroundColor("#FFF0F5")
.borderRadius(16)
}
}
子组件——PraiseCounter(双向@Link):
@Component
struct PraiseCounter {
@Link count: number // 与父组件的wordCount双向同步
build() {
Text(`当前共有 ${this.count} 条夸奖文案`)
.onClick(() => {
this.count++ // 修改会同步回父组件
})
}
}
@Prop 与 @Link 的核心区别:
@Prop是单向数据流:父组件修改 → 子组件刷新;子组件修改自己的副本 → 不会传回父组件。@Link是双向同步:父子任意一方修改值,双方都会刷新。
5.3 @Provide / @Consume:跨层级通信
当组件层级很深时(如祖父→父→子→孙),逐层通过 @Prop 传递会非常繁琐。此时可以使用 @Provide / @Consume:
// 祖先组件
@Component
struct GrandParent {
@Provide("themeColor") themeColor: string = "#FF69B4"
build() {
Column() {
ParentComponent()
}
}
}
@Component
struct ParentComponent {
build() {
ChildComponent()
}
}
// 孙组件(无需ParentComponent中转)
@Component
struct ChildComponent {
@Consume("themeColor") themeColor: string
build() {
Button("夸夸我")
.backgroundColor(this.themeColor)
}
}
@Provide 会将变量注入到组件子树中,@Consume 可以在任意后代组件中直接获取该变量,无需逐层传递。这是ArkUI实现"依赖注入"的方式。
5.4 AppStorage:应用级全局状态
如果需要在不同Ability或不同模块之间共享状态,可以使用 AppStorage:
// 初始化全局状态
AppStorage.SetOrCreate("praiseCount", 0)
// 在任意组件中绑定
@StorageLink("praiseCount") praiseCount: number = 0
// 使用时
Button(`已生成 ${this.praiseCount} 次夸奖`)
.onClick(() => {
this.praiseCount++ // 自动同步到所有绑定了该键的组件
})
六、资源管理与国际化
6.1 资源文件组织
鸿蒙的资源管理遵循限定词目录机制,支持按语言、屏幕密度、设备类型等维度组织资源文件。典型的资源目录结构如下:
resources/
├── base/ # 基础资源(所有情况通用)
│ ├── element/ # 基础元素(字符串、颜色、数值等)
│ │ ├── string.json # 字符串定义
│ │ ├── color.json # 颜色定义
│ │ └── float.json # 浮点数定义
│ ├── media/ # 媒体文件(图片、音频等)
│ │ ├── startIcon.png
│ │ └── layered_image.png
│ └── profile/ # 配置文件(如备份配置、页面路由配置)
│ ├── main_pages.json
│ └── backup_config.json
├── en_US/ # 美国英语限定词目录(当系统语言为美式英语时生效)
│ └── element/
│ └── string.json
├── zh_CN/ # 中文简体限定词目录
│ └── element/
│ └── string.json
├── ldpi/ # 低密度屏幕(~120dpi)
│ └── media/
└── dark/ # 深色模式限定词目录
└── element/
└── color.json
限定词的匹配优先级: 系统会根据当前设备的语言、区域、屏幕密度、字体大小、深色模式等维度,从最精确匹配的限定词目录中加载资源。
6.2 字符串资源与国际化
在 resources/base/element/string.json 中定义字符串:
{
"string": [
{
"name": "app_name",
"value": "每日夸夸神器"
},
{
"name": "module_desc",
"value": "每日夸夸神器模块"
},
{
"name": "EntryAbility_desc",
"value": "应用主入口"
},
{
"name": "EntryAbility_label",
"value": "夸夸神器"
},
{
"name": "praise_title",
"value": "每日夸夸神器"
}
]
}
在英文资源 resources/en_US/element/string.json 中提供对应翻译:
{
"string": [
{
"name": "app_name",
"value": "Daily Praise Tool"
},
{
"name": "praise_title",
"value": "Daily Praise Tool"
}
]
}
在代码中引用资源的方式:
// 方式一:通过 $r 引用(推荐,支持自动适配)
Text($r("app.string.praise_title"))
.fontSize(30)
// 方式二:硬编码(不利于国际化,不推荐)
Text("每日夸夸神器")
使用 $r("app.string.xxx") 引用字符串资源的好处是:当系统语言切换时,文本会自动切换到对应语言的翻译,无需修改代码。
6.3 颜色与样式资源
颜色也可以抽取到资源文件中,方便统一管理主题色:
// resources/base/element/color.json
{
"color": [
{
"name": "primary_pink",
"value": "#FF69B4"
},
{
"name": "secondary_blue",
"value": "#87CEFA"
},
{
"name": "card_bg",
"value": "#FFF0F5"
},
{
"name": "start_window_background",
"value": "#FFFFFF"
}
]
}
深色模式下的颜色资源(resources/dark/element/color.json):
{
"color": [
{
"name": "card_bg",
"value": "#2D2D2D" // 深色模式下卡片背景变为深灰
},
{
"name": "start_window_background",
"value": "#1A1A2E" // 深色模式启动窗口背景
}
]
}
在代码中引用颜色:
Text("每日夸夸神器")
.backgroundColor($r("app.color.card_bg"))
// 如果当前设备处于深色模式,自动使用 dark 限定词目录下的颜色值
6.4 图片资源管理
图片资源统一放在 resources/base/media/ 目录下,支持PNG、JPG、SVG、WebP等格式。引用方式:
// 在代码中引用
Image($r("app.media.startIcon"))
.width(48)
.height(48)
// 在配置文件中引用
// module.json5 中的 startWindowIcon: "$media:startIcon"
鸿蒙还提供了**分层图标(Layered Image)**机制,允许将图标拆分为前景和背景两层,在主题切换时自动适配颜色。
七、调试、测试与构建
7.1 hilog:结构化日志系统
在代码中我们使用了 hilog 进行日志输出:
import { hilog } from '@kit.PerformanceAnalysisKit';
const DOMAIN = 0x0000;
// 日志输出
hilog.info(DOMAIN, 'testTag', 'Ability onCreate');
hilog.error(DOMAIN, 'testTag', 'Failed: %{public}s', JSON.stringify(err));
hilog的日志级别:
| 方法 | 级别 | 使用场景 |
|---|---|---|
hilog.debug() |
DEBUG | 调试信息,发布版本默认不输出 |
hilog.info() |
INFO | 关键流程信息 |
hilog.warn() |
WARN | 警告信息,不影响主流程但值得关注 |
hilog.error() |
ERROR | 错误信息,需要开发者定位处理 |
hilog.fatal() |
FATAL | 致命错误,会导致应用崩溃 |
格式化参数:
%{public}s:公开字符串(在日志中明文显示)。%{private}s:私有字符串(在正式日志中被脱敏处理为<private>)。
在开发调试阶段,建议关键信息使用 %{public}s 以方便定位问题;涉及用户隐私的数据(如手机号、账号)务必使用 %{private}s。
7.2 单元测试
鸿蒙NEXT推荐使用 @ohos/hypium 和 @ohos/hamock 进行单元测试。测试文件通常放在 src/test/ 目录下:
// List.test.ets
import { describe, it, expect } from '@ohos/hypium';
describe('praiseTest', () => {
it('should_return_different_word_when_multiple_clicks', 0, () => {
const wordList = [
"你真的超级优秀",
"你的眼光和能力",
"保持自信"
];
const results = new Set<string>();
for (let i = 0; i < 100; i++) {
const index = Math.floor(Math.random() * wordList.length);
results.add(wordList[index]);
}
// 验证100次随机中,至少出现了2种不同的结果
expect(results.size).assertGreaterThan(1);
});
it('should_return_valid_index', 0, () => {
const wordList = ["a", "b", "c"];
for (let i = 0; i < 1000; i++) {
const index = Math.floor(Math.random() * wordList.length);
// 索引始终在有效范围内
expect(index).assertLessThan(wordList.length);
expect(index).assertGreaterThan(-1);
}
});
});
LocalUnit.test.ets 文件则展示了本地单元测试的基本结构,可以配合 @ohos/hamock 进行Mock测试。
7.3 应用构建与HAP打包
鸿蒙应用的编译产物是 HAP(Harmony Ability Package) 包。构建过程通过 hvigor 构建工具完成:
# 构建Debug包(用于调试)
hvigor assembleHap
# 构建Release包(用于发布)
hvigor assembleHap --mode release
# 构建App Pack(用于上架应用市场)
hvigor assembleApp --mode release
构建产物通常位于各个模块的 build/default/outputs/ 目录下。Release包需使用发布证书签名,签名配置在 build-profile.json5 的 signingConfigs 中指定。
八、鸿蒙NEXT开发常见问题与避坑指南
8.1 资源引用格式
在代码和配置文件中,资源引用的格式略有不同,初学者容易混淆:
| 位置 | 格式 | 示例 |
|---|---|---|
| eTS代码 | $r("app.type.name") |
$r("app.string.app_name") |
| JSON配置文件 | $type:name |
$string:app_name |
| 媒体文件 | $media:name(配置) / $r("app.media.name")(代码) |
$media:startIcon |
8.2 类型安全:ArkTS与TypeScript的区别
ArkTS是鸿蒙对TypeScript的定制版本,它做了更严格的类型约束来优化运行时性能:
// TypeScript 允许(ArkTS 不允许)
let data: any = getData()
data.someMethod()
// ArkTS 要求显式类型
let data: Record<string, Object> = getData()
// TypeScript 允许(ArkTS 不允许)
function fn(a: number, b?: number) { }
fn(1) // 可选参数不加也可以
// ArkTS 要求严格匹配参数
function fn(a: number, b?: number) { }
fn(1) // 必须传递已声明的所有必选参数
ArkTS的关键限制:
- 不允许使用
any类型,必须使用明确类型或Object、Record<string, Object>。 - 不允许使用
as any类型断言。 - 不允许使用
!非空断言操作符。 - 不允许在运行时动态修改对象的原型链。
- 不允许使用
eval、Function构造函数等动态执行代码。
8.3 布局常见问题
子组件溢出: 当 Column 或 Row 的宽度/高度固定,子组件尺寸总和超出时,默认情况下子组件会被压缩或溢出。可以使用 .clip(true) 裁剪超出部分或 .layoutWeight(1) 按比例分配空间。
文本换行: Text组件默认会自动换行,但如果设置 .maxLines() 并配合 .textOverflow({overflow: TextOverflow.Ellipsis}) 可以实现超出省略号效果。
位置偏移: 使用 .offset({x: 10, y: 20}) 可以进行相对位置偏移,而不是通过 margin 或 padding。
8.4 性能优化建议
- 避免在build()中执行耗时操作:
build()方法可能会被频繁调用,应避免在其中执行计算密集型任务或异步操作。 - 合理使用状态变量:不要把所有变量都声明为
@State,只有直接影响UI的变量才需要状态装饰器。 - 懒加载列表:当需要展示大量数据时,使用
LazyForEach代替ForEach实现按需渲染。 - 图片缓存:对网络图片使用
Image组件的.syncLoad(true)或配合ImageCache策略。
九、未来扩展:从单页到全场景
当前的"每日夸夸神器"虽然功能简单,但它拥有巨大的扩展空间。以下是一些值得尝试的进阶方向:
9.1 路由与多页面
引入 router 模块实现页面跳转:
import { router } from '@kit.ArkUI';
// 跳转到自定义文案页面
Button("自定义文案")
.onClick(() => {
router.pushUrl({
url: 'pages/CustomPraise',
params: { currentList: this.wordList }
});
})
9.2 数据持久化
使用 Preferences 保存用户自定义的夸奖文案:
import { preferences } from '@kit.ArkData';
async function savePraiseList(context: Context, list: string[]) {
const store = await preferences.getPreferences(context, 'praise_store');
await store.put('praise_list', JSON.stringify(list));
await store.flush();
}
async function loadPraiseList(context: Context): Promise<string[]> {
const store = await preferences.getPreferences(context, 'praise_store');
const json = await store.get('praise_list', '[]');
return JSON.parse(json.toString());
}
9.3 通知与卡片
通过 @ohos.backgroundTaskManager 实现定时推送每日一句夸奖到通知栏,或开发ArkTS卡片(Form)让用户在桌面直接看到当日的夸奖文案,无需打开应用。
9.4 跨设备流转
利用鸿蒙的分布式能力,将夸奖文案跨端流转到手表或智慧屏上。例如,用户可以在手机上收到每日夸奖,同样在手表上同步展示:
import { distributedObject } from '@kit.DistributedServiceKit';
const object = distributedObject.genCreateObject('praise_sync', {
word: ''
});
// 更新后自动同步到所有登录同账号的设备
this.currentWord = "你真棒!";
object.word = this.currentWord;
十、总结与展望
通过"每日夸夸神器"这个完整应用,我们系统地走过了HarmonyOS NEXT 6.1.1(API 24)应用开发的全流程:
- 工程结构 —— 理解了项目、模块、Ability的层级关系,以及
app.json5、module.json5、build-profile.json5等关键配置文件的含义。 - UIAbility生命周期 —— 掌握了
onCreate→onWindowStageCreate→onForeground→onBackground→onWindowStageDestroy→onDestroy的完整链路,以及各阶段的最佳实践。 - ArkUI声明式UI —— 学会了
@Entry、@Component、@State装饰器的使用,以及Column、Text、Button等基础组件的组合与样式设置。 - 状态管理 —— 掌握了
@State驱动UI刷新的原理,并了解了@Prop、@Link、@Provide/@Consume、AppStorage等进阶状态管理方案。 - 事件处理 —— 掌握了
onClick交互以及手势系统的扩展能力。 - 资源管理 —— 理解了限定词目录机制和国际化实现方式。
- 调试与测试 —— 了解了
hilog日志系统和hypium测试框架的基本用法。
鸿蒙NEXT作为新一代的全场景智慧生活操作系统,其声明式UI框架ArkUI在开发效率、运行性能和多端适配方面展现出了强大的能力。从一个人的夸夸神器,到全家人共享的温暖应用,甚至是跨设备流转的分布式体验,鸿蒙生态为开发者提供了无限可能。
最后,送大家一句话:在代码的世界里,每次点击"生成新夸奖",都是一次对生活的温柔致敬。保持好奇,保持温暖,你的下一行代码,也许就是点亮他人的那道光。
参考资料与延伸阅读:
- 本文所有代码基于 HarmonyOS NEXT 6.1.1 SDK(API 24)
- ArkUI组件完整API参考
- ArkTS编程规范与最佳实践
- 鸿蒙Ability开发指南
更多推荐


所有评论(0)