HarmonyOS应用实战-启示散页-55-结果页分享别打断动画:把分享动作延后到结果稳定之后
HarmonyOS 应用实战 55:结果页分享别打断动画,把动作放到结果稳定之后
结果页刚打开时,答案文本、来源题库和操作区并不是同时稳定:AnswerPage 先拿到路由参数,再异步读取收藏状态和题库名,同时分四段播放入场动画。此时如果把“分享”做成一进页就执行,用户看到的可能是半透明答案、空题库名,或者被系统选择器盖住的动画。
《答案之书》当前只实现了复制与题库文本分享:答案页的“复制”调用 Clipboard.copyText(this.param.text);题库列表读取完整 Deck 后用 composeShareText 拼成 名称#答案1#答案2,再复制到剪贴板。结果页尚无分享入口。本文以这些事实为基础,设计一个不打断结果呈现的分享时机,而不把设计示例说成已实现功能。

结果稳定不是“动画结束后等一秒”
AnswerPage.startEntrance() 的四段动画有明确时序:引号 380ms,正文在 120ms 延迟后 500ms,题库来源延迟 380ms,操作行延迟 480ms 后持续 420ms。最后一个动作的视觉结束大约在 900ms,而 deckName 又来自一次异步 DeckService.get。
| 条件 | 来源 | 分享能否依赖 |
|---|---|---|
param.text 非空 |
DrawingPage 的 AnswerParams |
可以构造答案主体 |
param.answerId 非空 |
抽取服务返回的 Answer |
可作内部关联,不必公开 |
deckName 已加载 |
DeckService.get(deckId) |
可选,缺失时不能阻塞分享 |
| 操作行已显示 | 入场动画状态 | 适合开放用户点击 |
所以“稳定”是业务数据可用加上用户可感知的 UI 已就绪,而不是随手写一个 setTimeout(1000)。固定延迟会在低端设备、动画时长调整或题库读取变慢时失效。


