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:
@ -2147,6 +2147,18 @@ struct MainPage {
|
||||
if (target === this.currentIndex) {
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* ★ 换窗格要**真的**有过渡:`animateTo` 只开了一个动画窗口,
|
||||
* 被换掉的那棵子树自己不声明 `.transition(...)` 的话什么都不会动
|
||||
* (实测:只包 `animateTo` 和硬切看不出差别)。
|
||||
* 过场动画挂在**内容容器**上(见 build 里 `Column().transition(...)`),
|
||||
* 这里只负责把 index 变掉;系统在动画窗口内跑那条 transition。
|
||||
*
|
||||
* 时长/曲线用令牌:`durFast`(120) —— WebUI 对应物是
|
||||
* `.rise-in { animation: rise-in 150ms cubic-bezier(0.22,0.61,0.36,1) }`,
|
||||
* 同一档(百毫秒级、不拖)。曲线用令牌里那条 `easeOutSoft`,
|
||||
* 与 WebUI 的 `.pane-rise` 同一根尺子。
|
||||
*/
|
||||
this.getUIContext().animateTo({
|
||||
duration: Theme.durBase,
|
||||
curve: Theme.easeOutSoft
|
||||
@ -2284,8 +2296,10 @@ struct MainPage {
|
||||
Column() {
|
||||
if (this.currentIndex === 0) {
|
||||
CommPage({ bgActive: this.bgActive, navReserve: this.navReserve })
|
||||
.transition(Theme.paneRiseIn())
|
||||
} else if (this.currentIndex === 2) {
|
||||
ContactsTab({ bgActive: this.bgActive, navReserve: this.navReserve })
|
||||
.transition(Theme.paneRiseIn())
|
||||
} else if (this.currentIndex === 3) {
|
||||
/*
|
||||
* ★ 第四项「我的」现在是**内容窗格**(不再是 push 出去的 @Entry 页)。
|
||||
@ -2297,6 +2311,7 @@ struct MainPage {
|
||||
* 所以这里按窗格挂载(与前三项同一套机制)。
|
||||
*/
|
||||
SettingsPane({ bgActive: this.bgActive, navReserve: this.navReserve })
|
||||
.transition(Theme.paneRiseIn())
|
||||
}
|
||||
/*
|
||||
* 日历与另外两个 pane 不同:**常驻挂载**,用 `visibility` 控制显示。
|
||||
@ -2319,6 +2334,7 @@ struct MainPage {
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.visibility(this.currentIndex === 1 ? Visibility.Visible : Visibility.None)
|
||||
.transition(Theme.paneRiseIn())
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
@ -2354,6 +2370,24 @@ struct MainPage {
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
/*
|
||||
* ★ 窗格切换的过场动画(用户 2026-09-17:「一方面一点动画都没有」)。
|
||||
*
|
||||
* 为什么必须有这一行:`animateTo` 只负责**开一个动画窗口**,
|
||||
* 被换掉的那棵子树自己不声明 transition 就什么都不会动 ——
|
||||
* 实测只包 `animateTo` 与硬切**肉眼看不出区别**。
|
||||
*
|
||||
* 形状对齐 WebUI 的 `.pane-rise` / `.rise-in`(`index.css:1208`):
|
||||
* `@keyframes rise-in { from { opacity: 0; transform: translateY(4px) } }`
|
||||
* ⇒ 4px 上浮 + 淡入,**只动刚出现的那块**。
|
||||
* 刻意不做整屏淡入/缩放:用户 09-14 否掉过"整屏一起淡"
|
||||
* (`index.css:1152` 原话「页面级入场仍不做」)。
|
||||
*
|
||||
* `TransitionEffect.asymmetric` 两边的时长不一样:
|
||||
* 进场 180ms(durBase,走完整条缓出曲线);
|
||||
* 出场 120ms(durFast)—— 旧窗格要**让位**得快,
|
||||
* 否则两层内容同时半透明地叠在屏幕上,看起来像"闪一下"。
|
||||
*/
|
||||
.padding({
|
||||
left: this.isWide ? Theme.paneGap : 0,
|
||||
right: this.isWide ? Theme.paneGap : 0,
|
||||
|
||||
Reference in New Issue
Block a user