文章目录
-
-
- 核心思路
- [卡片 UI:发送选择状态](#卡片 UI:发送选择状态)
- [FormAbility:存储状态 + 按状态刷新](#FormAbility:存储状态 + 按状态刷新)
- [进阶:onChangeFormVisibility 可见性驱动刷新](#进阶:onChangeFormVisibility 可见性驱动刷新)
- 状态驱动刷新完整时序图
- 这种方案的优点和局限
- 写在最后
-
用户在卡片上勾了两个复选框,分别代表"状态A"和"状态B"。我想让:只有勾选了状态A,才刷新状态A的内容;只有勾选了状态B,才刷新状态B的内容。这就是"状态驱动刷新"。
StageServiceWidgetCards的widgetupdatebystatus模块完整演示了这个功能,把它拆开讲清楚。
核心思路
卡片 UI 本身是无状态的(每次重建都从 LocalStorage 读初始值),但可以通过 message 事件把用户的选择发给 FormAbility,FormAbility 把这个"选择"存起来,然后在 onUpdateForm 里根据存储的选择决定刷新哪部分数据。
用户勾选 → postCardAction(message, {selectA: true})
→ onFormEvent() 存储选择状态
→ 下次 onUpdateForm() 读取选择状态
→ 只刷新被选中的那部分数据
卡片 UI:发送选择状态
typescript
// entry/src/main/ets/widgetupdatebystatus/pages/WidgetUpdateByStatusCard.ets
let storageUpdateByStatus = new LocalStorage();
@Entry(storageUpdateByStatus)
@Component
struct WidgetUpdateByStatusCard {
// 这两个值由 FormAbility 通过 updateForm 推送进来
@LocalStorageProp('textA') textA: Resource = $r('app.string.to_be_refreshed');
@LocalStorageProp('textB') textB: Resource = $r('app.string.to_be_refreshed');
// 这两个是卡片内部的 UI 状态,不需要和 FormAbility 同步
@State selectA: boolean = false;
@State selectB: boolean = false;
build() {
Column() {
// Checkbox 区域
Column() {
// 复选框 A
Row() {
Checkbox({ name: 'checkbox1', group: 'checkboxGroup' })
.padding(0)
.select(false)
.margin({ left: 26 })
.onChange((value: boolean) => {
this.selectA = value;
// 把选中状态发给 FormAbility
postCardAction(this, {
action: 'message',
params: {
selectA: JSON.stringify(value) // 注意:需要转成字符串
}
});
})
Text($r('app.string.status_a'))
.fontColor('#000000').opacity(0.9).fontSize(14)
.margin({ left: 8 })
}
.width('100%').padding(0).justifyContent(FlexAlign.Start)
// 复选框 B
Row() {
Checkbox({ name: 'checkbox2', group: 'checkboxGroup' })
.padding(0)
.select(false)
.margin({ left: 26 })
.onChange((value: boolean) => {
this.selectB = value;
postCardAction(this, {
action: 'message',
params: {
selectB: JSON.stringify(value)
}
});
})
Text($r('app.string.status_b'))
.fontColor('#000000').opacity(0.9).fontSize(14)
.margin({ left: 8 })
}
.width('100%').position({ y: 32 }).padding(0).justifyContent(FlexAlign.Start)
}
.position({ y: 12 })
// 数据显示区域
Column() {
Row() {
Text($r('app.string.status_a')).fontColor('#000000').opacity(0.4).fontSize(12)
Text(this.textA).fontColor('#000000').opacity(0.4).fontSize(12)
}
.margin({ top: '12px', left: 26, right: '26px' })
Row() {
Text($r('app.string.status_b')).fontColor('#000000').opacity(0.4).fontSize(12)
Text(this.textB).fontColor('#000000').opacity(0.4).fontSize(12)
}
.margin({ top: '12px', bottom: '21px', left: 26, right: '26px' })
}
.margin({ top: 80 }).width('100%').alignItems(HorizontalAlign.Start)
}
.width('100%').height('100%')
.backgroundImage($r('app.media.CardUpdateByStatus'))
.backgroundImageSize(ImageSize.Cover)
}
}
FormAbility:存储状态 + 按状态刷新
typescript
// 对应的 FormExtensionAbility
import { formBindingData, FormExtensionAbility, formProvider } from '@kit.FormKit';
import { Want } from '@kit.AbilityKit';
import { preferences } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
const TAG = 'WidgetUpdateByStatusFormAbility';
const DOMAIN_NUMBER = 0xFF00;
const PREF_PATH = '/data/storage/el2/base/haps/status_store';
export default class WidgetUpdateByStatusFormAbility extends FormExtensionAbility {
onAddForm(want: Want): formBindingData.FormBindingData {
const formData: Record<string, string> = {};
return formBindingData.createFormBindingData(formData);
}
onFormEvent(formId: string, message: string): void {
hilog.info(DOMAIN_NUMBER, TAG, `onFormEvent: ${message}`);
const msg: Record<string, string> = JSON.parse(message);
// 把用户的选择状态存到 Preferences
// key 格式:formId + '_selectA' / formId + '_selectB'
preferences.getPreferences(this.context, PREF_PATH).then(async (prefs) => {
if (msg.selectA !== undefined) {
await prefs.put(`${formId}_selectA`, msg.selectA);
hilog.info(DOMAIN_NUMBER, TAG, `存储状态A: ${msg.selectA}`);
}
if (msg.selectB !== undefined) {
await prefs.put(`${formId}_selectB`, msg.selectB);
hilog.info(DOMAIN_NUMBER, TAG, `存储状态B: ${msg.selectB}`);
}
await prefs.flush();
});
}
onUpdateForm(formId: string): void {
hilog.info(DOMAIN_NUMBER, TAG, `onUpdateForm: ${formId}`);
// 读取这个 formId 的选择状态
preferences.getPreferences(this.context, PREF_PATH).then(async (prefs) => {
const selectAStr = await prefs.get(`${formId}_selectA`, 'false') as string;
const selectBStr = await prefs.get(`${formId}_selectB`, 'false') as string;
const selectA = JSON.parse(selectAStr) as boolean;
const selectB = JSON.parse(selectBStr) as boolean;
// 根据选择状态,决定刷新哪部分数据
const formData: Record<string, string> = {};
if (selectA) {
// 只有勾选了状态A,才更新 textA
formData['textA'] = 'AAA - 状态A已刷新';
hilog.info(DOMAIN_NUMBER, TAG, '刷新状态A');
}
if (selectB) {
// 只有勾选了状态B,才更新 textB
formData['textB'] = 'BBB - 状态B已刷新';
hilog.info(DOMAIN_NUMBER, TAG, '刷新状态B');
}
// 只有有数据需要更新时才调 updateForm
if (Object.keys(formData).length > 0) {
await formProvider.updateForm(
formId,
formBindingData.createFormBindingData(formData)
).catch((error: BusinessError) => {
hilog.error(DOMAIN_NUMBER, TAG, `updateForm 失败: ${JSON.stringify(error)}`);
});
} else {
hilog.info(DOMAIN_NUMBER, TAG, '无状态被选中,跳过刷新');
}
});
}
onRemoveForm(formId: string): void {
// 清理这个 formId 的状态数据
preferences.getPreferences(this.context, PREF_PATH).then(async (prefs) => {
await prefs.delete(`${formId}_selectA`);
await prefs.delete(`${formId}_selectB`);
await prefs.flush();
});
}
}
进阶:onChangeFormVisibility 可见性驱动刷新
HarmonyOS 还提供了另一个维度的状态驱动:卡片可见性变化。当桌面滑动导致卡片从不可见变成可见时,触发这个回调:
typescript
// FormExtensionAbility 里新增这个回调
onChangeFormVisibility(newStatus: Record<string, number>): void {
hilog.info(DOMAIN_NUMBER, TAG, `可见性变化: ${JSON.stringify(newStatus)}`);
// newStatus 的格式:{ formId: visibilityStatus }
// visibilityStatus: 0 = 不可见, 1 = 可见
for (const formId in newStatus) {
const status = newStatus[formId];
if (status === 1) {
// 卡片变为可见,立即刷新数据
this.onUpdateForm(formId);
}
}
}
这样的效果是:用户滑到卡片所在的桌面时,卡片自动触发一次刷新,显示最新数据。
状态驱动刷新完整时序图

这种方案的优点和局限
优点:
- 精准刷新,只更新需要更新的部分,省流量省计算
- 用户自主控制,选什么刷什么,体验好
- 数据持久化后重启手机也不会丢失选择状态
局限:
onChangeFormVisibility不是所有设备都支持,老版本系统可能没有- FormAbility 存活时间只有 5 秒,Preferences 读写要用 async/await 确保完成
- 多个卡片实例时,要用 formId 作为 Preferences 的 key 前缀区分,不能共用同一个 key
写在最后
状态驱动刷新是一个"把控制权还给用户"的设计思路。不是所有数据都要无脑全量刷新,让用户选择感兴趣的内容,然后只刷他选的,这才是好的卡片体验。
WidgetUpdateByStatus 这个 demo 用 Checkbox 做演示,实际开发中可以换成任何有选择逻辑的 UI 元素:Tab 切换、开关、单选按钮都一样。