阳台小花园开发手记 02|HarmonyOS 7.0 互动卡片:用摇一摇激活入口【鸿蒙心迹】

1. 前言
上一篇,我们把阳台上的薄荷放进了一张2×2桌面卡片。普通态显示植物名、最近观察和日记入口;点一下花盆,激活态里的瓢虫从叶后探出来。两套界面使用相同的植物身份与本地资源,页面先加载,观察资料随后补齐。
现在给它增加一个动作:摇动手机,也能唤醒瓢虫。
用户眼中的变化很小。手指不必碰到花盆,多了一个与卡片打招呼的方式。代码却需要回答一个新的问题:提供方收到回调时,怎样知道这次应该播放动效?
可以把期望写成三个操作。点击花盆,尝试动效;摇动手机,尝试动效;在应用里更新观察,卡片显示新摘要。第三个操作尤其重要。用户记录“新长出两片叶子”,并不意味着他同时要求瓢虫出来走一圈。

本文继续使用薄荷 plant-001,保留 MintCard 和 MintLive 两个页面。观察日记仍由用户手动维护。摇动不会登记浇水,也不会改变植物的生长状态。我们要增加的是一个触发入口,而它应当准确表达用户的动作。
2. 方案
处理事件时,我习惯先问两个问题:事件从哪里到达,它允许程序做什么?这两个答案有时相同,有时需要额外信息才能建立联系。
onUpdateForm 就属于后一种情况。按照ArkTS卡片生命周期的说明,它还用于定时、定点及使用方主动更新等场景。因此,进入这个函数只能说明发生了相应的更新回调,不能单独证明用户摇动了手机。
如果在函数入口直接请求动画,代码会非常短。但这段代码也把一个假设藏了起来:所有更新都代表摇动。摘要刷新一旦走到这里,假设便变成用户看得见的行为。
我们需要把三条路径分别写清楚。
| 输入 | 接入路径 | 应用职责 |
|---|---|---|
| 点击花盆 | 卡片发送 message,提供方接收 onFormEvent | 识别动效消息,发起请求 |
| 系统摇动 | 配置 shake,由系统参与识别与调度 | 依据正式来源机制分发请求 |
| 普通更新 | 对应的内容更新流程 | 读取并更新卡片内容 |
互动卡片开发的摇动流程涉及 onUpdateForm 和提供方请求激活。这意味着添加配置之后,仍要处理应用侧的分发;也意味着来源判断必须参考完整接口,不能仅凭流程图推测回调参数。

这个区别适合收在一个很小的接入层里。它负责把系统事件解释成应用可以理解的输入;后面的代码只决定是否请求动效。这样,SDK参数的变化不会散落到页面、数据读取和动画逻辑中。
两种动效入口则应尽早汇合。它们打开同一个 MintLive,使用同一盆薄荷和同一套加载逻辑。为摇动另建一个页面,会多出一套需要同步维护的植物名、花盆位置和缺省状态,当前需求并不需要这种分叉。
日记按钮仍保留独立的业务入口,目标是 plant-001。点击动效区域与点击日记按钮表达不同意图;判断消息时,应当保留这种区别。
3. 实现
先看版本。场景动效基础配置从API20已有;本篇使用的 triggerTypes 中的 shake 从26.0.0开始支持。基础能力与新增入口需要分别记录,排查兼容性时才能找到正确的检查点。
| 配置或能力 | 版本边界 | 本篇用途 |
|---|---|---|
sceneAnimationParams | API20已有 | 声明场景动效配置 |
abilityName | 场景动效基础配置项 | 引用激活扩展 |
triggerTypes 中的 shake | API26增量 | 接入系统摇动触发 |
设备能显示普通卡片,并不能据此推出它支持场景动效或摇动入口。
第01篇的 sceneAnimationParams 已经指向 MintLiveAbility。现在将以下片段合入既有卡片定义:
{
"sceneAnimationParams": {
"abilityName": "MintLiveAbility",
"triggerTypes": ["shake"]
}
}
这里新增的只有 triggerTypes。普通扩展仍叫 MintFormAbility,激活扩展仍叫 MintLiveAbility,模块声明中的扩展名称必须与引用一致。卡片标识、2×2规格和页面名称都沿用上一版。
也不要同时加入 funInteractionParams。两类配置同时存在会影响系统对卡片类型的识别。小花园使用的是场景动效,后续请求与生命周期讨论都建立在这一点上。

