fix(harmony): 补齐窗格切换动画(transition 挂在会换的那棵子树上)
用户(2026-09-17):「一方面一点动画都没有」。
★ 第一版写错了,这里记下来 —— 它的错法很典型,下次还会踩:
只在 onClick 里把 `currentIndex = ...` 包了一层 `getUIContext().animateTo(...)`,
并把 `.transition(...)` 挂在**内容容器(稳定父节点)**上。
编译过、判据(当时只钉"有 animateTo")全绿,**但动画是静默失效的**:
`animateTo` 只负责开一个动画窗口,被换掉的子树自己不声明 transition 就什么都不会动;
而挂 transition 的那个 Column 在新旧两种状态下**都是同一个节点**,永远不会触发。
实测取证:把时长临时改成 20s,6 秒后截图仍是硬切(收件箱整版清晰、没有叠影)。
修法(对齐 WebUI `.pane-rise` / `.rise-in`,`index.css:1208`):
· `Theme.paneRiseIn()` —— 4vp 上浮 + 淡入;`riseInOffset = 4` 与
`@keyframes rise-in { from { opacity:0; transform: translateY(4px) } }` 逐字一致。
· 挂到**if/else 各自的子树根**上(CommPage / ContactsTab / SettingsPane,
以及常驻+visibility 的日历那一支)—— 这才是会被插入/移除的节点。
· `TransitionEffect.asymmetric`:入场 180ms(durBase)、出场 120ms(durFast)。
出场更快是刻意的:同长会让新旧两层半透明地叠着,看起来像"闪一下",
而用户对"闪"敏感(09-14 否掉过整屏淡入)。
· 续用令牌 `durFast=120` / `durBase=180` / `easeOutSoft=cubic-bezier(0.22,1,0.36,1)`。
判据(219 passed / 0 failed):
· 新增「窗格切换有真的过场动画」6 条:令牌数值逐个钉死(120 / 180 /
cubic-bezier(0.22,1,0.36,1));`transition` 必须挂在 if/else 分支根(≥2 处,
含日历那一支);必须 asymmetric。
· **变异自检**:删掉那 4 处 `.transition(...)` ⇒ 该条立刻变红;还原 ⇒ 全绿。
留给以后的坑(写进 Theme 注释):`snapshot_display` 单次往返约 1s,
180ms 的过渡**抓不到**(连拍三帧像素级一致,会得出"动画没做"的错误结论)。
要取证就得把时长临时调到 20s 再取样,验完还原。本文件注释里留了这条与那次实测值。
未验:真机手感(模拟器已确认过渡链路生效);出场动画与详情 push 的叠加观感。
This commit is contained in:
@ -341,6 +341,57 @@ test('★ 系统 `Tabs` 的 bar 已经不在(这一期换的就是它),且
|
||||
assert.throws(() => N.navKeyAt(1.5) === undefined, undefined, '非整数下标不该悄悄生效');
|
||||
});
|
||||
|
||||
/*
|
||||
* ★ 窗格切换必须**真的有**过场动画。
|
||||
*
|
||||
* 用户(2026-09-17):「一方面一点动画都没有」。实测改造前全仓
|
||||
* `animateTo` / `transition` / `animation` **一次都没用过**。
|
||||
*
|
||||
* 这条判据钉三件事,缺一条动画在这套代码里就是**静默失效**:
|
||||
* ① `animateTo` 只负责开动画窗口 —— 被换掉的子树自己不声明 `.transition(...)`
|
||||
* 就**什么都不会动**。实测确认:只包 `animateTo` 与硬切肉眼看不出差别。
|
||||
* ② 过场动画必须挂在**会被插入/移除的那棵子树**上(if/else 的分支根),
|
||||
* 挂在两级之上的稳定父容器上等于没挂(第一版就是这么写错的,
|
||||
* 实测 6 秒取样仍是硬切)。
|
||||
* ③ 时长/曲线必须来自令牌,不许就地拍数 —— 与 WebUI 的
|
||||
* `--dur-fast`/`--dur-base`/`--ease-out-soft` 同一根尺子。
|
||||
*/
|
||||
test('★ 窗格切换有真的过场动画(transition 挂在会换的那棵子树上)', () => {
|
||||
const theme = code(join(HARMONY_ETS, 'common', 'Theme.ets'));
|
||||
const mainCode = stripComments(main);
|
||||
|
||||
// 令牌:三个数与 WebUI 逐字一致
|
||||
assert.match(theme, /durFast:\s*number\s*=\s*120/,
|
||||
'★ durFast 必须是 120(WebUI `--dur-fast: 120ms`)');
|
||||
assert.match(theme, /durBase:\s*number\s*=\s*180/,
|
||||
'★ durBase 必须是 180(WebUI `--dur-base: 180ms`)');
|
||||
assert.match(theme, /curves\.cubicBezierCurve\(\s*0\.22\s*,\s*1\s*,\s*0\.36\s*,\s*1\s*\)/,
|
||||
'★ 缓动必须是 cubic-bezier(0.22, 1, 0.36, 1)(WebUI `--ease-out-soft`)——' +
|
||||
'用 Curve.EaseOut 之类枚举是**另一根**曲线,观感对不上');
|
||||
// 上浮量与 WebUI @keyframes rise-in 的 translateY(4px) 一致
|
||||
assert.match(theme, /riseInOffset:\s*number\s*=\s*4/,
|
||||
'★ 入场上浮量必须是 4(WebUI `@keyframes rise-in` 的 `translateY(4px)`)');
|
||||
|
||||
// transition 构造器存在,且入场/出场**不对称**(同长会闪)
|
||||
assert.match(theme, /paneRiseIn\(\):\s*TransitionEffect/,
|
||||
'要有 paneRiseIn() 这个过渡构造器');
|
||||
assert.match(theme, /TransitionEffect\.asymmetric\(/,
|
||||
'★ 入场/出场必须 asymmetric(出场更快)—— 同长会让新旧两层半透明叠着,看起来像"闪一下"');
|
||||
|
||||
// ② 关键:transition 要挂在 if/else 的**分支根**上
|
||||
const branchRoots = (mainCode.match(/\}\)\s*\n\s*\.transition\(Theme\.paneRiseIn\(\)\)/g) ?? []).length;
|
||||
assert.ok(branchRoots >= 2,
|
||||
`★ transition 要挂在会被换掉的子树根上(if/else 分支),实际只找到 ${branchRoots} 处。` +
|
||||
'挂在两级之上的稳定父容器上等于没挂(实测过:6 秒取样仍是硬切)。');
|
||||
// 日历那一支(常驻 + visibility)也要有
|
||||
assert.match(mainCode, /\.visibility\(this\.currentIndex === 1[\s\S]{0,120}\.transition\(Theme\.paneRiseIn\(\)\)/,
|
||||
'★ 日历窗格(常驻挂载 + visibility 控制)也要挂过场过渡');
|
||||
|
||||
// animateTo 走 getUIContext(全局 animateTo 已废弃,另行有判据钉)
|
||||
assert.match(mainCode, /getUIContext\(\)\.animateTo\(/,
|
||||
'切窗格要开动画窗口:`getUIContext().animateTo(...)`');
|
||||
});
|
||||
|
||||
test('★ 玻璃只在两处、且这一处是"背后有可变内容"(GLASS 登记的放行条件)', () => {
|
||||
/*
|
||||
* pi 撤回"模糊只由壁纸层负责"后给的是两条:①同一张底只许糊一次;
|
||||
|
||||
Reference in New Issue
Block a user