ArkTS 做应用内"快捷胶囊" —— 续航日记踩过的两个鸿蒙声明式 UI workaround

掘金本周最热 · 软件开发

续航日记(BatteryLog)的首页有一排"快捷胶囊":用户点一下某个胶囊,就记一条能量变化(比如"+15 午睡"、"-10 开会"、"+20 散步")。听起来是桌面 ShortcutInfo 的典型场景,但续航日记走的不是鸿蒙系统的桌面快捷方式,而是 应用内 的自定义组件。做这个过程中撞到 ArkTS 和 TypeScript 的两个明显差异。记录一下,顺便分享给有需要的朋友。 一、桌面 ShortcutInfo 和应用内快捷胶囊的区别 鸿蒙有两种"快捷"概念容易混。 桌面 ShortcutInfo (真·系统快捷方式): 在 module.json5 声明 shortcuts.json + metadata 用户在桌面上 长按 app 图标 出现快捷方式菜单 点快捷方式启动 app 时 Want 带特定参数 典型场景:相机类 app 的"扫一扫 / 自拍 / 录像"快捷入口 应用内快捷胶囊 (续航日记做的): 完全是 ArkTS 自定义组件 ,渲染在 app 内部 用户必须先打开 app 才能点 点击后触发 app 内部的业务动作(没有启动参数的概念) 续航日记选第二种,三个理由: 一是 胶囊内容高度个性化 。用户自己定义:胶囊的标题、增减值 (-50 到 +50)、emoji 图标、是否挂到某个项目。桌面 ShortcutInfo 是在 app 开发时固定的(4 个最多),不支持用户动态添加。 二是 胶囊状态要上下文感知 。续航日记里的胶囊有"置顶"、"按项目分组"、"按日活跃度排序"这些行为。桌面 ShortcutInfo 没这个能力。 三是 离开 app 用不着 。续航日记记录能量变化的操作一定发生在"用户打开 app 后",不是"桌面快速扫码"这种外部场景。所以不需要走系统快捷方式。 什么时候要用桌面 ShortcutInfo :你的 app 有"外部用户快速进入某个功能"的场景(如打车类 app 的"回家 / 公司"按钮)。续航日记没这个需求。 二、QuickAction 的数据模型 续航日记的快捷胶囊数据模型(纯 interface): // domain/models/BatteryModels.ets export interface QuickActionModel { id : string // UUID title : string // 用户填的标题,如"午睡" delta : number // 能量变化:-50 到 +50,步长 5 icon : string // emoji,最多 2 code points projectId : string | null // 可选:挂到某个项目 durationMinutes : number // 仅当 projectId≠null 且 delta<0 时非 0 sortOrder : number // 排序值 isPinned : boolean // 是否置顶 } 两个字段设计上有点意思。 delta 的范围和步长 。-50 到 +50 是经验值——真实的"能量变化"不会超过 50 分(续航日记能量满分 100);步长 5 让 UI 的 slider 不至于太精细(滑块只有 21 档,交互友好)。这些约束不是技术选择,是产品决策,但要在 schema 层固定下来——否则有一天用户数据里会出现 "+73.5 分" 这种古怪值。 durationMinutes 的条件存在 。"做了 30 分钟的事"只在"耗能 + 挂了项目"的组合里有意义——比如"-10 开会 挂到产品 X 项目"要记"开会多久",但"+15 午睡"(没挂项目)就不需要时长。续航日记用一个 helper 做条件化: // domain/logic/QuickActionDurationHelper.ets export class QuickActionDurationHelper { static shouldTrackQuickActionDuration ( projectId : string | null | undefined , delta : number ): boolean { return !!projectId && delta < 0 } static normalizeQuickActionDurationMinutes ( raw : number , projectId : string | null | undefined , delta : number ): number { if (! QuickActionDurationHelper . shouldTrackQuickActionDuration (projectId, delta)

查看原文