配置之后,最容易仓促完成的部分是来源识别。这里需要核对当前SDK的 FormExtensionAbility、相关 FormParam 文档与完整官方样例,确认正式参数、来源语义和支持版本。现有流程资料没有给出完整判别逻辑,因此本文不指定某个Want键来代表摇动。
这个接口细节可以与应用的决策规则分开。下面先定义接入层输出的应用类型。枚举名称由项目定义,不对应SDK字段。
enum MintInput {
AnimationClick,
SystemShake,
ContentRefresh,
Unclassified
}
function shouldRequestAnimation(input: MintInput): boolean {
return input === MintInput.AnimationClick ||
input === MintInput.SystemShake;
}
这段函数有一个容易检查的性质:只有两类明确输入能申请动效。普通刷新与无法分类的事件都不会被升级成摇动。Unclassified 还提醒调用者,无法判断与已经确认普通刷新是两种状态。
它只决定动效请求,不决定全部业务处理。普通更新仍应完成原有数据刷新职责,来源识别失败也不应顺手切断已有的内容更新流程。把函数命名为 shouldRequestAnimation,比笼统的 handleEvent 更能限制它承担的责任。
点击入口相对直接。卡片与提供方可以约定一个应用消息名,识别时只接受该消息。下面的类型和函数位于消息已经按项目协议解析、校验之后:
interface MintMessage {
action: string;
}
function classifyMintMessage(message: MintMessage): MintInput {
if (message.action === 'playMintAnimation') {
return MintInput.AnimationClick;
}
return MintInput.Unclassified;
}
playMintAnimation 是小花园自己的协议值。其他卡片消息不会因为进入 onFormEvent 就自动获得播放动画的含义;消息解析失败时也应记录原因,而不是填入默认的动效动作。
摇动接入层要遵循同样的规则:只有正式机制确认的来源,才能产生 SystemShake。人工观察到“刚才摇了手机”,或者日志恰好出现 onUpdateForm,都不能代替程序里的来源判别。这个映射是接入前必须完成的核验项,应用枚举本身不会补上缺失的系统证据。

