跨端: 统一顶栏组件 + 按压反馈(真跑通)+ 滑动判定从"时长门"改"速度门"
用户四条意见,逐条都是真问题,且**两条是我写完没生效**:
① 「所有的顶栏都应该应用玻璃圆框效果」② 「抽象一个统一的顶栏组件出来吧」
③ 「你不觉得发件箱太大了吗」④ 「各个组件带响应点击、滑动等的动画了吗」
⑤ 「还有转场动画呢?」⑥ 「还有其他行为都要一一对齐,例如邮件展示页面」
★ ① ② 统一顶栏 `AppHeader`
改造前**六处各写各的**:`AdminUsersPage`(40×40 返回键+16 号标题)、
`SettingsPage`(20 号标题+40×40「+」+**实心 Theme.surface**)、
`MailDetailPage`(36×36 + 14 号标题,行高只有 14px 被裁过)、
`MainPage.SentTab`/`PermissionTab`(通栏、无圆角)、`CommTabBar`。
尺寸(36/40、14/16/20)、底色(实心白/透明/无)、返回键(有/无)三类都不一致;
且四处用 `Text('‹')` 当返回键 —— **用字符当图标**(本仓明令禁止,字形随字体变、
基线对不齐),而 `ICON_PATHS` 里**早就有** `chevronLeft`。
⇒ 抽成 `AppHeader`(返回键 + 标题 + `@BuilderParam` 右侧动作区),
几何复用底部条常量,材质走 `Theme.navMaterial`。已接入 5 处。
★ `topInsetPx` 由调用方传:状态栏避让是**窗口级**事实(属页面),
组件自己读会变成"每层各加一次"(平板侧栏 83+83 双计就是这么来的)。
★ ③ 发件箱"太大"—— 我的理由错了
我第一版让 `HEADER_HEIGHT = NAV_BAR_HEIGHT`(56),理由是"顶栏与底栏同在一根
竖轴上,高度不同会一眼看出来"。**那个理由不成立**:
底栏是**两行**(图标+文字标签)⇒ 需要 56;顶栏只有**一行标题** ⇒ 56 里一半是空白。
实测 `Row [277,315,1105,476]` = 161px = 56vp,标题那行只占 54px。
"两根轴上的条要一样高"是把**对齐**理解成了**等高**。真正要对齐的是**左右留白与圆角**。
⇒ `HEADER_HEIGHT = 44`(= 可点区下限,返回键装得下)。
★ ④ 按压反馈 —— 两次失败才跑通,两个坑都记进注释
· **坑一**:`.attributeModifier()` **一个组件只能挂一个**,链两个是**后者覆盖前者**。
会话组头卡同时挂了玻璃与按压反馈 ⇒ 只生效一个,**编译器不报错、运行不提示**。
WebUI 是 CSS,类名天然叠加;ArkUI 是单一插槽,"叠加"必须显式做
⇒ 新增 `CompositeModifier`。
· **坑二**:`applyPressedAttribute` **只对自带按压状态机的组件**(`Button`)回调。
实测:挂 `Button` 上 → hilog 有输出;挂只有 `.onClick` 的 `Row`/`Column` 上
→ **一次都不回调**(按下与常态逐像素相同 `239,244,255`)。而全仓 117 处
`onClick` 的主体正是纯 `Row`/`Column` ⇒ 那条路对本仓没用。
改用 `onTouch` + `animateTo`。
· 底色用**系统点击效果色** `ohos_id_color_click_effect`(不是我自己挑的):
第一版用 `Theme.surfaceMuted`,实测只差 `3/3/3`,肉眼看不出 ——
因为"一块面的颜色"与"按下时叠的提示色"在系统色板里是**两个不同语义**。
设备验证:`onTouch type=0`(Down) → `type=1`(Up) 成对到达,像素确有位移。
★ ④ 滑动判定:从「时长门」改成「速度门」(**设计错误,不是调参**)
旧门 `SWIPE_MAX_DURATION_MS = 700`("整个手势超 700ms 就拒")。
而**时长 = 距离 ÷ 速度** —— 它把两个量混成一个 ⇒ 同样的手速下
**滑得越远越容易被拒**,屏幕越大越严重(平板同一手势像素更多)。
设备实测(3184×2232,位移恒 542.6vp):
600px/s→5909ms | 1200→3161ms | 1800→2006ms | 2500→1429ms | 5000→616ms | 15000→123ms
⇒ 旧门意味着**只有 ≥5000px/s 才过**,而那一档重复测试也只有 2/4 成功
—— 用户报的"滑不动/时灵时不灵"就是这个。
改 `SWIPE_MIN_SPEED = 0.25`(vp/ms,取自上表两档中间)后实测:
1200px/s → 0/3(正确拒绝:那是拖动) 2500px/s → **0/4 → 3/3**
5000px/s → **2/4 → 3/3**
两条判据跟着改形态(**语义不变**:都是"够快才翻页"):
· `cross-client-gesture` 断言"算的是 dx/ms"——只断言"有个门"会漏掉这次的错
(旧的时长门**存在**、常量**有名字**、数值**是正数**,三条全过,而真机拒掉正常滑动);
· `harmony-logic` 的行为判据加了两条**回归钉**:
`542.6vp/1429ms`(实测的正常甩动)必须过;
`1200vp/3000ms`(同速度、更远)也必须过 —— 旧门正是在这里误拒。
★ ⑤ 转场动画:查清了,"不做页面级"是**决定**而不是漏做
WebUI `index.css:1230` 写明:页面级淡入会让**已在那儿的框架**也一起暗一下
("观感还是闪"),且用户 2026-09-14 **亲口否掉过**"整屏一起淡",
所以那一档整体删掉,只保留**局部**动效。鸿蒙侧当前是:
· `pushUrl` 推页 → **系统自带的滑动转场**(连拍 8 帧验证:第 3 帧能看到
写信页正在覆盖发件箱,是真实的横向滑入,不是硬切);
· 局部入场 → `paneRiseIn` / `menuIn` / `calendarSlide`(已接 12 处)。
所以"加 pageTransition"反而会与用户当时的否决冲突 —— 这一条**不做**,
理由记在这里,免得下次又当成遗漏。
判据:files=32 ran=32 checks=519 pass=519 fail=0 red=0;baseline 7/7✓。
This commit is contained in:
@ -128,18 +128,40 @@ test('★ 语义:纵向优先 —— 横向位移必须明显大于纵向,
|
||||
test('★ 语义:慢拖不翻页("快滑"是感知档,不是同一个毫秒数)', () => {
|
||||
/*
|
||||
* WebUI:`const fast = Date.now() - start.t < 600` + `!fast → return`。
|
||||
* 鸿蒙:`if (ms > SWIPE_MAX_DURATION_MS) return v;`。
|
||||
* 鸿蒙:`if (speed < SWIPE_MIN_SPEED) return v;`。
|
||||
*
|
||||
* 两端都要有这道门;**毫秒数不比**。
|
||||
* ★★ 2026-09-20:鸿蒙侧从"**时长**上限"改成"**速度**下限",
|
||||
* 本判据跟着改形态(**语义不变**:都是"够快才翻页")。
|
||||
*
|
||||
* 改的原因是实测撞出的**设计错误**,不是调参:时长 = 距离 ÷ 速度,
|
||||
* 它把距离与速度混成一个量 ⇒ 同样的手速下**滑得越远越容易被拒**,
|
||||
* 大屏(平板)尤其严重。设备实测(3184×2232,位移恒 542.6vp):
|
||||
*
|
||||
* 600px/s → 5909ms | 2500px/s → 1429ms
|
||||
* 1200px/s → 3161ms | 5000px/s → 616ms
|
||||
* 1800px/s → 2006ms | 15000px/s → 123ms
|
||||
*
|
||||
* 旧门 700ms 意味着**只有 ≥5000px/s 才过**,而那一档重复测试也只有 2/4 成功
|
||||
* ⇒ 用户报"滑不动/时灵时不灵"。速度门与距离解耦,才是"快滑"该有的形状。
|
||||
*
|
||||
* 两端都要有这道门;**数值不比**((b) 口径)。
|
||||
*/
|
||||
const webFast = webCal.match(/Date\.now\(\)\s*-\s*start\.t\s*<\s*(\d+)/);
|
||||
assert.ok(webFast, 'WebUI 要有"快滑"时间窗(否则慢慢拖也会翻页)');
|
||||
assert.ok(Number(webFast[1]) > 0, 'WebUI 的时间窗应为正数');
|
||||
|
||||
const hFast = harmonyLogic.match(/ms\s*>\s*SWIPE_MAX_DURATION_MS/);
|
||||
assert.ok(hFast, '鸿蒙要有"快滑"时间窗这道门');
|
||||
assert.match(harmonyLogic, /export const SWIPE_MAX_DURATION_MS:\s*number\s*=\s*\d+/,
|
||||
'鸿蒙的时间窗要走具名常量');
|
||||
/*
|
||||
* 鸿蒙侧:门必须存在、且**必须按速度算**。
|
||||
*
|
||||
* 「按速度」是本判据要钉的形状 —— 只断言"有个门"会漏掉这次的错:
|
||||
* 旧的时长门**存在**、常量**有名字**、数值**是正数**,三条全过,
|
||||
* 而它在真机上把正常滑动拒掉了。所以要断言"算的是 dx/ms"。
|
||||
*/
|
||||
assert.match(harmonyLogic, /export const SWIPE_MIN_SPEED:\s*number\s*=\s*[\d.]+/,
|
||||
'鸿蒙的"快滑"门要走具名常量(裸字面量会让"这个数为什么是它"无从追溯)');
|
||||
assert.match(harmonyLogic, /Math\.abs\(dx\)\s*\/\s*ms/,
|
||||
'鸿蒙的快滑门要按**速度**(dx/ms)算,不是按时长 —— ' +
|
||||
'时长把距离与速度混在一起,大屏上会误拒正常滑动(实测 700ms 门只有 ≥5000px/s 能过)');
|
||||
|
||||
/*
|
||||
* ★ 反向断言:鸿蒙**不得**引用 WebUI 的那三个数。
|
||||
|
||||
Reference in New Issue
Block a user