HarmonyOS技术精讲-Form Kit(卡片开发服务)第2篇:搭建ArkTS卡片开发环境与创建第一个卡片

先搞清楚:卡片开发要解决什么问题
卡片的核心价值是“在桌面或负一屏,以轻量级、低功耗的方式,快速呈现用户关心的信息”。它不是应用主界面的替代品,而是应用核心功能的精华摘录。
适用场景:
- 天气、时钟、日历等需要实时刷新信息的桌面小组件。
- 音乐播放器的快捷控制面板。
- 待办事项、记步数等无需打开应用即可查看的信息卡片。
不适用场景:
- 需要复杂用户交互(如多级页面跳转)的场景。
- 对性能要求极高,需要大量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卡片项目,核心文件包括:
form_config.json:卡片配置信息,决定了卡片的外观(尺寸、是否可刷新等)。FormAbility.ets:卡片生命周期管理入口,负责监听卡片的创建、更新、销毁等事件。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动作,这表示它可以在桌面显示。
第七步:运行和调试
在模拟器或真机上运行你的应用。应用安装成功后,不要直接点击应用图标。正确的做法是:
- 回到桌面。
- 长按桌面空白处,点击“服务卡片”。
- 在卡片列表中找到你的应用,然后选择“HelloCard”。
- 卡片就会被添加到桌面。
如果卡片没有出现,检查:
- 编译是否成功,控制台有无报错。
module.json5配置是否正确。- 模拟器是否支持卡片功能(大部分模拟器是支持的)。
真正有价值的踩坑
坑1:卡片UI更新不生效
现象:在FormAbility的onUpdate回调中,通过widget.getWidget()获取Widget对象并调用updateForm,但UI没有任何变化。
原因:onUpdate方法运行在卡片的生命周期线程中,并不是UI线程。直接在该线程里调用UI更新API是不被允许的,或者调用时机不对。
解决方案:正确的做法是在FormAbility中通过widget模块的updateFormByKeyValue或updateForm方法来异步更新。而且,确保你已经正确获取了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机制。
解决方案:将需要持久化的状态写入到LocalStorage或AppStorage中,在卡片初始化时恢复。
// 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实例。
最佳实践
- 不要在
build()中创建复杂对象:build()方法在UI需要刷新时会被频繁调用。如果里面创建了复杂的对象或进行了耗时计算,会直接拖垮UI性能,导致掉帧。尽量将这类操作放到@State变量变化时的回调或FormAbility的onUpdate中。 - 优先使用
updateFormByKeyValue:相比updateForm,前者是更细粒度的更新。你只需要传入改变的状态值,框架会帮你完成diff,从而减少不必要的UI重绘。 - 合理设置
updateDuration:不要为了实时性把这个值设成1或0,系统会忽略。请根据你的业务场景选择合理的更新周期,比如2(2小时)或4(4小时)。如果要求更高频率,考虑使用其他后台同步方案。
FAQ
Q:为什么我在模拟器上看到卡片是空白的?
A:最常见的原因是card.ets文件中没有正确导出@Entry装饰器,或者form_config.json里的src路径写错了。另外,检查模拟器是否成功创建了卡片,有时候模拟器会崩溃导致卡片无法加载。
Q:为什么onCreate里拿到的want参数是空的?
A:这在早期版本的DevEco Studio中比较常见。通常是由于want的序列化出现问题。可以尝试在module.json5中给FormAbility添加launchType为singleton,但这并不是一个根治的方法。如果遇到,建议升级DevEco Studio和SDK到最新版。
Q:卡片可以添加列表吗?可以异步加载图片吗?
A:可以。ArkTS卡片完全支持List、Grid等复杂布局,也支持通过Image组件异步加载网络图片,但需要配置networkAccess权限。性能优化是关键,不要在卡片主线程中做过多网络操作,否则容易导致卡片卡死。
示例代码地址:项目地址
更多推荐


所有评论(0)