跨端: 回复/转发改内联底栏 + 入场动画对称化(照鸿蒙文档纠正三处误判)
用户:「点击回复按键与新建邮件部分的动画与 webui 不一致,动画不符合鸿蒙视觉
要求」。两个问题是分开的:结构是覆盖式弹层 vs WebUI 的内联底栏;动画则是我
单方面发明的不对称过渡 + 150ms 低于鸿蒙规范下限。
## 结构:覆盖式弹层 → 底部内联条(回复 / 转发)
WebUI `MailView.tsx:410/1057` 的 `ReplyBar`/`ForwardBar` 是 `border-t` 分出的
**内联底栏**,与正文并列(正文 `flex-1 overflow-y-auto` 保持可见可滚),高度由
内容决定。我们原先是整屏遮罩 + `height('60%')` + `position({x:0,y:0})`。
三条用户可感知的差异:弹层盖住正文(写回复时看不到原文)/固定 60% 高(写一行
也占半屏)/遮罩整屏变暗。结构不用动外层 —— 原版那两处本来就是正文 Stack 的
**兄弟**(同在 `Column` 里 ⇒ 本来竖直排列),错只错在给条加了遮罩/定高/绝对定位。
## 动画
① `paneRiseIn()` / `calendarSlide()` 去 `asymmetric`,改**对称**。
WebUI 是 `animation: rise-in 150ms … both` —— `both` 就是进出同一条关键帧。
我原先让出场只做 `opacity` 且更短(120ms),"出现时浮上来、消失时只淡出",
正是"与 webui 不一致"的来源。当初写不对称的理由(换窗格时两层同时半透明会
"闪")只对**换窗格**成立,对回复框/转发条不成立 —— 我把两种场景混用了。
② `durRise` 150 → **200ms**(用户选定"折中")。WebUI 是 150(web 常规档),
鸿蒙官方「元素淡入/位移进入」建议 **200-300ms**,150 比下限还低 25%。
## 照文档纠正三处误判(本轮的真正收获)
我为了搞清"为什么动画不播",先后编出过三个错误理论,读文档后逐条推翻:
① **不是 "NavDestination 吃掉子组件的 `.transition()`"**。
实测:`ComposeView` 根上的 `.transition()` 一直在播。我之所以连测七八轮都报
"没有中间帧",是因为**拿平均亮度当探针** —— 白底窗格 50% 透明叠在浅色背景上
平均亮度几乎不变。换成**位移**探针后,立刻看到"整栏下移 300vp 且半透明"的
中间帧。教训:**探针对被测变化不敏感时,量的是噪声**。
② **不 `customTransition` 也能做**。`NavDestination` 确实有 `customTransition`
(API 15+),但它是**整页转场**,我们要的只是内容块的一次上浮淡入。
(顺带记一条:`NavDestinationTransition.curve` 的类型是枚举 `Curve`,
不收 `ICurve` —— 试过用 `curves.cubicBezierCurve` 会编译报错。)
③ **`.opacity()` 在 `NavDestination` 上是生效的**。先前判定"不生效"同样是那个
废探针害的;换 `opacity(0)` 二元判定后整页消失,证明它一直生效。
## 连带修一个真 bug(判据抓的)
回复/转发改成内联后**失去了"弹层有固定高度"这层键盘保护** —— 官方默认
`KeyboardAvoidMode.OFFSET`(整页上移)会把贴底的「取消/发送/转发」顶出屏幕。
在 `EntryAbility` 里显式设 `RESIZE`(按剩余高度重排)。坑:`@kit.ArkUI` 与全局
作用域各有一个同名 `KeyboardAvoidMode`,**只有前者有 `RESIZE`**。
## 判据(4 条红全部结算,逐条说明为什么不是放宽)
· `harmony-nav` durRise:从"逐字等于 150"改为**区间 200-300**(钉住用户裁定,
退回 150 与写 800 都红,已变异验证)。
· `harmony-nav` asymmetric:**反转**为"不得 asymmetric"(旧断言把上一版设计锁住,
而 WebUI 本来就是对称的)。
· `harmony-admin` 弹层高度:原断言数的形状只属于废弃的覆盖式弹层 → 改为
**新结构下的等价不变式**(键盘避让必须 RESIZE)。这条判据当年抓的是真 bug,
该 bug 换了形态仍在,所以不能简单删。
· `cross-client-theme`:删掉我中途废弃留下的孤儿令牌 `riseCurveEnum`。
新增 4 条变异条目(全部 `红✓`);`mutants=52 ran=52 skipped=0`。
`files=33 checks=530 pass=530 fail=0`,`baseline=7/7✓`。
设备已验:回复/转发确为内联底栏(正文可见、`border-t` 分隔)。
This commit is contained in:
@ -831,34 +831,78 @@ test('★ 底部弹层必须有明确高度(否则键盘一弹,按钮全被
|
||||
*/
|
||||
const src = code(join(ETS, 'pages/MailDetailPage.ets'));
|
||||
|
||||
/* 找所有"底部弹层"(`borderRadius({ topLeft: ... })` 是这类弹层的标志) */
|
||||
const sheets = [...src.matchAll(/\.borderRadius\(\{\s*topLeft:\s*\d+/g)];
|
||||
assert.ok(sheets.length >= 2,
|
||||
`详情页应有多个底部弹层(回复 / 转发)—— 实际找到 ${sheets.length} 个`);
|
||||
/*
|
||||
* ★★ 2026-09-20 重写:回复/转发**不再是弹层**,改成了内联底栏。
|
||||
*
|
||||
* 用户原话:「点击回复按键与新建邮件部分的动画与 webui 不一致」,并选定
|
||||
* `inline` 方案(WebUI 那边 `MailView.tsx:410/1057` 的 `ReplyBar`/`ForwardBar`
|
||||
* 就是**内联底栏**:`border-t`、与正文并列、高度由内容决定,正文仍可见)。
|
||||
*
|
||||
* 旧断言数"`borderRadius({ topLeft: N })` 形式的底部弹层 ≥ 2 个"——
|
||||
* 那个形状**只属于已废弃的覆盖式弹层**。继续要求它等于把上一版设计锁住。
|
||||
*
|
||||
* ★ 但这条判据**原本抓的是一个真 bug**,那个 bug 没有随结构一起消失,
|
||||
* 只是换了形态,所以判据不能简单删掉,要**换成新结构下的等价不变式**:
|
||||
*
|
||||
* 旧形态(覆盖层 `height('60%')`):弹层自己有固定高度,键盘弹起时
|
||||
* 内容能重分配 ⇒ 要断"有明确高度 + 说明框 layoutWeight(1)"。
|
||||
*
|
||||
* 新形态(内联底栏,高度由内容决定):**底栏没有固定高度**,
|
||||
* 于是"按钮被键盘顶出屏幕"的风险改由**整页的键盘避让模式**承担 ——
|
||||
* 官方默认 `KeyboardAvoidMode.OFFSET` 是"整页上移",页面被推高后
|
||||
* 贴底的内容(正是这些底栏按钮)会被顶出可视区。
|
||||
* 正解是把避让模式设成 `RESIZE`("the page is resized",
|
||||
* 按剩余高度重排,贴底元素留在屏内)。
|
||||
* ⇒ 新不变式:**必须在 Ability 层显式设成 RESIZE**,
|
||||
* 否则内联底栏的按钮在键盘弹起后点不到(同一个后果,另一种成因)。
|
||||
*/
|
||||
const ability = code(join(ROOT, 'client/harmony/entry/src/main/ets/entryability/EntryAbility.ets'));
|
||||
assert.match(ability, /setKeyboardAvoidMode\(\s*KeyboardAvoidMode\.RESIZE\s*\)/,
|
||||
'★ 内联底栏(回复/转发)没有自己的固定高度,键盘避让必须用 `RESIZE`。\n' +
|
||||
' 官方默认是 `KeyboardAvoidMode.OFFSET`(整页上移)—— 页面被推高后,\n' +
|
||||
' 贴底的「取消 / 发送 / 转发」按钮会被顶出可视区(旧版覆盖弹层踩过同一个坑,\n' +
|
||||
' 当时是靠"弹层有固定高度"绕过的;现在结构变了,要正面解决)。');
|
||||
assert.ok(!/KeyboardAvoidMode\.OFFSET/.test(ability),
|
||||
'★ 不要显式设成 `OFFSET`(那就是"整页上移",正是要避免的那个模式)');
|
||||
|
||||
/*
|
||||
* 逐个弹层:从 borderRadius 往回找它的高度声明。
|
||||
* 在 `.borderRadius({ topLeft` 之前的一段里找 `.height(`。
|
||||
* 逐个**仍然存在的**覆盖式弹层:从 `borderRadius({ topLeft` 往回找高度声明。
|
||||
*
|
||||
* ★ 2026-09-20:回复/转发已改成内联底栏(不再是弹层),这道检查现在只剩
|
||||
* **对话树弹层**一个对象。它仍然该有明确高度 —— 理由没变:
|
||||
* 尺寸由内容决定的覆盖层,键盘弹起时内容会被挤出可视区。
|
||||
* (内联底栏那半改用上面的 `KeyboardAvoidMode.RESIZE` 承担。)
|
||||
*/
|
||||
const sheets = [...src.matchAll(/\.borderRadius\(\{\s*topLeft:\s*\d+/g)];
|
||||
assert.ok(sheets.length >= 1,
|
||||
`详情页应还有覆盖式弹层(对话树)—— 实际找到 ${sheets.length} 个。\n` +
|
||||
' 若回复/转发已全变内联底栏,至少对话树那一个还在;一个都没有说明结构被改坏了。');
|
||||
const noHeight = [];
|
||||
for (const m of sheets) {
|
||||
const before = src.slice(Math.max(0, m.index - 1200), m.index);
|
||||
/* 最近的 .height(...) 要在这一段里(弹层的修饰符链上) */
|
||||
if (!/\.height\(('100%'|\d+|'[0-9]+%')\)/.test(before)) {
|
||||
const line = src.slice(0, m.index).split('\n').length;
|
||||
noHeight.push(`MailDetailPage.ets:${line}`);
|
||||
}
|
||||
}
|
||||
assert.deepEqual(noHeight, [],
|
||||
'★ 底部弹层必须有明确高度 —— 没有高度时它的尺寸由内容决定,' +
|
||||
'而键盘弹出(默认 `KeyboardAvoidMode.OFFSET`)会把内容整体上移,' +
|
||||
'**「取消 / 转发」按钮会被顶到键盘下面**(实测截图:按钮点不到)。\n' +
|
||||
'★ 覆盖式弹层必须有明确高度 —— 没有高度时它的尺寸由内容决定,' +
|
||||
'内容一多就会被挤出可视区(旧版回复/转发弹层实测过:按钮点不到)。\n' +
|
||||
`缺高度的弹层:\n ${noHeight.join('\n ')}`);
|
||||
|
||||
/* ② 说明框用 layoutWeight 而不是固定高度(否则它撑住高度、按钮仍被挤出) */
|
||||
/*
|
||||
* ② 底栏内部各元素用**内容尺寸/百分比**,不得再用 `layoutWeight(1)` 抢空间。
|
||||
*
|
||||
* 旧形态里弹层有固定高度(`height('60%')`),说明框必须 `layoutWeight(1)`
|
||||
* 才能在键盘顶上来时自己缩短、把按钮留在屏内。
|
||||
* 新形态是**内联底栏**(WebUI 同形):高度由内容决定,说明框给**固定高度**、
|
||||
* 由外层 `Scroll` 承担超长内容 —— 再挂 `layoutWeight(1)` 反而会在
|
||||
* 无固定高度的容器里抢走全部剩余空间(按钮被压没)。
|
||||
*/
|
||||
const ta = /TextArea\(\{[\s\S]{0,200}?转发说明[\s\S]{0,400}?\n\s*\}\)([\s\S]{0,300}?)\.onChange/.exec(src);
|
||||
assert.ok(ta, '要能找到转发弹层的说明框(TextArea)');
|
||||
assert.match(ta[1], /\.layoutWeight\(1\)/,
|
||||
'★ 转发说明框要用 `layoutWeight(1)`(不是固定 `height(N)`)—— ' +
|
||||
'弹层高度固定后,只有它会伸缩才能把「取消 / 转发」按钮留在键盘之上');
|
||||
if (ta) {
|
||||
assert.ok(!/\.layoutWeight\(1\)/.test(ta[1]),
|
||||
'★ 内联底栏里的转发说明框不得用 `layoutWeight(1)` —— ' +
|
||||
'底栏高度由内容决定,抢权重会把「取消 / 转发」按钮压出可视区');
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user