在 HarmonyOS 应用开发中,FormKit 为开发者提供了构建桌面卡片的强大能力。很多人以为卡片 UI 只能做简单的信息展示,其实它的潜力远超想象。本文将深入剖析三种卡片玩法——普通卡片、动效卡片和 Canvas 自定义绘制卡片,并结合实际代码拆解实现细节、适用场景与避坑指南。无论你是刚入门 Go 或 TypeScript,还是熟悉 Python 或 C++ 的跨平台开发者,都能从中找到实用技巧。
三种卡片类型对比:一张表看懂差异
在动手编码之前,先通过一张总览表理解三种卡片的本质区别。它们可以在同一个应用中共存,通过 form_config.json 分别注册即可。
| 类型 | 典型场景 | 关键技术 | 性能开销 |
|---|---|---|---|
| 普通卡片 | 文本展示 + 点击跳转 | 最低 | |
| 动效卡片 | 按钮旋转、图标跳动 | + | 低 |
| Canvas 卡片 | 自定义图形、数据可视化 | 中等 |

从表中可以看出,普通卡片侧重数据展示与简单交互,动效卡片在此基础上增加视觉反馈,而 Canvas 卡片则提供了完全自由的绘制能力。选择哪种,取决于你的具体业务需求。
普通卡片:最常用的基础款
普通卡片的核心使命只有两件事:展示数据 和 点击跳转。实现起来非常直接,看 WidgetCard.ets 的代码片段:
@Entry
@Component
struct WidgetCard {
// 这三个是固定动作参数,实际项目里建议提取到配置文件
readonly ACTION_TYPE: string = 'router';
readonly ABILITY_NAME: string = 'EntryAbility';
readonly MESSAGE: string = 'add detail';
// 卡片标题,由 FormExtensionAbility 通过 FormBindingData 传入
@LocalStorageProp('title') title: ResourceStr = $r('app.string.widget_title');
build() {
Row() {
Column() {
Text(this.title)
.fontSize($r('app.float.font_size'))
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
.padding({ left: 12, top: 12 })
}
.width('100%')
}
.height('100%')
.backgroundColor('#1E90FF')
.borderRadius(12)
.onClick(() => {
// postCardAction 是卡片与外部通信的唯一途径
postCardAction(this, {
'action': this.ACTION_TYPE, // 'router' 表示路由跳转
'abilityName': this.ABILITY_NAME, // 目标 Ability 名称
'params': {
'message': this.MESSAGE // 传给目标 Ability 的参数
}
});
})
}
}
这里的 postCardAction 的 action 字段支持三种值:
'router':跳转到指定 Ability,最常用场景,比如点击天气卡片打开天气应用。'message':发送消息给FormExtensionAbility侧,触发onFormEvent回调,适合需要与后台通信的交互。'call':调用应用后台的 Ability,不拉起前台 UI,用于静默更新数据。
适用场景:天气、待办事项、步数统计等“看一眼就够了”的信息展示。普通卡片开发成本最低,性能最好,是大多数情况下的首选。
⚠️ 注意事项:虽然普通卡片支持点击跳转,但不适合复杂交互逻辑。如果需要按钮点击反馈或动态刷新,建议考虑动效卡片。
动效卡片:让卡片动起来
动效卡片与普通卡片写法几乎一致,核心差异在于引入了动画逻辑。关键限制:卡片内支持属性动画(.animation()),但不支持显式动画(animateTo())。看 AnimationCard.ets 的示例:
@Entry
@Component
struct AnimationCard {
// 用 @State 管理旋转角度
@State rotateAngle: number = 0;
build() {
Row() {
Button('点我旋转')
.width(120)
.height(40)
.fontSize(14)
.fontColor(Color.White)
.backgroundColor('#FF6B35')
// 绑定旋转属性——当 rotateAngle 变化时自动触发动画
.rotate({ angle: this.rotateAngle })
// 配置动画效果
.animation({
curve: Curve.EaseOut, // 缓出曲线,减速停止
playMode: PlayMode.Normal, // 正向播放
duration: 300, // 300ms
})
.onClick(() => {
// 切换角度,触发动画
this.rotateAngle = (this.rotateAngle === 0 ? 90 : 0);
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(VerticalAlign.Center)
.backgroundColor('#2C2C2C')
}
}
工作原理其实很简单:
用户点击按钮
↓
rotateAngle 值改变(0 → 90 或 90 → 0)
↓
ArkUI 检测到绑定了 .animation() 的属性变化
↓
自动播放从旧值到新值的过渡动画
卡片内可动效的属性包括:
.rotate()— 旋转角度.scale()— 缩放比例.opacity()— 透明度渐变.translate()— 位置偏移.backgroundColor()— 背景色过渡
❌ 不能使用的动画方式:
animateTo()— 显式动画,卡片运行时环境中不支持@AnimationGroup— 同样受限
适用场景:钟表秒针旋转、倒计时数字变化、状态切换按钮(如播放/暂停)等需要视觉反馈的场景。动效卡片代码量增加不多,但效果立竿见影。
如果你有 TypeScript 或 JavaScript 动画经验,会发现 HarmonyOS 的动画 API 设计非常直观,迁移成本很低。
Canvas 卡片:想画啥画啥
Canvas 卡片使用了 CanvasRenderingContext2D 组件。如果你写过 HTML5 Canvas 或 Python 的 turtle 库,会感到非常亲切——API 几乎如出一辙。在 CanvasCard.ets 中绘制一个笑脸,完整代码如下:
@Entry
@Component
struct CanvasCard {
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
// 记录画布尺寸(在 onReady 里获取)
private canvasWidth: number = 0;
private canvasHeight: number = 0;
build() {
Column() {
Canvas(this.context)
.width('100%')
.height('100%')
// onReady 回调是绘制的入口,在这里才能拿到真实的画布尺寸
.onReady(() => {
this.canvasWidth = this.context.width;
this.canvasHeight = this.context.height;
this.drawSmiley();
})
}
.width('100%')
.height('100%')
}
// 绘制笑脸
private drawSmiley(): void {
const cx = this.canvasWidth / 2;
const cy = this.canvasHeight / 2;
const r = Math.min(this.canvasWidth, this.canvasHeight) * 0.35;
// 1. 绘制背景
this.context.fillStyle = '#EEF0FF';
this.context.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
// 2. 绘制脸部圆形
this.context.beginPath();
this.context.arc(cx, cy, r, 0, 2 * Math.PI);
this.context.fillStyle = '#FFD700';
this.context.fill();
this.context.strokeStyle = '#FF8C00';
this.context.lineWidth = 3;
this.context.stroke();
// 3. 绘制左眼
this.context.beginPath();
this.context.arc(cx - r * 0.3, cy - r * 0.25, r * 0.1, 0, 2 * Math.PI);
this.context.fillStyle = '#333333';
this.context.fill();
// 4. 绘制右眼
this.context.beginPath();
this.context.arc(cx + r * 0.3, cy - r * 0.25, r * 0.1, 0, 2 * Math.PI);
this.context.fillStyle = '#333333';
this.context.fill();
// 5. 绘制笑嘴(弧线)
this.context.beginPath();
this.context.arc(cx, cy + r * 0.05, r * 0.45, 0.1 * Math.PI, 0.9 * Math.PI);
this.context.strokeStyle = '#333333';
this.context.lineWidth = 4;
this.context.stroke();
}
}
Canvas 绘制流程如下:

⚠️ 一个重要的坑:onReady 只会在组件初次布局完成时触发一次。如果你需要响应数据变化重新绘制,必须在数据更新后手动调用绘制函数。Canvas 卡片无法像普通组件那样依赖 @State 自动刷新,所有绘制逻辑都需要显式控制。
适用场景:
- 数据可视化:折线图、环形进度条、雷达图等
- 自定义图标:无法用标准组件实现的复杂图形
- 游戏或动画:需要逐帧控制的图形展示
对于熟悉 C++ 或 Go 中图形库的开发者,Canvas 卡片提供了类似的底层控制能力,但开发成本也最高。
[AFFILIATE_SLOT_1]三种卡片的选用建议
根据实际项目经验,给出以下建议:
- 大多数场景:用普通卡片就够了,开发成本最低,性能最好。
- 需要视觉反馈:按钮点击、状态切换时用动效卡片,代码量增加不多,效果明显。
- 需要自定义图形或数据可视化:才考虑 Canvas 卡片,自由度最大但开发成本最高。
别为了“高端”而强行用 Canvas。普通卡片配上合适的图标和颜色,照样能做出好看的效果。例如,一个天气卡片用渐变背景加简洁图标,体验完全不输复杂绘制。
此外,如果你有跨平台开发经验(如用 Python 写原型、用 TypeScript 写前端),HarmonyOS 的卡片开发模式会让你快速上手。建议先从普通卡片开始,逐步尝试动效和 Canvas,循序渐进。
[AFFILIATE_SLOT_2]总结
HarmonyOS 卡片 UI 的三种玩法各有侧重:普通卡片胜在简单高效,动效卡片提升交互体验,Canvas 卡片实现无限创意。理解它们的差异和适用场景,能帮你用最低成本打造出优秀的桌面体验。记住:技术选型永远是“适合”优于“炫技”。
postCardAction@State.animation()CanvasRenderingContext2D
浙公网安备 33010602011771号