鸿蒙服务卡片实战:为新华字典应用添加桌面快捷查询卡片

复制代码
# 鸿蒙服务卡片实战:为新华字典应用添加桌面快捷查询卡片

## 一、前言

HarmonyOS 服务卡片(Service Widget)是鸿蒙系统的一大特色功能,它允许应用以卡片形式展示在桌面上,用户无需打开应用即可获取关键信息或快速执行操作。

在上篇文章中,我们实现了一款完整的新华字典鸿蒙应用。本文将继续深入,为应用添加服务卡片支持,使用户可以在桌面直接点击卡片打开字典查询。

---

## 二、服务卡片基础知识

### 2.1 什么是服务卡片

服务卡片是 HarmonyOS 提供的桌面 UI 组件,它本质上是一个轻量级的应用界面,运行在独立的进程中,通过 **FormExtensionAbility** 管理生命周期。

### 2.2 卡片类型

HarmonyOS 的服务卡片分为两种类型:

| 类型 | 交互方式 | 更新方式 | 适用场景 |
|------|---------|---------|---------|
| **静态卡片** | `FormLink` 组件 | 不可动态更新 | 信息展示、快捷入口 |
| **动态卡片** | `postCardAction()` 函数 | 支持定时/事件更新 | 实时信息、动态内容 |

> **关键区别**:静态卡片只能用 `FormLink` 实现点击交互,而动态卡片需要使用 `postCardAction()`。

在本文中,我们实现的是**动态卡片**,因为它支持定时更新数据和更灵活的交互。

### 2.3 卡片生命周期

```
创建 (onAddForm) → 可见 (onChangeFormVisibility) 
  → 更新 (onUpdateForm) → 不可见 → 销毁 (onRemoveForm)
```

---

## 三、开发环境

| 工具 | 版本              |
|------|-----------------|
| OpenHarmony SDK | 23(API 23)      |
| Hvigor | 6.24.2          |
| HDC | 3.2.0d          |
| 目标设备 | HarmonyOS 6.1.0 |
| 开发语言 | ArkTS           |
| UI 框架 | ArkUI           |

---

## 四、卡片架构设计

### 4.1 整体架构

```
┌─────────────────────────────────────┐
│           桌面卡片展示                  │
│  ┌─────────────────────────────────┐ │
│  │  📖 新华字典                     │ │
│  │     查字典                       │ │
│  │   点击打开应用                    │ │
│  └─────────────────────────────────┘ │
│         ↓ onClick                    │
│    postCardAction('router')          │
│         ↓                            │
│    EntryAbility 启动                  │
└─────────────────────────────────────┘
```

### 4.2 文件结构

```
entry/src/main/ets/form/
├── FormAbility.ets       # FormExtensionAbility(卡片生命周期管理)
└── FormWidget.ets        # 卡片页面 UI(ArkUI 组件)

entry/src/main/resources/base/profile/
└── form_config.json      # 卡片配置(尺寸、更新策略等)
```

---

## 五、实现步骤

### 5.1 创建卡片配置文件

首先在 `resources/base/profile/` 下创建 `form_config.json`,描述卡片的元数据:

```json
{
  "forms": [
    {
      "name": "DictionaryForm",
      "displayName": "新华字典",
      "description": "快速查询汉字释义",
      "src": "./ets/form/FormWidget.ets",
      "uiSyntax": "arkts",
      "window": {
        "designWidth": 120,
        "autoDesignWidth": true
      },
      "colorMode": "auto",
      "formConfigAbility": "ability://com.nutpi.myapplication/EntryAbility",
      "formVisibleNotify": true,
      "isDefault": true,
      "updateEnabled": true,
      "scheduledUpdateTime": "10:00",
      "updateDuration": 0,
      "defaultDimension": "1*2",
      "supportDimensions": ["1*2", "2*2"]
    }
  ]
}
```

**关键字段说明:**

| 字段 | 值 | 说明 |
|------|-----|------|
| `uiSyntax` | `"arkts"` | 使用 ArkTS 开发卡片 UI |
| `formVisibleNotify` | `true` | 开启可见性通知(动态卡片) |
| `updateEnabled` | `true` | 允许定时更新 |
| `supportDimensions` | `["1*2", "2*2"]` | 支持的尺寸规格 |
| `defaultDimension` | `"1*2"` | 默认尺寸 |

### 5.2 实现 FormExtensionAbility

FormExtensionAbility 是卡片的后端逻辑,管理卡片的生命周期:

```typescript
import { FormExtensionAbility, formBindingData, formInfo } from '@kit.FormKit';
import { Want } from '@kit.AbilityKit';

export default class DictionaryFormAbility extends FormExtensionAbility {
  /**
   * 卡片被创建时调用,返回卡片初始数据
   */
  onAddForm(want: Want): formBindingData.FormBindingData {
    const formData: Record<string, Object> = {
      'word': '好',
      'pinyin': 'hǎo',
      'radical': '女',
      'strokes': '6',
      'formHint': '点击打开新华字典',
    };
    return formBindingData.createFormBindingData(formData);
  }

  /**
   * 卡片更新时调用(定时更新或主动刷新)
   */
  onUpdateForm(formId: string): void {
    // 可以在这里从网络获取最新数据并更新卡片
  }

  /**
   * 卡片可见性变化时调用
   */
  onChangeFormVisibility(newStatus: Record<string, number>): void {
    // 可见时刷新数据,不可见时释放资源
  }

  /**
   * 卡片被销毁时调用
   */
  onRemoveForm(formId: string): void {
    // 清理资源
  }

  /**
   * 卡片消息事件处理
   */
  onFormEvent(formId: string, message: string): void {
    // 处理来自卡片的消息事件
  }
}
```

**生命周期方法调用时机:**

| 方法 | 调用时机 |
|------|---------|
| `onAddForm` | 用户将卡片添加到桌面时 |
| `onUpdateForm` | 达到定时更新间隔时 |
| `onChangeFormVisibility` | 卡片在桌面可见/不可见时 |
| `onRemoveForm` | 用户从桌面移除卡片时 |
| `onFormEvent` | 卡片内触发 message 事件时 |

### 5.3 实现卡片 UI

卡片页面是一个普通的 ArkUI 组件,用 `@Entry` 和 `@Component` 装饰:

```typescript
@Entry
@Component
struct DictionaryForm {
  build() {
    Column() {
      // 标题区域
      Row() {
        Text('📖').fontSize(20).margin({ right: 6 })
        Text('新华字典')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor('#1a1a2e')
      }
      .margin({ bottom: 8 })

      // 汉字展示
      Text('查字典')
        .fontSize(40)
        .fontWeight(FontWeight.Bold)
        .fontColor('#4a6cf7')
        .margin({ bottom: 4 })

      // 子标题
      Text('点击打开应用')
        .fontSize(10)
        .fontColor('#bbb')
        .margin({ top: 6 })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
    .padding(12)
    // 点击跳转到主应用
    .onClick(() => {
      postCardAction(this, {
        action: 'router',
        abilityName: 'EntryAbility',
        params: {}
      });
    })
  }
}
```

**关键实现细节:**

1. **`postCardAction()`**:这是动态卡片中的全局函数,用于触发交互动作。它有以下几个 action 类型:
   - `"router"`:跳转到指定 UIAbility
   - `"message"`:发送消息到 FormExtensionAbility 的 `onFormEvent`
   - `"call"`:在后台启动 UIAbility

2. **`this` 上下文**:`postCardAction` 的第一个参数是当前组件的 `this` 上下文。

### 5.4 注册卡片到模块配置

在 `module.json5` 中注册 FormExtensionAbility:

```json5
{
  "extensionAbilities": [
    {
      "name": "DictionaryFormAbility",
      "srcEntry": "./ets/form/FormAbility.ets",
      "label": "$string:EntryAbility_label",
      "description": "新华字典服务卡片",
      "type": "form",
      "exported": true,
      "metadata": [
        {
          "name": "ohos.extension.form",
          "resource": "$profile:form_config"
        }
      ]
    }
  ]
}
```

**重要参数:**

| 参数 | 说明 |
|------|------|
| `type: "form"` | 声明此 ExtensionAbility 是服务卡片类型 |
| `metadata` | 引用 `ohos.extension.form` 类型,指向卡片配置 |

---

## 六、FormLink vs postCardAction

这是开发中最容易踩坑的地方,必须根据卡片类型选择正确的交互方式:

| | 静态卡片 | 动态卡片 |
|------|---------|---------|
| **配置特征** | `updateEnabled: false` | `updateEnabled: true` 或 `formVisibleNotify: true` |
| **交互方式** | `FormLink` 组件 | `postCardAction()` 函数 |
| **数据更新** | 不支持运行时更新 | 支持定时/事件更新 |
| **适用场景** | 简单信息展示 | 实时数据展示 |

**区分方法:** 只要 `form_config.json` 中设置了 `updateEnabled: true` 或 `formVisibleNotify: true`,卡片就是动态的,必须使用 `postCardAction`。

如果使用了错误的交互方式,点击卡片将没有任何反应。

---

## 七、遇到的坑与解决方案

### 坑 1:FormLink 点击无反应

**现象**:使用 `FormLink` 组件包裹卡片内容后,点击卡片没有任何跳转

