news 2026/6/28 5:45:03

HarmonyOS7 桌面卡片怎么从 0 到 1?Widget 小组件开发全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HarmonyOS7 桌面卡片怎么从 0 到 1?Widget 小组件开发全流程

文章目录

    • 前言
    • FormExtensionAbility:卡片的"幕后大脑"
    • 配置卡片的元数据
    • 卡片 UI:ArkTS 写法和普通页面不太一样
    • 处理卡片的交互事件
    • 数据更新:定时 + 事件驱动双保险
    • 实战:日程卡片
    • 几点踩坑经验

前言

桌面卡片(Widget)是 HarmonyOS 里特别讨喜的一个功能。用户不用打开 App 就能在桌面上看到关键信息——天气、日程、股票行情,一眼就够。对开发者来说,这也是一个很好的 App 曝光入口。

但卡片的开发跟普通页面有不少区别,特别是数据更新机制和交互方式。今天咱们从零搭一个天气卡片 + 日程卡片,把整个流程跑通。

FormExtensionAbility:卡片的"幕后大脑"

每个卡片背后都有一个FormExtensionAbility,它就是卡片的服务端。系统通过这个 Ability 来管理卡片的生命周期——创建、更新、销毁。

先看看它的核心回调:

import{formBindingData,FormExtensionAbility}from'@kit.FormKit';exportclassWeatherFormAbilityextendsFormExtensionAbility{// 卡片被添加到桌面时触发onAddForm(want:Want){constformId=want.parameters?.['formId']asstring;console.info(`卡片创建:${formId}`);returnthis.buildFormData(formId);}// 卡片需要更新时触发(定时/手动)onUpdateForm(formId:string){console.info(`卡片更新:${formId}`);returnthis.buildFormData(formId);}// 卡片被删除时触发onRemoveForm(formId:string){console.info(`卡片删除:${formId}`);}// 卡片从不可见变为可见时触发onCastToNormalForm(formId:string){returnthis.buildFormData(formId);}privatebuildFormData(formId:string):formBindingData.FormBindingData{constdata={temperature:'26°C',weather:'晴',city:'杭州',updateTime:newDate().toLocaleTimeString()};returnformBindingData.createFormBindingData(data);}}

几个关键的生命周期节点要搞清楚:

  • onAddForm:用户把卡片拖到桌面上时调用,返回初始数据
  • onUpdateForm:定时触发或手动触发的数据更新
  • onRemoveForm:用户删除卡片时调用,做清理工作
  • onCastToNormalForm:临时卡片转为常态卡片时调用(比如卡片从后台回前台)

配置卡片的元数据

卡片的大小、更新频率这些信息要在form_config.json里配置:

{"forms":[{"name":"WeatherForm","displayName":"天气卡片","description":"实时天气信息","src":"./ets/entryformability/EntryFormAbility.ets","uiSyntax":"arkts","window":{"designWidth":720,"autoDesignWidth":true},"colorMode":"auto","isDynamic":true,"isDefault":true,"updateEnabled":true,"scheduledUpdateTime":"08:00","updateDuration":1,"defaultDimension":"2*2","supportDimensions":["2*2","2*4","4*4"]}]}

几个参数要注意:

  • isDynamic: true:启用动态卡片(ArkTS 卡片),支持交互
  • updateEnabled: true:开启定时更新
  • scheduledUpdateTime:定时更新时间,系统会在接近这个时间时触发更新
  • supportDimensions:卡片支持的尺寸,多配几个让用户有选择

卡片 UI:ArkTS 写法和普通页面不太一样

卡片 UI 用的是 ArkTS,但跟普通页面有一些限制——不能用弹窗、不能用动画、组件类型也有限制。不过常用的 Text、Image、Column、Row、Button 都有。

来写天气卡片的 UI:

// WeatherFormWidget.ets@Entry@Componentstruct WeatherFormWidget{@StorageProp('temperature')temperature:string='--°C';@StorageProp('weather')weather:string='加载中';@StorageProp('city')city:string='';@StorageProp('updateTime')updateTime:string='';build(){Column(){Row(){Image($r('app.media.weather_sunny')).width(40).height(40)Column(){Text(this.city).fontSize(14).fontColor('#666666')Text(this.temperature).fontSize(36).fontWeight(FontWeight.Bold).fontColor('#333333')}.margin({left:12})}.width('100%')Row(){Text(this.weather).fontSize(14).fontColor('#888888')Blank()Text(`更新于${this.updateTime}`).fontSize(12).fontColor('#AAAAAA')}.width('100%').margin({top:12})// 按钮:点击刷新天气Button('刷新天气').fontSize(12).height(28).width(80).margin({top:8}).onClick(()=>{// 通过 postCardAction 发送事件给 FormExtensionAbilitypostCardAction(this,{action:'message',params:{action:'refresh'}});})}.width('100%').height('100%').padding(16).backgroundColor('#FFFFFF').borderRadius(16)}}

注意@StorageProp装饰器——它从formBindingData传过来的数据里取值。postCardAction则是卡片 UI 跟 FormExtensionAbility 通信的桥梁。

处理卡片的交互事件

卡片里点击按钮后,事件会传到 FormExtensionAbility 的onFormEvent回调:

exportclassWeatherFormAbilityextendsFormExtensionAbility{// ... 其他回调onFormEvent(formId:string,message:string){constparams=JSON.parse(message)asRecord<string,string>;if(params['action']==='refresh'){// 模拟获取最新天气数据constnewData=this.fetchWeatherData();constformData=formBindingData.createFormBindingData(newData);this.context.updateForm(formId,formData);}}privatefetchWeatherData():Record<string,string>{// 实际项目中这里应该调网络接口return{temperature:`${Math.floor(Math.random()*15+20)}°C`,weather:['晴','多云','阴','小雨'][Math.floor(Math.random()*4)],city:'杭州',updateTime:newDate().toLocaleTimeString()};}}

卡片支持的交互方式有限,主要是通过postCardAction发送message事件。不能直接调异步接口——onFormEvent里不能写async/await,所以网络请求需要换个方式处理。

数据更新:定时 + 事件驱动双保险

卡片的数据更新有两种方式:

定时更新由系统控制,在form_config.json里配好scheduledUpdateTime就行,系统到点会调onUpdateForm。但别指望精确到分钟——系统会综合考虑电量和性能,可能延迟触发。

事件驱动适合需要实时更新的场景,比如日程提醒快到了:

// 在 EntryAbility 或其他地方主动触发卡片更新import{formProvider}from'@kit.FormKit';asyncfunctionrefreshAllForms(context:Context){try{// 获取所有活跃的卡片constformInfos=awaitformProvider.getFormsInfo(context);for(constformofformInfos){if(form.name==='WeatherForm'){constdata=formBindingData.createFormBindingData({temperature:'28°C',weather:'多云转晴',city:'杭州',updateTime:newDate().toLocaleTimeString()});awaitformProvider.updateForm(form.formId,data);}}}catch(err){console.error(`刷新卡片失败:${JSON.stringify(err)}`);}}

实战:日程卡片

再写一个日程卡片,支持显示今日待办和点击标记完成:

// ScheduleFormWidget.etsinterfaceScheduleItem{id:string;title:string;time:string;done:boolean;}@Entry@Componentstruct ScheduleFormWidget{@StorageProp('schedules')schedulesJson:string='[]';getschedules():ScheduleItem[]{try{returnJSON.parse(this.schedulesJson)asScheduleItem[];}catch(e){return[];}}build(){Column(){Row(){Text('今日日程').fontSize(16).fontWeight(FontWeight.Bold)Blank()Text(`${this.schedules.filter(s=>!s.done).length}项待办`).fontSize(12).fontColor('#FF6B00')}.width('100%')ForEach(this.schedules.slice(0,3),(item:ScheduleItem)=>{Row(){Text(item.done?'✓':'○').fontSize(16).fontColor(item.done?'#4CAF50':'#999999').margin({right:8})Column(){Text(item.title).fontSize(14).fontColor(item.done?'#BBBBBB':'#333333').decoration({type:item.done?TextDecorationType.LineThrough:TextDecorationType.None})Text(item.time).fontSize(11).fontColor('#AAAAAA')}.alignItems(HorizontalAlign.Start).layoutWeight(1)}.width('100%').margin({top:8}).onClick(()=>{postCardAction(this,{action:'message',params:{action:'toggle',scheduleId:item.id}});})})}.width('100%').height('100%').padding(16).backgroundColor('#FFFFFF').borderRadius(16)}}

对应的 FormExtensionAbility 处理标记完成的事件:

exportclassScheduleFormAbilityextendsFormExtensionAbility{privateschedules:ScheduleItem[]=[{id:'1',title:'产品评审会',time:'09:30',done:false},{id:'2',title:'提交周报',time:'14:00',done:false},{id:'3',title:'健身',time:'18:30',done:true},];onAddForm(want:Want){returnthis.buildData();}onFormEvent(formId:string,message:string){constparams=JSON.parse(message)asRecord<string,string>;if(params['action']==='toggle'){constid=params['scheduleId'];consttarget=this.schedules.find(s=>s.id===id);if(target){target.done=!target.done;this.context.updateForm(formId,this.buildData());}}}privatebuildData():formBindingData.FormBindingData{returnformBindingData.createFormBindingData({schedules:JSON.stringify(this.schedules)});}}

几点踩坑经验

卡片开发最容易踩的坑是数据传递方式formBindingData只支持 JSON 可序列化的数据,复杂对象、函数什么的传不过去。我一开始想把整个数据对象传过去,结果卡片上显示的全是 undefined,排查了好久。

另一个坑是卡片 UI 的组件限制。不是所有 ArkUI 组件都能在卡片里用,像DialogSheet这些弹窗类组件不行,Canvas也有限制。写之前先看看官方文档的支持列表,别写到一半发现组件不能用。

最后一个建议:卡片的数据尽量做本地缓存onAddForm触发时如果网络不好,用户看到的就是空白卡片,体验很差。先展示缓存数据,后台再异步更新。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/28 5:39:43

集成电路与人工智能专业报考全解析:条件、优劣势与理性抉择

集成电路与人工智能专业报考全解析&#xff1a;条件、优劣势与理性抉择 在国家战略的强力驱动与产业变革的浪潮下&#xff0c;集成电路&#xff08;“造芯”&#xff09;与人工智能&#xff08;“AI”&#xff09;无疑是当下最耀眼的两大“风口”赛道。它们不仅代表着未来的科技…

作者头像 李华
网站建设 2026/6/28 5:32:05

【招聘】招聘思维| 候选人就是火车,你选对了吗?

招聘思维| 候选人就是火车&#xff0c;你选对了吗&#xff1f; ——论招聘中的"多头并发"与"上车哲学"很多招聘人有一个根深蒂固的迷信&#xff1a;“只要我把候选人服务得足够好&#xff0c;沟通得足够深&#xff0c;他一定会来。”这话听起来很有职业素养…

作者头像 李华
网站建设 2026/6/28 5:28:04

YOLO注意力机制改进- 第26篇:NAM归一化注意力的高效实现

6.1 引言 在目标检测任务中,注意力机制已成为提升模型性能的关键技术之一。从早期的SENet(Squeeze-and-Excitation Networks)提出通道注意力开始,研究者们相继提出了CBAM(Convolutional Block Attention Module)、ECA(Efficient Channel Attention)等多种注意力机制。…

作者头像 李华
网站建设 2026/6/28 5:16:13

一文了解BSC链链游项目Crypto Lifeline

时隔一段时间&#xff0c;链游板块再度受到关注。目前Solana链上有几款链游表现活跃&#xff0c;单个游戏日内在线人数可达数千人&#xff0c;其龙头代币Kins市值峰值曾突破2000万美元&#xff0c;在短时间内吸引了较多玩家参与&#xff0c;链游叙事重新成为市场讨论点之一。随…

作者头像 李华
网站建设 2026/6/28 5:14:51

【Agentic RL / 强化学习 / OPD】OpenClaw-RL 源码阅读笔记 --- (1)---基础

0x00 概要 本系列的目的是&#xff1a;借着对 OpenClaw-RL 源码的学习&#xff0c;来梳理强化学习的一些相关概念和思想。所以&#xff0c;会有一些扩展和发散&#xff0c;OpenClaw-RL 只是一个切入点。而且&#xff0c;因为整篇系列是一个整体&#xff0c;所以有些概念的解读…

作者头像 李华