通过判断后,点击和摇动都交给已有的动效请求逻辑,接入 requestOverflow。当前卡片实例使用 formId 标识,植物对象使用 plantId 标识;不能因为只有一盆薄荷就把两者互换。
激活页面继续遵守上一篇的加载顺序。以下是 onLiveFormCreate 同步路径中的关键调用,存储项已在调用前准备好:
session.loadContent('pages/MintLive', initialStorage);
loadSnapshot('plant-001')
.then(applySnapshot)
.catch(showReadFailure);
其中数据读取与更新函数沿用项目封装。增加入口不应改变页面何时出现,也不应让摇动路径重新等待业务资料。单次场景动效申请仍不超过3500ms,并且允许被提前中断。
请求失败时,保留返回信息和当前上下文即可,不循环重试到成功。普通卡片仍应显示摘要、提供日记入口。为了补偿摇动没有触发而在后台持续监听加速度,会引入另一套识别与生命周期问题,也无法证明系统摇动入口已经正确接入。
4. 检验
一个入口是否正确,既要检查它该做的事,也要检查它不该做的事。对小花园来说,点击和摇动能否进入激活UI是一半;普通刷新是否保持安静是另一半。
可以先检查应用规则。下面的检查只验证分发函数,不涉及系统是否识别摇动:
function checkAnimationPolicy(): void {
if (!shouldRequestAnimation(MintInput.AnimationClick)) {
throw new Error('Click should request animation');
}
if (!shouldRequestAnimation(MintInput.SystemShake)) {
throw new Error('Shake should request animation');
}
if (shouldRequestAnimation(MintInput.ContentRefresh)) {
throw new Error('Refresh must not request animation');
}
if (shouldRequestAnimation(MintInput.Unclassified)) {
throw new Error('Unknown input must not request animation');
}
}
这里的覆盖范围很小,但边界明确。即使四项检查成立,也只能说明应用规则符合预期,系统事件到枚举值的映射仍须单独验证。
真机对照准备A、B两组配置:A保留第01篇配置,B增加 shake。完成来源处理后,两组使用相同代码、资源与数据,只保留配置差异。记录机型、系统、SDK、工具版本和构建编号,不能把换设备后的行为差异归因于一行配置。
每个用例重复三次,分别保存日志和录屏。先确认卡片状态,再开始记录、执行操作,等待动效退出或观察结束,最后核对摘要与日记。普通更新和摇动分开执行,否则一次相邻的回调很容易被错误归因。
| 用例 | 核对重点 | 实际记录 |
|---|---|---|
| 点击花盆 | 消息是否正确分类,是否请求并显示激活UI | 待采集 |
| 单次摇动 | 正式来源证据、请求结果、A/B表现 | 待采集 |
| 连续摇动 | 事件、请求与会话的对应关系 | 待采集 |
| 普通更新 | 摘要更新,未被误分发为动效 | 待采集 |
| 卡片不在当前桌面 | 实际回调、呈现情况及返回后的内容 | 待采集 |
| 请求失败 | 诊断信息、普通态内容与日记可用性 | 待采集 |
| 旧系统 | 分别记录普通卡片、点击动效、摇动能力 | 待采集 |
普通更新用例尤其需要写清触发方法。onUpdateForm 是提供方回调,数据回推又是另一个操作。仅调用一次数据更新方法,没有观察到目标回调,不能声称已经覆盖了“更新回调误触发动效”的风险。

日志应同时保留人工操作与程序判断。人工操作回答“做了什么”,来源证据回答“程序依据什么分类”。可以采用下面的记录结构,空位由实际采集填入:
env: device=... os=... sdk=... build=... variant=...
context: formId=... plantId=plant-001 operationId=...
operation: kind=... time=...
callback: name=... time=... sourceEvidence=...
decision: input=... requestAnimation=...
request: time=... result=...
lifecycle: event=... time=...
video: file=... visibleFrame=...
operationId 是应用侧关联记录的编号,不是系统参数。一次操作可能对应多个事件,连续摇动也未必逐次产生独立激活。先保留对应关系,再解释原因,不预设系统会排队或每次都会响应。
观察结果还要分层。来源正确,说明分类有依据;请求成功,说明请求阶段返回了成功;创建回调出现,说明生命周期走到了相应位置。屏幕是否显示薄荷、动画是否完整播放,需要录屏提供另外的证据。

旧系统与不在当前桌面的用例也按这一方法记录,不提前填写错误码。缺少对应设备时保留覆盖缺口。真正有用的结果是“在哪个环境、执行什么操作、得到哪些事件与画面”,而不是脱离条件的一句“摇一摇正常”。
5. 结尾
摇动入口的配置只有一行,正确接入却需要明确的事件语义。点击消息、系统摇动和普通更新可以经过相近的处理位置,但应用必须保留它们各自的含义。
这篇整理出的版本表、配置差异、触发图、分发规则与验证矩阵,共同约束一个行为:只有明确的动效输入才发起请求。正式来源映射完成核验后,还应结合日志与录屏确认整条路径,尤其检查普通更新是否被误触发。
下一篇继续使用同一个 MintLive 和薄荷 plant-001,在可靠的激活路径上处理区域控制:叶片伸出边框时,花盆怎样仍留在原来的位置。
更多推荐

所有评论(0)