现有抽取链路已经提供了正确的结果快照
DrawingPage 在 onReady 时并行启动翻书动画和 AnswerPickService.pickOne(deckId);抽取成功后等待最短过渡时间,再用 replacePath 将 deckId、answerId、text、可选 question 传给 AnswerPage。这份 AnswerParams 是本次结果的快照。
const params: AnswerParams = {
deckId: this.param.deckId,
answerId: this.picked.id,
text: this.picked.text,
question: this.param.question
};
this.pathStack.replacePath({ name: RouteName.Answer, param: params });
分享应从这份参数构造文案,而不是在点击分享时再随机抽一次,也不该从当前选择器重新读取题库来猜答案。否则用户分享的可能不是屏幕上看到的那条结果,重现问题时也无法定位。
把“文案”和“系统分享”分成两个边界
当前项目已经有 Clipboard 工具;它只负责把纯文本写入剪贴板,并在失败时记录错误后抛出。建议新增的结果分享也遵循同样边界:纯函数负责生成文案,页面只在用户点击后调用具体通道。下面是设计示例。
interface AnswerShareSnapshot {
answer: string;
deckName?: string;
question?: string;
}
function composeAnswerShareText(snapshot: AnswerShareSnapshot): string {
const lines: string[] = ['《启示散页》的一页答案', `“${snapshot.answer}”`];
if (snapshot.question?.trim()) {
lines.push(`对应问题:${snapshot.question.trim()}`);
}
if (snapshot.deckName?.trim()) {
lines.push(`来源题库:${snapshot.deckName.trim()}`);
}
return lines.join('\n');
}
问题是否放进分享文案必须由用户决定。问题常常比答案敏感;一个安全的默认值是只分享答案与应用名,提供一个明确开关“包含问题”,而不是默认把 param.question 发到外部应用。
用可观察的 ready 状态控制按钮,不用猜时间
当前答案页已经有 actionsOpacity 和 actionsTranslateY。若加入分享按钮,可额外维护一个业务状态 shareReady:正文参数必须有效,操作行需显示,题库名不是必需条件。它不需要持久化,离开页面自然销毁。
@State private shareReady: boolean = false;
private markShareReady(): void {
if (this.param.text.trim().length > 0) {
this.shareReady = true;
}
}
private async onShare(): Promise<void> {
if (!this.shareReady) {
return;
}
const text = composeAnswerShareText({
answer: this.param.text,
deckName: this.deckName
});
await Clipboard.copyText(text);
promptAction.showToast({ message: '分享文案已复制' });
}
markShareReady 应由最后一段操作区动画完成的回调或明确的页面状态切换触发;不要通过 setTimeout 与动画数字复制绑定。第一版可以先提供“复制分享文案”,它复用已有 Clipboard 边界;接入系统分享面板时,再把 onShare 的最后一层替换为平台能力,文案生成逻辑保持不变。
为什么不在 onReady 自动拉起分享面板
系统分享面板是一次外部交互,会抢占焦点。自动拉起有三个问题:用户还没读到答案;动画被遮住,回到页面时视觉状态难解释;如果用户是从“再来一次”快速切换,旧页面的异步回调还可能影响新页面。
正确顺序应是:
DrawingPage 得到 Answer
→ replacePath(AnswerParams)
→ AnswerPage 渲染与入场动画
→ 操作区可见,shareReady = true
→ 用户点击分享
→ 构造当前参数快照 → Clipboard / 系统分享
这里没有 Repository 或 AppStorage,因为分享当前结果不是持久化业务事实。不要为了显示“分享成功”给 AppStorage 增加键;Toast 或页面内短暂反馈足够。若以后要统计分享次数,应只记录聚合事件码和结果类型,避免保存答案与用户问题正文。
type ShareTrace = {
channel: 'clipboard' | 'system_panel';
answerLength: number;
includeQuestion: boolean;
result: 'success' | 'failure';
};
function logShareTrace(trace: ShareTrace): void {
hilog.info(DOMAIN, TAG, 'share channel=%{public}s len=%{public}d q=%{public}d ok=%{public}s',
trace.channel, trace.answerLength, trace.includeQuestion ? 1 : 0, trace.result);
}
这类诊断只记录通道、长度和结果,既能区分剪贴板或系统面板失败,也不会把答案内容带进日志;它应在分享完成或捕获异常后调用,而不是在构造快照时记录正文。
与“复制答案”“分享题库”不要混为一个协议
| 功能 | 当前/建议载荷 | 入口 | 隐私与长度 |
|---|---|---|---|
| 复制答案 | 单条 param.text |
答案页已有 | 最小载荷 |
| 分享结果 | 答案 + 可选题库名/问题 | 答案页建议能力 | 问题默认不带出 |
| 分享题库 | name#answer… |
题库列表已有 | 可能很长,需用户明确触发 |
复用 composeShareText 去分享单条结果会得到无法理解的导入格式;反过来拿结果文案做题库导出会破坏 parseDeckImport 的约定。协议的使用者不同,格式就必须各自稳定。
验证重点是“分享的就是眼前这一条”
实现后可按以下路径复查:
- 抽取一条答案,动画未完成时确认分享按钮不可点或不可见。
- 动画完成后点击分享,复制文本应与页面答案一致,且不出现另一轮随机结果。
- 在
deckName仍未加载或题库已删除时分享:答案仍可分享,来源行可省略。 - 有问题、无问题两种路径分别验证默认文案;开启“包含问题”时再确认正文准确。
- 快速点“再来一次”并返回结果页,确认旧页面不会再次弹 Toast 或改变新结果状态。
- 让剪贴板写入失败,确认保留当前页面、记录非敏感错误,并提示“复制失败”。
常见问题与定位
| 现象 | 优先检查 | 修复 |
|---|---|---|
| 分享内容不是屏幕上的答案 | 是否点击时重新调用了 pickOne |
只使用当前 AnswerParams 快照 |
| 动画被分享面板打断 | 是否在 onReady 自动触发 |
改为操作区稳定后由用户点击 |
| 分享文案漏题库名 | 是否把题库名当成必填 | deckName 可选,不能阻塞答案分享 |
| 用户问题被意外带出 | 是否默认拼接 question |
默认只含答案,改为显式选择 |
| 从结果页分享出导入格式 | 是否误用 composeShareText |
为结果单独维护 composeAnswerShareText |
排障日志沿用现有做法:记录通道成功/失败、字符串长度或是否携带可选字段,不记录答案和问题正文。这样能知道链路在哪里失败,又不会把用户内容写入 hilog。
小结
结果页分享的关键不是多一个图标,而是尊重 DrawingPage → AnswerPage 产生的结果快照和答案页的入场节奏。用数据就绪和操作区可见定义可分享状态,让用户主动触发,再把文案构造与平台通道分开,才能避免动画被抢焦点、答案串位和隐私泄露。
当前项目已具备答案复制和题库文本导出,尚未实现结果分享入口。本文的 shareReady 与结果文案函数是可落地的扩展设计;真机分享面板、返回行为与不同设备体验仍需在实际工程中验证。
更多推荐


所有评论(0)