跨端: 统一顶栏组件 + 按压反馈(真跑通)+ 滑动判定从"时长门"改"速度门"
用户四条意见,逐条都是真问题,且**两条是我写完没生效**:
① 「所有的顶栏都应该应用玻璃圆框效果」② 「抽象一个统一的顶栏组件出来吧」
③ 「你不觉得发件箱太大了吗」④ 「各个组件带响应点击、滑动等的动画了吗」
⑤ 「还有转场动画呢?」⑥ 「还有其他行为都要一一对齐,例如邮件展示页面」
★ ① ② 统一顶栏 `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:
@ -9,7 +9,7 @@
|
||||
import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||
import { ApiClient, ApiError } from '../api/ApiClient';
|
||||
import { Theme } from '../common/Theme';
|
||||
import { GlassCardModifier, PaneModifier } from '../common/Surface';
|
||||
import { AppHeader, CompositeModifier, GlassCardModifier, PaneModifier, PressFeedbackModifier } from '../common/Surface';
|
||||
import { AmIcon } from '../common/Icons';
|
||||
import { WideSidebar } from './WideSidebar';
|
||||
import { MailDetailView } from './MailDetailPage';
|
||||
@ -629,7 +629,17 @@ struct InboxTab {
|
||||
.width('100%').height(78)
|
||||
.padding({ left: 10, right: 12 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
.attributeModifier(GlassCardModifier.of(this.bgActive))
|
||||
/*
|
||||
* ★★ 两个属性集要**合成一个**再挂(实测撞出来的)。
|
||||
*
|
||||
* `.attributeModifier()` 一个组件只能挂一个,链两个是后者覆盖前者 ——
|
||||
* 第一版就是链了两句,结果玻璃与按压反馈只生效一个,而编译器不报错。
|
||||
* WebUI 那边是 CSS 类名天然叠加;ArkUI 是单一插槽,叠加要显式做。
|
||||
*/
|
||||
.attributeModifier(CompositeModifier.of([
|
||||
GlassCardModifier.of(this.bgActive),
|
||||
PressFeedbackModifier.of()
|
||||
]))
|
||||
.borderRadius(Theme.radiusCard)
|
||||
.border({ width: 1, color: Theme.border })
|
||||
.clip(true)
|
||||
@ -882,38 +892,32 @@ struct SentTab {
|
||||
.onClick(() => { this.toggleExpanded(g.key); })
|
||||
}
|
||||
|
||||
/** 顶栏右侧:已加载封数 */
|
||||
@Builder
|
||||
SentCountTrailing() {
|
||||
if (this.loaded > 0) {
|
||||
Text(this.loaded + ' 封').fontSize(12).fontColor(Theme.textSubtleFor())
|
||||
.margin({ right: 8 })
|
||||
}
|
||||
}
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
Row() {
|
||||
Text('发件箱').fontSize(20).fontWeight(FontWeight.Bold).fontColor(Theme.textPrimary)
|
||||
Blank()
|
||||
if (this.loaded > 0) {
|
||||
Text(this.loaded + ' 封').fontSize(12).fontColor(Theme.textSubtleFor())
|
||||
}
|
||||
}
|
||||
.width('100%').height(56).padding({ left: 16, right: 16 })
|
||||
/*
|
||||
* 顶栏底:**不自己铺一层实心白** —— 与 WebUI 一致。
|
||||
* ★ 2026-09-20 改用**统一顶栏**(用户:「所有的顶栏都应该应用玻璃圆框效果」)。
|
||||
*
|
||||
* ★★ 2026-09-19 修(用户:「一个横着过去的白条,我真的服了」+
|
||||
* 「期望:融进背景」)。
|
||||
*
|
||||
* WebUI 的顶栏**自身没有底色** —— 它只有 `border-b border-gray-200`
|
||||
* (`ContactPanel.tsx:64`、`CommTabs.tsx:40`、`CalendarView.tsx:295`),
|
||||
* 底色由它所在的**面板**提供。壁纸开启时那层面板是玻璃色
|
||||
* (`index.css:876` `html[data-bg='on'] .bg-white`),顶栏就跟着变玻璃。
|
||||
*
|
||||
* 鸿蒙这边顶栏写死了 `Theme.surface`(实心白)⇒ 无论壁纸开没开,
|
||||
* 顶上都是**一条不通明的白带**,横贯屏幕、与下面的玻璃内容脱开 ——
|
||||
* 就是用户说的那条白条。
|
||||
*
|
||||
* 改成与**页面底**同一套口径(`bgActive ? Transparent : surface`):
|
||||
* · 壁纸关:仍是不透明白(与下面内容同色,看不出这条带的边界);
|
||||
* · 壁纸开:透明,壁纸从顶栏透上来 —— 这才是"融进背景"。
|
||||
* 这里此前有过一段反复:先写死 `Theme.surface`(实心白条 → 用户
|
||||
* 「一个横着过去的白条,我真的服了」),再改成"与页面底同口径"的透明通栏。
|
||||
* 两次都还是**通栏**,而底栏是悬浮圆框 —— 同一根竖轴上两种风格。
|
||||
* 现在统一走 `AppHeader`:圆框 + 与底栏同值的高度/圆角/侧留白。
|
||||
*/
|
||||
.attributeModifier(GlassCardModifier.of(this.bgActive))
|
||||
/* 下边框对齐 WebUI:顶栏与内容的分界靠这条线,不靠底色差 */
|
||||
.border({ width: { bottom: 1 }, color: Theme.border })
|
||||
AppHeader({
|
||||
title: '发件箱',
|
||||
showBack: false,
|
||||
active: this.bgActive,
|
||||
topInsetPx: 0,
|
||||
trailing: this.SentCountTrailing
|
||||
})
|
||||
|
||||
if (this.loading) {
|
||||
Column() { LoadingProgress().width(32).height(32) }
|
||||
@ -1092,6 +1096,19 @@ struct PermissionTab {
|
||||
}
|
||||
}
|
||||
|
||||
/** 顶栏右侧:待决策徽标(橙=有人被卡住,不是"有东西要读") */
|
||||
@Builder
|
||||
PendingTrailing() {
|
||||
if (this.requests.length > 0) {
|
||||
Text(this.requests.length + ' 待决策')
|
||||
.fontSize(12).fontColor(Theme.accentFg)
|
||||
.backgroundColor(Theme.warnFg)
|
||||
.borderRadius(10)
|
||||
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
|
||||
.margin({ right: 8 })
|
||||
}
|
||||
}
|
||||
|
||||
@Builder
|
||||
RequestCard(req: PermissionRequest) {
|
||||
Column() {
|
||||
@ -1162,41 +1179,14 @@ struct PermissionTab {
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
Row() {
|
||||
Text('授权').fontSize(20).fontWeight(FontWeight.Bold).fontColor(Theme.textPrimary)
|
||||
Blank()
|
||||
if (this.requests.length > 0) {
|
||||
// 待决策的橙色徽标:这不是"有东西要读",是"有人被卡住"
|
||||
Text(this.requests.length + ' 待决策')
|
||||
.fontSize(12).fontColor(Theme.accentFg)
|
||||
.backgroundColor(Theme.warnFg)
|
||||
.borderRadius(10)
|
||||
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
|
||||
}
|
||||
}
|
||||
.width('100%').height(56).padding({ left: 16, right: 16 })
|
||||
/*
|
||||
* 顶栏底:**不自己铺一层实心白** —— 与 WebUI 一致。
|
||||
*
|
||||
* ★★ 2026-09-19 修(用户:「一个横着过去的白条,我真的服了」+
|
||||
* 「期望:融进背景」)。
|
||||
*
|
||||
* WebUI 的顶栏**自身没有底色** —— 它只有 `border-b border-gray-200`
|
||||
* (`ContactPanel.tsx:64`、`CommTabs.tsx:40`、`CalendarView.tsx:295`),
|
||||
* 底色由它所在的**面板**提供。壁纸开启时那层面板是玻璃色
|
||||
* (`index.css:876` `html[data-bg='on'] .bg-white`),顶栏就跟着变玻璃。
|
||||
*
|
||||
* 鸿蒙这边顶栏写死了 `Theme.surface`(实心白)⇒ 无论壁纸开没开,
|
||||
* 顶上都是**一条不通明的白带**,横贯屏幕、与下面的玻璃内容脱开 ——
|
||||
* 就是用户说的那条白条。
|
||||
*
|
||||
* 改成与**页面底**同一套口径(`bgActive ? Transparent : surface`):
|
||||
* · 壁纸关:仍是不透明白(与下面内容同色,看不出这条带的边界);
|
||||
* · 壁纸开:透明,壁纸从顶栏透上来 —— 这才是"融进背景"。
|
||||
*/
|
||||
.attributeModifier(GlassCardModifier.of(this.bgActive))
|
||||
/* 下边框对齐 WebUI:顶栏与内容的分界靠这条线,不靠底色差 */
|
||||
.border({ width: { bottom: 1 }, color: Theme.border })
|
||||
/* 同 SentTab:统一走 AppHeader(圆框 + 与底栏同族的几何与材质) */
|
||||
AppHeader({
|
||||
title: '授权',
|
||||
showBack: false,
|
||||
active: this.bgActive,
|
||||
topInsetPx: 0,
|
||||
trailing: this.PendingTrailing
|
||||
})
|
||||
|
||||
if (this.loading) {
|
||||
Column() { LoadingProgress().width(32).height(32) }
|
||||
@ -1427,7 +1417,16 @@ struct CommPage {
|
||||
})
|
||||
}, (key: string) => key)
|
||||
}
|
||||
.width('100%')
|
||||
/*
|
||||
* ★ 宽度用 `calc(100% - 2×TAB_BAR_SIDE)`,**不是 `width('100%')` + `margin`**。
|
||||
*
|
||||
* ArkUI 里 `width('100%')` 是按父容器算的,再叠 `margin` 会**向右溢出**
|
||||
* 而不是把条挤窄 —— 实测(模拟器 3184px、密度 2.875):
|
||||
* 页签条实际占 x=231..1151,与面板同宽;TAB_BAR_SIDE(16vp=46px) 完全没生效
|
||||
* ⇒ 看起来仍是"通栏",只是多了圆角,与底部悬浮条(左右各留 16vp)不齐。
|
||||
* 把留白算进宽度里,左右才真的各让出 16vp。
|
||||
*/
|
||||
.width(`calc(100% - ${TAB_BAR_SIDE * 2}vp)`)
|
||||
/*
|
||||
* ★★ 2026-09-20(用户:「顶部的收件箱发件箱和授权为什么不做成悬浮玻璃?」)
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user