fix(harmony): 滚动列表上下边缘渐隐,替掉硬截断

用户(2026-09-17):「上下还是硬截断,不是 webui 那种渐变」。

WebUI 的做法是 `index.css` 给 `.overflow-y-auto` 加
`mask-image: linear-gradient(to bottom, transparent 0, #000 min(12px,10%), ...)`。

ArkUI 的对应物是滚动容器自带的 `fadingEdge(enabled, { fadingEdgeLength })`
(API 14+,定义在 `ScrollableCommonMethod` 上 ⇒ List/Scroll/Grid/WaterFlow 都有),
它淡掉的是**渲染结果本身**,与背景无关。

**刻意不用** linearGradient + mask 手搓渐变色遮罩:那是拿背景色画一个
并不存在的"底色",在壁纸/玻璃主题下必然露馅(壁纸本身带颜色,遮罩层对不上)。

- 新增 `LIST_FADE_LENGTH = 12`(NavItems.ts):与 WebUI 的 `min(12px, 10%)`
  同量级,也接近列表内边距(10vp)—— 静止时几乎看不出,滚动时才起作用。
- MainPage 的 5 个列表容器(收件箱/发件箱/授权/联系人卡片/联系人列表)
  全部挂 `.fadingEdge(true, { fadingEdgeLength: LengthMetrics.vp(LIST_FADE_LENGTH) })`。
  显式传长度而不是用默认 32vp:WebUI 那边踩过同一个坑
  (「渐变用的过猛了,比如收件人候选那里」——固定像素用在高度不固定的容器上)。

判据(harmony-nav.test.mjs 新增一条):常量值、五个列表都挂、长度走
LengthMetrics.vp、禁止 linearGradient+mask 顶替;并做了变异自检
(删掉任一处 fadingEdge 必须判红,实测通过)。

实测(模拟器 1256×2760,密度 3.489):
- 滚动后顶边被切断的卡片文字明显洗白(红色评分 5.5 vs 完整卡片 15.5);
- 底边同样渐隐;截图 /tmp/harmony-fade-scroll.jpeg。

测试:199 passed / 0 failed。
This commit is contained in:
2026-09-17 16:43:56 +08:00
parent ff75339baa
commit fc3fff4f36
3 changed files with 74 additions and 0 deletions

View File

@ -455,3 +455,53 @@ test('★ ⑤ 变异自检:nav 项退回带文字或 22 尺寸或图标不上
`Theme.navMaterial 用的档位 \`${decl[1]}\` 必须在 SDK 的 BlurStyle 枚举里` +
`(成员:${members.join('、')})`);
});
/*
* ★ 滚动列表的上下边缘渐隐(2026-09-17 用户:「上下还是硬截断,不是 webui 那种渐变」)。
*
* WebUI 的做法是 `index.css` 里给 `.overflow-y-auto` 加
* `mask-image: linear-gradient(to bottom, transparent 0, #000 min(12px,10%), ...)`。
* ArkUI 里**不要**手搓一层渐变色遮罩 —— 那是拿背景色画一个并不存在的"底色",
* 在壁纸/玻璃主题下会露馅(壁纸本身带颜色,遮罩层一定对不上)。
* 对应物是滚动容器自带的 `fadingEdge(enabled, { fadingEdgeLength })`(API 14+,
* 定义在 `ScrollableCommonMethod` 上 ⇒ List/Scroll/Grid/WaterFlow 都有),
* 它淡掉的是**渲染结果本身**,与背景无关。
*
* 这一条钉三件事:
* ① 常量存在且是"可争论的那一个数"(派生自 WebUI 的 12px,不是随手写);
* ② 五个列表容器**都**挂上了(漏一个就是"有的地方还是硬截断");
* ③ 用的是 `LengthMetrics.vp(...)` 而不是裸数字 —— fadingEdgeLength 是 LengthMetrics。
*/
test('★ 滚动列表上下边缘渐隐:所有列表容器都挂 fadingEdge(对应 WebUI 的 mask-image)', () => {
const nav = read('model/NavItems.ts');
const len = /export const LIST_FADE_LENGTH:\s*number\s*=\s*(\d+)\s*;/.exec(nav);
assert.ok(len, 'NavItems.ts 要导出 LIST_FADE_LENGTH(渐隐高度,单一可争论的数)');
assert.equal(len[1], '12',
'渐隐高度取 12vp:与 WebUI 的 `min(12px, 10%)` 同量级、也接近列表内边距(10vp)。' +
'改这个数要有理由(它直接决定"切得有交代"的观感强度)');
// 五个列表容器:收件箱 / 发件箱 / 授权 / 联系人卡片 / 联系人列表
const lists = main.match(/List\(\{[^)]*\}\)\s*\{/g) ?? [];
assert.ok(lists.length >= 5, `MainPage 里应当有 5 个列表容器,实际 ${lists.length} 个(判据前提要复核)`);
const fades = main.match(/\.fadingEdge\(/g) ?? [];
assert.equal(fades.length, lists.length,
`每个列表容器都要挂 fadingEdge(实际 ${fades.length} 个 / ${lists.length} 个列表)。` +
'漏掉的那个就是"上下还是硬截断" —— 用户 2026-09-17 原话。');
// 每个 fadingEdge 都要带上显式长度,且走 LengthMetrics.vp(不是裸数字)
const withLen = main.match(/\.fadingEdge\(true,\s*\{\s*fadingEdgeLength:\s*LengthMetrics\.vp\(LIST_FADE_LENGTH\)\s*\}\)/g) ?? [];
assert.equal(withLen.length, fades.length,
`每个 fadingEdge 都要写 \`{ fadingEdgeLength: LengthMetrics.vp(LIST_FADE_LENGTH) }\`。` +
'不写长度会落回默认 32vp —— 在 58px 高的小容器里会把内容洗白' +
'(WebUI 那边踩过同一个坑:「渐变用的过猛了,比如收件人候选那里」)。');
// 不许用"渐变色遮罩"顶替:那是拿背景色画假底色,壁纸主题下必然露馅
assert.ok(!/linearGradient\(\{[^}]*transparent[\s\S]{0,200}\.mask\(/.test(main),
'★ 不许用 linearGradient + mask 手搓渐隐:那是拿背景色画一个不存在的"底色",' +
'壁纸/玻璃主题下会露馅。用滚动容器原生的 fadingEdge(淡的是渲染结果本身)。');
// LengthMetrics 要真的 import 进来(否则编译不过 —— 但这条更早给出可读的错)
assert.match(main, /import\s*\{[^}]*LengthMetrics[^}]*\}\s*from\s*'@kit\.ArkUI'/,
'MainPage 要从 @kit.ArkUI import LengthMetrics');
});