在这里插入图片描述

先搞清楚:卡片开发要解决什么问题

卡片的核心价值是“在桌面或负一屏,以轻量级、低功耗的方式,快速呈现用户关心的信息”。它不是应用主界面的替代品,而是应用核心功能的精华摘录。

适用场景:

  • 天气、时钟、日历等需要实时刷新信息的桌面小组件。
  • 音乐播放器的快捷控制面板。
  • 待办事项、记步数等无需打开应用即可查看的信息卡片。

不适用场景:

  • 需要复杂用户交互(如多级页面跳转)的场景。
  • 对性能要求极高,需要大量GPU渲染的效果。

ArkTS卡片是当前推荐的开发方式,相比JS卡片,它拥有更好的开发体验和性能表现。如果你还在用老的JS卡片方案,建议尽快迁移。

环境说明

  • DevEco Studio 版本:DevEco Studio 6.1.0 及以上
  • HarmonyOS SDK 版本:HarmonyOS 6.1.0(23) 及以上
  • 目标设备:手机、平板(推荐使用模拟器或真机进行调试)

注意:如果你使用的是早期版本的DevEco Studio,可能无法找到卡片模板,或者创建成功后编译报错。确保你的开发环境是最新的,能省去很多麻烦。

核心实现:从零创建你的第一个ArkTS卡片

第一步:选择正确的项目模板

打开DevEco Studio,创建一个新项目。这里有个常见的坑:很多人直接选择“Empty Ability”模板,然后手动往里面加卡片代码,结果发现配置死活不对。

正确做法是:选择“Empty Ability”模板后,在“Device”选项卡里,务必勾选“Show in Service Center”和“Widget”。

勾选“Widget”后,IDE会自动在工程里为你生成卡片相关的代码和资源配置文件,这一步能节省大量手动配置的时间。项目创建成功后的目录结构里,你会看到 entry/src/main/ets/FormAbility 这个文件夹,里面就是我们操作卡片的核心文件。

第二步:理解卡片模块的文件结构

一个标准的ArkTS卡片项目,核心文件包括:

  1. form_config.json:卡片配置信息,决定了卡片的外观(尺寸、是否可刷新等)。
  2. FormAbility.ets:卡片生命周期管理入口,负责监听卡片的创建、更新、销毁等事件。
  3. card.ets:卡片的UI界面,使用ArkTS编写。

第三步:编写第一个卡片UI(card.ets)

这是你最关心的部分。我们写一个最简单的“Hello World”卡片,包含一个文本和一个按钮(但注意:卡片内按钮只能触发点击事件,不能直接做跳转)。

// entry/src/main/ets/FormAbility/pages/card.ets
@Entry
@Component
struct CardWidget {
  @State message: string = 'Hello HarmonyOS Card!'

  // 点击事件回调
  onWidgetClick() {
    this.message = 'You clicked me!';
  }

  build() {
    // 这里用了一个栈容器,让文字居中显示
    Stack() {
      Column() {
        Text(this.message)
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')
          .textAlign(TextAlign.Center)
      }
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#007DFF') // 卡片背景色
    // 绑定点击事件
    .onClick(() => {
      this.onWidgetClick();
    })
  }
}

代码说明

  • @Entry@Component是ArkTS组件的基本装饰器,没什么好说的。
  • @State message是卡片的状态变量,当它改变时,UI会自动更新。这一点非常关键,它决定了卡片能否实时响应用户操作或数据更新。
  • 卡片内部的onClick事件是可行的,但注意它只能执行简单的状态切换或调用postCardAction(用于向应用发送消息),不能做页面跳转。

第四步:配置卡片生命周期入口(FormAbility.ets)

IDE生成的FormAbility.ets已经包含了基本框架,但为了让卡片能够正确运行,我们需要理解并修改几个关键回调。

// entry/src/main/ets/FormAbility/FormAbility.ets
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import hilog from '@ohos.hilog';
import window from '@ohos.window';
import Want from '@ohos.app.ability.Want';

export default class FormAbility extends AbilityConstant.Ability {
  // 卡片创建时调用
  onCreate(want: Want, abilityParam: AbilityConstant.AbilityStartParams) {
    hilog.info(0x0000, 'testTag', '%{public}s', 'FormAbility onCreate');
    // 这里可以对卡片进行初始化设置,比如从网络拉取数据
  }