**原因**:卡片配置了 `formVisibleNotify: true`,被识别为**动态卡片**,但 `FormLink` 只适用于**静态卡片**

**解决**:将 `FormLink` 替换为 `Column.onClick` + `postCardAction()`

```typescript
// ❌ 静态卡片方式(动态卡片不生效)
FormLink({ action: 'router', abilityName: 'EntryAbility', params: {} }) {
  // 卡片内容
}

// ✅ 动态卡片方式
Column() {
  // 卡片内容
}
.onClick(() => {
  postCardAction(this, {
    action: 'router',
    abilityName: 'EntryAbility',
    params: {}
  });
})
```

### 坑 2:卡片配置 Schema 校验失败

**现象**:构建时 hvigor 报错 `must have required property 'updateEnabled'`

**原因**:卡片配置缺少 `updateEnabled` 字段,这是必填项

**解决**:在 `form_config.json` 中添加 `"updateEnabled": true`

### 坑 3:@Entry 缺少参数警告

**现象**:编译警告 `@Entry should have a parameter, like '@Entry (storage)'`

**原因**:卡片页面中 `@Entry` 装饰器缺少 `LocalStorage` 参数

**解决**:对于不需要 LocalStorage 的简单卡片,可以忽略此警告;如果需要在卡片中使用状态数据,可以添加 `@Entry({ storage: formStorage })`。

---

## 八、构建与部署

### 8.1 构建

```bash
hvigorw assembleHap --mode module -p product=default -p buildMode=debug
```

### 8.2 安装

```bash
hdc install entry/build/default/outputs/default/entry-default-signed.hap
```

### 8.3 在桌面添加卡片

安装后,在设备桌面操作:

1. 长按桌面空白区域
2. 选择「服务卡片」
3. 找到「新华字典」
4. 点击添加到桌面

---

## 九、项目心得

1. **卡片类型决定交互方式**:开发前需要先明确卡片是静态还是动态,选错交互方式会导致功能失效。

2. **卡片 UI 受限制**:服务卡片只支持有限的 ArkUI 组件(Column、Row、Text、Image、Button 等),不支持 Scroll、List 等复杂组件。设计卡片 UI 时需要注意这一点。

3. **Card dimensions**:卡片尺寸使用网格单位(1×2、2×2、2×4 等),设计时要考虑不同尺寸下的布局适配。

4. **动态更新机制**:动态卡片可以通过 `onUpdateForm` 定时更新数据,也可以通过 `formProvider` 主动推送更新。

5. **postCardAction 的三种 action**:
   - `router`:最常用,用户点击后跳转到应用
   - `message`:通过 `onFormEvent` 回调处理,适合不需要打开应用的轻量操作
   - `call`:后台启动,适合执行不需要界面的任务

---

## 十、总结

本文详细介绍了为鸿蒙新华字典应用添加服务卡片的完整过程,从卡片配置、生命周期管理、UI 实现到交互处理。

核心要点:
- 动态卡片用 `postCardAction`,静态卡片用 `FormLink`
- `form_config.json` 中 `updateEnabled` 和 `formVisibleNotify` 决定卡片类型
- `FormExtensionAbility` 管理卡片生命周期
- 卡片 UI 受限于有限的 ArkUI 组件集

---

## 项目地址

本文所有代码已开源:[https://gitcode.com/jianguoxu/myapplication](https://gitcode.com/jianguoxu/myapplication)
相关推荐
Davina_yu3 小时前
弹窗交互:AlertDialog与CustomDialog的创建与关闭(11)
harmonyos·鸿蒙·鸿蒙系统
90后的晨仔3 小时前
HarmonyOS 锁屏音频播放完整实践指南
harmonyos
90后的晨仔3 小时前
鸿蒙应用动态桌面图标功能实现完全指南
harmonyos
nashane4 小时前
HarmonyOS 6学习:JsCrash“闪退”法医指南——从FaultLog堆栈还原崩溃现场的终极手册
学习·华为·harmonyos
李二。5 小时前
鸿蒙OS NEXT 批量重命名工具:PC端文件管理的效率革命
华为·harmonyos
HwJack206 小时前
鸿蒙背景下 Cocos Creator 的三大 JS 引擎:JIT 与热更新的十字路口
javascript·华为·harmonyos
提子拌饭1336 小时前
Column 嵌套布局:多级 Column 实现复杂纵向结构——鸿蒙 HarmonyOS ArkTS 原生学习应用
学习·华为·harmonyos·鸿蒙·鸿蒙系统
前端不太难8 小时前
鸿蒙 App 分布式数据同步:架构设计 + Demo 实现
分布式·状态模式·harmonyos