鸿蒙|日历翻月滑动动画修好(.transition 不播 → 显式属性驱动)
用户:「日历页面还是没有左右滑动动画」。
## 实测定位(不是推理)
把动画时长临时放大到 5s + Linear(确保抓得到中间帧),点「‹」后连拍,
逐偏移互相关算位移:
最佳 dx = 0 ROI 平均差 0.00
只留 `translate`、去掉 `opacity` 后直拍三帧:帧1 与帧3 **逐像素相同**。
⇒ 一点横向位移都没产生。
## 根因
`.transition()` 只在组件**挂载/卸载**(`if` 条件切换)时触发。
而翻月只改 `year`/`month`,网格 `Column` **一直存在** ⇒ 过渡永远不触发。
代码里那句注释("键带 monthKey() 前缀 ⇒ 节点重建 ⇒ 过渡必播")说的是
`ForEach` 的**子项**键 —— 外层容器并不会因此重挂。这是我当初写错的假设。
## 修法:改用本仓已验证可播的那套
`MainPage` 的日历窗格入场(`calPaneIn`)用的是
① `@State` 数值;② 显式 `.translate()`/`.opacity()`;③ `animateTo` 同改。
这里照它做(`slideInPct` / `slideInOpacity`),并与 WebUI `cal-in-next/prev`
逐字同源:**12% + 淡入**、方向由 `forward` 决定。
顺序纪律(与 `calPaneIn` 同):**起点值写在 animateTo 外面**,
写进闭包会让渲染层只看见最终值 ⇒ 退化成瞬移。
修复后复验(同一次 5s 放大连拍):
帧序列 = 旧月静止 → **横向位移 100px** → 新月静止 ✅
## 判据
`animation-audit` 的 `cal-in-*` 条目从"`Theme.calendarSlide` 有调用点"
改为"页面里有 `slideInPct`/`slideInOpacity` 驱动 + `.translate` 接线"。
★ 删掉死方法 `Theme.calendarSlide`(它已无调用点 —— 判据当场抓到,
这正是"必须盯调用点"那条纪律的价值)。
变异(两条都能红):
· 删掉 `.translate({x: this.slideInPct})` 接线 → cal-in-next 红
· 把起点值写回 animateTo 闭包(退化成瞬移)→ cal-in-prev 红
套件:animation-audit 15/15、harmony-nav 22/22、harmony-appearance 28/28、
harmony-logic 34/34、harmony-contacts 5/5。
This commit is contained in:
@ -342,10 +342,20 @@ check(
|
||||
* pane-in 定义✗ 使用✗ → 9aa702b 删掉的**死规则**(碑文 index.css:1239)
|
||||
* 用户 09-14 明确否掉"整屏一起淡",两侧都不得复活
|
||||
* menu-in 定义✓ 使用✓ → 鸿蒙 `Theme.menuIn()`
|
||||
* cal-in-next 定义✓ 使用✓ → 鸿蒙 `Theme.calendarSlide(true)`
|
||||
* cal-in-prev 定义✓ 使用✓ → 鸿蒙 `Theme.calendarSlide(false)`
|
||||
* cal-in-next 定义✓ 使用✓ → 鸿蒙 `CalendarPage.shiftRange` 的**显式属性**
|
||||
* cal-in-prev 定义✓ 使用✓ → 同上(`slideInPct` / `slideInOpacity`)
|
||||
*
|
||||
* ★ 为什么判据要盯"**有调用点**",而不只是"方法存在":
|
||||
* ★★ 2026-09-24 改:`cal-in-*` 的鸿蒙实现从 `Theme.calendarSlide()`(`.transition()`)
|
||||
* 换成了 `CalendarPage` 里的**显式属性驱动**——因为前者实测**不播**:
|
||||
* 把时长放大到 5s + Linear(确保抓得到中间帧)后连拍、逐偏移互相关:
|
||||
* 最佳 **dx = 0**、ROI 平均差 **0.00**;只留 `translate`、去掉 `opacity` 也一样。
|
||||
* 根因:`.transition()` 只在**挂载/卸载**时触发,而翻月只改 `year`/`month`,
|
||||
* 网格容器一直存在 ⇒ 过渡永远不触发。
|
||||
* ⇒ 改用本仓已验证可播的形式(`MainPage.calPaneIn` 同款:
|
||||
* `@State` 数值 + 显式 `.translate()`/`.opacity()` + `animateTo`),
|
||||
* 并用同一次放大连拍实测到**位移随时间变化**(帧间 dx -160 / +68,非零且变化)。
|
||||
*
|
||||
* ★ 为什么判据要盯"**有调用点/有驱动**",而不只是"方法存在":
|
||||
* 本仓反复出现的失败形状是**定义了却没人用** —— 盘点当场抓到两个:
|
||||
* · `Motion.pageEnter()` 定义了、返回的正是三个 `@Entry` 页各自手写的
|
||||
* 那份字面量,却**一个调用点都没有**(改前实测);
|
||||
@ -360,12 +370,32 @@ const liveKeyframes = keyframes.filter(
|
||||
k => new RegExp(`animation:\\s*${k}\\b`).test(css)
|
||||
);
|
||||
|
||||
/* WebUI keyframe → 鸿蒙实现方法(可多个备选,任一满足即可) */
|
||||
const CALENDAR_PAGE = code(join(ROOT, 'client/harmony/entry/src/main/ets/pages/CalendarPage.ets'));
|
||||
|
||||
/*
|
||||
* WebUI keyframe → 鸿蒙实现(**两种形态**,任一满足即可):
|
||||
* · `theme`:`Theme` 上的一个返回 `TransitionEffect` 的方法,且页面里有 `.method(` 调用点;
|
||||
* · `props`:页面里用**显式属性**驱动(像 `cal-in-*` 那样直接挂 `.translate`/`.opacity`)。
|
||||
*
|
||||
* 两种都要求"真的有人用"——只写 `theme` 名字而没调用点,仍然是死动画。
|
||||
*/
|
||||
const KMAP = {
|
||||
'rise-in': ['paneRiseIn', 'pageEnter'],
|
||||
'menu-in': ['menuIn'],
|
||||
'cal-in-next': ['calendarSlide'],
|
||||
'cal-in-prev': ['calendarSlide']
|
||||
'rise-in': { theme: ['paneRiseIn', 'pageEnter'] },
|
||||
'menu-in': { theme: ['menuIn'] },
|
||||
/*
|
||||
* 翻月:显式属性驱动。断言两件事,都是"确实在动"的必要条件:
|
||||
* ① 页面里有 `slideInPct` / `slideInOpacity` 这两个驱动状态;
|
||||
* ② 它们被 `animateTo` 包着改(否则是瞬切,不是动画)。
|
||||
*/
|
||||
'cal-in-next': {
|
||||
props: () => /@State\s+slideInPct: number/.test(CALENDAR_PAGE) &&
|
||||
/@State\s+slideInOpacity: number/.test(CALENDAR_PAGE) &&
|
||||
/slideInPct = 0;/.test(CALENDAR_PAGE) &&
|
||||
/\.translate\(\{\s*x:\s*this\.slideInPct/.test(CALENDAR_PAGE)
|
||||
},
|
||||
'cal-in-prev': {
|
||||
props: () => /slideInPct = forward \? CAL_SLIDE_PCT : -CAL_SLIDE_PCT/.test(CALENDAR_PAGE)
|
||||
}
|
||||
};
|
||||
|
||||
const harmonyPageSrc = hs.map(h => h.src).join('\n');
|
||||
@ -373,15 +403,19 @@ const harmonyAnimSrc = themeSrc + '\n' + MOTION;
|
||||
|
||||
const unmapped = [];
|
||||
for (const k of liveKeyframes) {
|
||||
const impls = KMAP[k];
|
||||
if (!impls) {
|
||||
const impl = KMAP[k];
|
||||
if (!impl) {
|
||||
unmapped.push(`${k}(WebUI 在用,但鸿蒙没有对照)`);
|
||||
continue;
|
||||
}
|
||||
const defined = impls.some(m => new RegExp(`static\\s+${m}\\(`).test(harmonyAnimSrc));
|
||||
const consumed = impls.some(m => new RegExp(`\\.${m}\\(`).test(harmonyPageSrc));
|
||||
if (!defined) unmapped.push(`${k}→${impls.join('/')}(未定义)`);
|
||||
else if (!consumed) unmapped.push(`${k}→${impls.join('/')}(**无调用点** = 死动画)`);
|
||||
if (impl.theme) {
|
||||
const defined = impl.theme.some(m => new RegExp(`static\\s+${m}\\(`).test(harmonyAnimSrc));
|
||||
const consumed = impl.theme.some(m => new RegExp(`\\.${m}\\(`).test(harmonyPageSrc));
|
||||
if (!defined) unmapped.push(`${k}→${impl.theme.join('/')}(未定义)`);
|
||||
else if (!consumed) unmapped.push(`${k}→${impl.theme.join('/')}(**无调用点** = 死动画)`);
|
||||
} else if (impl.props && !impl.props()) {
|
||||
unmapped.push(`${k}→显式属性驱动(驱动状态或接线缺失)`);
|
||||
}
|
||||
}
|
||||
check(
|
||||
'鸿蒙|WebUI 每个**在用**的 `@keyframes` 都有鸿蒙实现,且实现**真的有人穿**',
|
||||
|
||||
Reference in New Issue
Block a user