  // 当卡片需要更新时调用(比如设置了定时刷新)
  onUpdate(formId: string) {
    hilog.info(0x0000, 'testTag', '%{public}s', 'FormAbility onUpdate');
    // 更新卡片数据
    this.updateForm(formId);
  }

  // 卡片被销毁时调用
  onDestroy(formId: string) {
    hilog.info(0x0000, 'testTag', '%{public}s', 'FormAbility onDestroy');
  }

  // 这是一个自定义方法,用于更新卡片UI
  // 实际开发中,需要在这里调用 widget.getWidget().updateForm() 来更新卡片数据
  // 但本示例为了简化,不做数据更新
  private updateForm(formId: string) {
    // 更新代码
  }
}

重点onCreate方法里拿到的want参数里包含了卡片ID、卡片名称等信息。在onUpdate回调里,你需要通过updateForm方法来刷新卡片UI。但很多初学者会发现,在onUpdate里直接调用widget对象会报错,因为这个对象需要在UI线程中获取。这个问题我们在踩坑章节细说。

第五步:配置卡片信息(form_config.json)

这个文件告诉系统,你的卡片长什么样,有什么行为。

{
  "forms": [
    {
      "name": "HelloCard",
      "description": "这是第一个卡片",
      "src": "./pages/card.ets", // 指向我们的UI文件
      "window": {
        "designWidth": 720, // 设计稿宽度
        "autoDesignWidth": true
      },
      "formConfig": {
        "landscapeLayout": "default", // 横屏布局
        "portraitLayout": "default"     // 竖屏布局
      },
      "updateEnabled": true, // 允许卡片定时更新
      "scheduledUpdateTime": "10:30", // 每天10:30更新(需要配合updateDuration使用)
      "updateDuration": 2, // 更新周期,单位小时,最少2小时
      "defaultDimension": "2*2", // 卡片尺寸,2x2 网格
      "supportDimensions": ["2*2"] // 支持的尺寸
    }
  ]
}

踩坑点updateDuration的最小值是2,即最少每2小时更新一次。如果你想实现更快的刷新(比如天气卡片每分钟刷新),这种卡片上的定时刷新机制是达不到的。你需要用到coprocessor(协处理器)或者应用后台跑到前台时手动更新。

第六步:在module.json5中声明卡片

这一步很多人会忽略,导致卡片在桌面上找不到。在entry/src/main/module.json5文件中,你需要将FormAbility声明进去。

{
  "module": {
    // ... 其他配置
    "abilities": [
      {
        "name": "FormAbility",
        "srcEntry": "./ets/FormAbility/FormAbility.ets",
        "description": "卡片能力",
        "icon": "$media:icon",
        "label": "$string:entry_FormAbility",
        "startWindowIcon": "$media:icon",
        "startWindowBackground": "$color:start_window_background",
        "visible": true,
        "skills": [
          {
            "entities": [
              "entity.system.home"
            ],
            "actions": [
              "action.system.home"
            ]
          }
        ]
      }
    ]
  }
}

关键点在于"visible": true"skills"字段。skills定义了该Ability可以处理action.system.home动作,这表示它可以在桌面显示。

第七步:运行和调试

在模拟器或真机上运行你的应用。应用安装成功后,不要直接点击应用图标。正确的做法是:

  1. 回到桌面。
  2. 长按桌面空白处,点击“服务卡片”。
  3. 在卡片列表中找到你的应用,然后选择“HelloCard”。
  4. 卡片就会被添加到桌面。

如果卡片没有出现,检查:

  • 编译是否成功,控制台有无报错。
  • module.json5配置是否正确。
  • 模拟器是否支持卡片功能(大部分模拟器是支持的)。

真正有价值的踩坑

坑1:卡片UI更新不生效

现象:在FormAbilityonUpdate回调中,通过widget.getWidget()获取Widget对象并调用updateForm,但UI没有任何变化。

原因onUpdate方法运行在卡片的生命周期线程中,并不是UI线程。直接在该线程里调用UI更新API是不被允许的,或者调用时机不对。

解决方案:正确的做法是在FormAbility中通过widget模块的updateFormByKeyValueupdateForm方法来异步更新。而且,确保你已经正确获取了widget对象。

// 正确的更新方式
import widget from '@ohos.arkui.widget';

export default class FormAbility extends AbilityConstant.Ability {
  onUpdate(formId: string) {
    // 构造新的数据
    let formData = {
      "message": "更新后的文本" // 这里的key必须与card.ets中@State变量名对应
    };
    // 异步更新
    widget.updateForm(formId, {"formData": JSON.stringify(formData)}, (err, data) => {
      if (err) {
        hilog.error(0x0000, 'testTag', 'Failed to update form. Cause: %{public}s', JSON.stringify(err));
      } else {
        hilog.info(0x0000, 'testTag', 'Succeeded in updating form. Data: %{public}s', JSON.stringify(data));
      }
    });
  }
}

关键点formData的key必须和card.ets中的@State状态变量名完全一致,否则UI不会刷新。这是ArkTS卡片状态同步的机制,官方文档虽然提了,但没强调这个一致性要求,很多人踩坑。

坑2:卡片状态在返回后丢失

现象:从应用跳转到卡片,滑动一下卡片,再回到桌面,卡片的message状态又变回了'Hello HarmonyOS Card!'

原因:卡片的@State状态是存放在内存中的,当卡片被销毁或资源回收后,状态就丢失了。卡片不像应用有完整的saveState机制。

解决方案:将需要持久化的状态写入到LocalStorageAppStorage中,在卡片初始化时恢复。

// card.ets
@Entry
@Component
struct CardWidget {
  @State message: string = AppStorage.get('cardMessage') || 'Hello HarmonyOS Card!';

  onWidgetClick() {
    this.message = 'You clicked me!';
    AppStorage.set('cardMessage', this.message);
  }
}

注意AppStorage的读写虽然简单,但它是一个全局存储,不同卡片之间的数据可能会污染。更推荐的做法是使用LocalStorage,在FormAbility创建卡片时为每个卡片创建一个独立的LocalStorage实例。

最佳实践

  1. 不要在build()中创建复杂对象build()方法在UI需要刷新时会被频繁调用。如果里面创建了复杂的对象或进行了耗时计算,会直接拖垮UI性能,导致掉帧。尽量将这类操作放到@State变量变化时的回调或FormAbilityonUpdate中。
  2. 优先使用updateFormByKeyValue:相比updateForm,前者是更细粒度的更新。你只需要传入改变的状态值,框架会帮你完成diff,从而减少不必要的UI重绘。
  3. 合理设置updateDuration:不要为了实时性把这个值设成10,系统会忽略。请根据你的业务场景选择合理的更新周期,比如2(2小时)或4(4小时)。如果要求更高频率,考虑使用其他后台同步方案。

FAQ

Q:为什么我在模拟器上看到卡片是空白的?
A:最常见的原因是card.ets文件中没有正确导出@Entry装饰器,或者form_config.json里的src路径写错了。另外,检查模拟器是否成功创建了卡片,有时候模拟器会崩溃导致卡片无法加载。

Q:为什么onCreate里拿到的want参数是空的?
A:这在早期版本的DevEco Studio中比较常见。通常是由于want的序列化出现问题。可以尝试在module.json5中给FormAbility添加launchTypesingleton,但这并不是一个根治的方法。如果遇到,建议升级DevEco Studio和SDK到最新版。

Q:卡片可以添加列表吗?可以异步加载图片吗?
A:可以。ArkTS卡片完全支持ListGrid等复杂布局,也支持通过Image组件异步加载网络图片,但需要配置networkAccess权限。性能优化是关键,不要在卡片主线程中做过多网络操作,否则容易导致卡片卡死。

示例代码地址:项目地址

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