HarmonyOS 桌面卡片开发实战:为 SOS 紧急呼救功能添加桌面卡片
目录
一、项目结构概览
项目采用多模块架构,帮助模块 help 是一个 HAR(静态共享包),被入口 HAP products/default 依赖:
GuardEHome/
├── features/
│ └── help/ # HAR 模块(被 HAP 依赖)
│ └── src/main/ets/
│ ├── HelpPage.ets # 【改造】添加 @StorageProp 监听卡片信号
│ ├── view/
│ │ ├── SOSView.ets # SOS 按钮(长按 1.5s 触发)
│ │ └── DistressView.ets # 紧急状态页(红黑闪烁)
│ └── constants/
│ └── CommonConstants.ets
│
├── products/
│ └── default/ # Entry HAP 模块(卡片代码写在此处)
│ └── src/main/
│ ├── ets/
│ │ ├── defaultability/
│ │ │ └── DefaultAbility.ets # 【改造】拦截卡片 Want 参数
│ │ ├── soswidgetability/
│ │ │ └── SOSWidgetAbility.ets # 【新增】FormExtensionAbility
│ │ └── pages/
│ │ ├── Index.ets
│ │ └── SOSWidgetPage.ets # 【新增】卡片 UI 页面
│ ├── module.json5 # 【改造】注册 extensionAbilities
│ └── resources/
│ └── base/
│ ├── element/
│ │ └── string.json # 【改造】添加卡片名称/描述
│ └── profile/
│ ├── main_pages.json # 【改造】注册卡片页面路由
│ └── form_config.json # 【新增】卡片元信息配置
关键约束:FormExtensionAbility 必须存在于 HAP 中,无法写在 HAR 里。所以卡片相关的 Ability 和页面需要放到 products/default 模块,通过 AppStorage 与 help 模块的 HelpPage 通信。
二、实现思路
桌面卡片 (SOSWidgetPage.ets)
│
│ postCardAction('router', { target: 'sos' })
▼
DefaultAbility (onCreate / onNewWant)
│
│ AppStorage.setOrCreate('sosWidgetTrigger', true)
▼
HelpPage (监听 @StorageProp 变化)
│
│ currentView = DISTRESS_VIEW
▼
DistressView(红黑闪烁 → 紧急呼救)
整个链路最核心的设计是:
- 卡片侧:ArkTS 卡片通过
postCardAction向 App 发送router事件,附带参数target: 'sos'。 - Ability 侧:不论是冷启动(
onCreate)还是热启动(onNewWant),只要收到target === 'sos',就将标记写入AppStorage。 - UI 侧:
HelpPage通过@StorageProp('sosWidgetTrigger')绑定该标记,一旦检测为true就切换到DistressView。
三、实操步骤
3.1 创建卡片 UI 页面
卡片页面是一个标准的 @Entry + @Component 组件,布局上以大红色背景突出 SOS 警示感。
// products/default/src/main/ets/pages/SOSWidgetPage.ets
@Entry
@Component
struct SOSWidgetPage {
build() {
// 整体容器:红色背景,居中排列
Column() {
// 大号 SOS 文字,加粗白色
Text('SOS')
.fontSize(36)
.fontWeight(FontWeight.Bold)
.fontColor(Color.White)
// 副标题提示
Text('紧急呼救')
.fontSize(12)
.fontColor(Color.White)
.margin({ top: 4 })
}
.width('100%')
.height('100%')
.backgroundColor('#FF1744') // 醒目的警示红
.borderRadius(16)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
// 点击卡片 → 打开 App 并传递 SOS 触发信号
.onClick(() => {
postCardAction(this, {
'action': 'router', // router 类型:打开指定 Ability
'abilityName': 'DefaultAbility', // 目标 Ability 名称
'moduleName': 'default', // 目标模块名称
'params': {
'target': 'sos' // 自定义参数,Ability 端据此识别
}
});
})
}
}
关键 API:
postCardAction是 ArkTS 卡片专有 API,支持router、message、call三种 action。此处用router打开 App 主 Ability 并传递参数。
3.2 创建 FormExtensionAbility
FormExtensionAbility 是卡片生命周期的管理者。虽然本例的卡片 UI 不需要动态更新数据,但仍需提供骨架实现。
// products/default/src/main/ets/soswidgetability/SOSWidgetAbility.ets
import { FormExtensionAbility, formBindingData } from '@kit.FormKit';
import { Want } from '@kit.AbilityKit';
export default class SOSWidgetAbility extends FormExtensionAbility {
/**
* 用户将卡片添加到桌面时调用
* @param want 包含卡片配置信息的 Want
* @returns 表单绑定数据
*/
onAddForm(want: Want): formBindingData.FormBindingData {
// 本例为静态卡片,无需传递初始数据
return formBindingData.createFormBindingData({});
}
/**
* 用户从桌面移除卡片时调用
*/
onRemoveForm(formId: string): void {
console.info('SOSWidgetAbility onRemoveForm');
}
/**
* 卡片接收到 message 事件时调用
*/
onFormEvent(formId: string, message: string): void {
console.info('SOSWidgetAbility onFormEvent');
}
}
3.3 配置 form_config.json
form_config.json 描述卡片的元信息:名称、尺寸、页面路径等。注意这是一个独立的配置文件,需要和 main_pages.json 并列放在 profile 目录下。
// products/default/src/main/resources/base/profile/form_config.json
{
"forms": [
{
"name": "SOSWidget", // 卡片唯一标识名
"uiSyntax": "arkts", // 必须指定为 arkts
"displayName": "$string:sos_widget_name", // 卡片显示名称(引用字符串资源)
"description": "$string:sos_widget_desc", // 卡片描述
"src": "./ets/pages/SOSWidgetPage.ets", // 卡片 UI 页面路径(相对于 src/main/)
"window": {
"designWidth": 720, // 设计稿宽度
"autoDesignWidth": true // 自动适配设备宽度
},
"colorMode": "auto", // 自适应深色/浅色模式
"isDefault": true, // 设为默认卡片
"updateEnabled": false, // 不需要定时更新
"scheduledUpdateTime": "10:30",
"updateDuration": 0, // 0 表示不自动更新
"defaultDimension": "2*2", // 默认尺寸 2×2 栅格
"supportDimensions": [ // 支持的尺寸列表
"2*2"
]
}
]
}
重点:
src路径是相对于模块根目录src/main/的,必须是./ets/pages/xxx.ets格式。
3.4 在 module.json5 中注册
在 entry 模块的 module.json5 中添加 extensionAbilities 节点,将 SOSWidgetAbility 注册为 form 类型扩展能力。
// products/default/src/main/module.json5
{
"module": {
"name": "default",
"type": "entry",
// ... 其他配置 ...
"extensionAbilities": [
{
"name": "DefaultBackupAbility",
"srcEntry": "./ets/defaultbackupability/DefaultBackupAbility.ets",
"type": "backup",
"exported": false,
"metadata": [
{
"name": "ohos.extension.backup",
"resource": "$profile:backup_config"
}
]
},
// 👇 新增:卡片扩展能力
{
"name": "SOSWidgetAbility", // 类名
"srcEntry": "./ets/soswidgetability/SOSWidgetAbility.ets", // 入口文件
"type": "form", // 类型固定为 form
"exported": false,
"metadata": [
{
"name": "ohos.extension.form", // 固定 metadata 名称
"resource": "$profile:form_config" // 引用 form_config.json
}
]
}
]
}
}
3.5 将卡片页面注册到 main_pages.json
卡片页面也属于页面路由,必须在 main_pages.json 中注册,否则构建系统会提示 “Form referenced in the config was not found”。
// products/default/src/main/resources/base/profile/main_pages.json
{
"src": [
"pages/Index",
"pages/AdaptiveIndex",
"pages/ResponsiveIndex",
"pages/SystemCapabilitiesIndex",
"pages/SOSWidgetPage"
]
}
3.6 添加字符串资源
在 string.json 中添加卡片相关的国际化字符串。
// products/default/src/main/resources/base/element/string.json
{
"string": [
// ... 已有配置 ...
{
"name": "sos_widget_name",
"value": "SOS 紧急呼救"
},
{
"name": "sos_widget_desc",
"value": "一键触发 SOS 紧急呼救"
}
]
}
3.7 处理卡片点击事件——改造 Ability
当用户点击桌面卡片时,系统会通过 postCardAction 发送一个 Want 到 DefaultAbility。无论 App 处于冷启动还是热启动状态,我们都需要拦截这个参数并传递给 UI。
// products/default/src/main/ets/defaultability/DefaultAbility.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 DefaultAbility extends UIAbility {
/**
* 冷启动:App 进程不存在时首次创建
* @param want 启动参数(包含卡片传来的 target)
*/
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
try {
this.context.getApplicationContext()
.setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
} catch (err) {
hilog.error(DOMAIN, 'testTag', 'setColorMode failed: %{public}s', JSON.stringify(err));
}
// 检查是否由 SOS 卡片触发
this.checkWidgetTrigger(want);
}
/**
* 热启动:App 已在后台运行,重新被唤起
* @param want 新的启动参数
*/
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
this.checkWidgetTrigger(want);
}
/**
* 统一处理卡片触发的 SOS 信号
* 将信号写入 AppStorage,HelpPage 通过 @StorageProp 监听
*/
private checkWidgetTrigger(want: Want): void {
const target: string = want?.parameters?.target as string;
if (target === 'sos') {
// AppStorage 是 ArkUI 全局状态存储,可在 Ability 和 Component 间共享
AppStorage.setOrCreate('sosWidgetTrigger', true);
}
}
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(DOMAIN, 'testTag', 'loadContent failed: %{public}s', JSON.stringify(err));
return;
}
});
}
// ... 其他生命周期方法保持不变 ...
}
核心思路:
AppStorage是 ArkUI 内置的全局键值存储,不区分模块边界。在 Ability 中写入、在 HAR 模块的 Component 中读取,完美解决跨模块通信问题。
3.8 监听触发信号——改造 HelpPage
HelpPage 原本通过 currentView 状态控制视图切换。我们通过 @StorageProp 绑定 sosWidgetTrigger,当该值变为 true 时自动跳转到 DistressView。
// features/help/src/main/ets/HelpPage.ets
import { ViewConstants } from './constants/CommonConstants';
import { EmergencyContact } from './model/EmergencyContact';
import { EmergencyContactViewModel } from './viewmodel/EmergencyContactViewModel';
import { SOSView } from './view/SOSView';
import { DistressView } from './view/DistressView';
import { ContactListView } from './view/ContactListView';
import { ContactEditView } from './view/ContactEditView';
import { EmergencyCallView } from './view/EmergencyCallView';
import { window } from '@kit.ArkUI';
@Component
export struct HelpPage {
@State currentView: number = ViewConstants.HUB_VIEW;
@State contactList: EmergencyContact[] = [];
@State editContactId: number = -1;
@State isEditing: boolean = false;
@State topPadding: number = 24;
private contactViewModel: EmergencyContactViewModel = new EmergencyContactViewModel();
// 👇 @StorageProp 绑定 AppStorage 中的卡片触发标记
// @Watch 在标记值变化时自动回调
@StorageProp('sosWidgetTrigger') @Watch('onWidgetTrigger') sosWidgetTrigger: boolean = false;
/**
* @Watch 回调:当 AppStorage 中 sosWidgetTrigger 变为 true 时触发
* 适用于 App 已在运行、用户点击卡片的热启动场景
*/
onWidgetTrigger(): void {
if (this.sosWidgetTrigger) {
this.currentView = ViewConstants.DISTRESS_VIEW; // 切换到紧急状态页
AppStorage.setOrCreate('sosWidgetTrigger', false); // 重置标记,防止重复触发
}
}
aboutToAppear(): void {
// 👇 冷启动场景:组件初次创建时检查标记的初始值
if (this.sosWidgetTrigger) {
this.currentView = ViewConstants.DISTRESS_VIEW;
AppStorage.setOrCreate('sosWidgetTrigger', false);
}
// 获取状态栏高度,适配安全区域
window.getLastWindow(getContext(this)).then((data: window.Window) => {
let avoidArea: window.AvoidArea =
data.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);
this.topPadding = this.getUIContext().px2vp(avoidArea.topRect.height + 8);
}).catch(() => {
this.topPadding = 36;
});
}
// ... 其余方法(build、HubContent、FeatureCard 等)保持不变 ...
}
注意:
@Watch只在值变化后回调,首次初始化时不会触发。所以冷启动场景需要在aboutToAppear中额外检查一次初始值。
四、编译与运行
4.1 执行构建
# 在 DevEco Studio 中点击 Build,或使用命令行
hvigorw --mode project -p product=default assembleApp -p buildMode=debug --no-daemon
4.2 部署到模拟器
# 通过 DevEco Studio 或 hdc 安装
hdc install products/default/build/default/outputs/default/default-default-signed.hap
4.3 添加桌面卡片
- 长按模拟器桌面空白处 → 选择"服务卡片"
- 找到 “SOS 紧急呼救” 卡片
- 拖动到桌面
- 点击卡片 → App 启动并直接进入 SOS 紧急呼救状态
五、踩坑记录
❌ 坑1:form.src 路径格式
问题:构建时报 Form referenced in the config ... was not found。
原因:form_config.json 中 src 字段的路径必须是相对于 src/main/ 目录的完整路径,且需要包含 ./ets/ 前缀和 .ets 后缀。
正确写法:
"src": "./ets/pages/SOSWidgetPage.ets"
❌ 坑2:@Entry 组件未注册到 main_pages.json
问题:即使 form_config.json 路径正确,构建仍找不到页面。
原因:所有页面(包括卡片页面)都必须在 main_pages.json 的 src 数组中注册。
解决:将 "pages/SOSWidgetPage" 追加到 main_pages.json。
❌ 坑3:AppStorage 的导入方式
问题:ArkTS 检查提示 @kit.ArkUI 没有 AppStorage 导出。
原因:AppStorage 是 ArkUI 框架的内置全局对象,无需显式 import,直接使用即可。
// ❌ 错误
import { AppStorage } from '@kit.ArkUI';
// ✅ 正确
// 无需 import,直接使用
AppStorage.setOrCreate('key', value);
❌ 坑4:@Watch 在冷启动时不触发
问题:@StorageProp 绑定后,@Watch 回调只监听到后续变化,组件初始化时的已有值不会触发。
解决:在 aboutToAppear 中额外检查一次初始值,覆盖冷启动场景。
六、总结
本文从实战角度记录了为 HarmonyOS App 添加 ArkTS 桌面卡片的完整流程。核心收获如下:
| 知识点 | 说明 |
|---|---|
| ArkTS 卡片架构 | @Entry 卡片页面 + FormExtensionAbility + form_config.json 三者缺一不可 |
| 跨模块通信 | AppStorage 是 HAR 与 HAP 之间共享状态的零成本方案 |
| 冷/热启动兼容 | onCreate 处理冷启动,onNewWant 处理热启动,aboutToAppear + @Watch 双保险接收信号 |
| 路由配置 | 卡片页面需同时注册到 form_config.json(src 字段)和 main_pages.json |
桌面卡片的本质是系统级快捷入口。将 SOS 呼救功能做成桌面卡片后,用户从点击到触发紧急状态仅需 1 步 + 0 等待,极大提升了紧急场景下的可用性。
掌握了上述模式,你也可以为自己的 App 快速添加天气卡片、快捷操作卡片、信息摘要卡片等各类 ArkTS 桌面卡片。
更多推荐



所有评论(0)