跨端: 上下黑边真的消了 —— 全屏 + 避让是"同一套东西的两半",上次只删了一半
用户第三次报同一条:「你再看看页面底部,那么大的黑色,你看从头到尾都没
修好,你能不能好好看看我给你的示例工程怎么处理上下黑边的」。
## 根因:上一次把"两半"当成了"一件事",删掉一半就以为修好了
`6861934` 的注释白纸黑字写着「★ **刻意不用** `setWindowLayoutFullScreen(true)`」,
理由是「实测过:它确实也消掉黑带,但会连状态栏区域一起吃进布局,于是页签栏
被时钟/电量盖住(截图硬证「07:43」与「收件箱」重叠)」。
那次实测**是真的**,结论**下错了**:被盖住不是"不该全屏",而是
**只做了全屏、没做避让**。示例工程里这两件事本来就是**同一套东西的两半**:
common/.../util/WindowUtil.ets → setWindowLayoutFullScreen(true)
+ getWindowAvoidArea(TYPE_SYSTEM /
TYPE_NAVIGATION_INDICATOR)
features/mine/.../view/MineView.ets:251 → .margin({ top: statusBarHeight + …,
bottom: naviIndicatorHeight })
只做前半 ⇒ 内容跑到状态栏底下没人让(那次退回的原因);
只做后半 ⇒ 黑边照旧(这三次报修的原因)。退回的代价是**黑边留了三天**。
## 实测(模拟器 1256x2760,四页一致)
修前:顶部纯黑 136px、底部纯黑 60px + 手势条 20px
修后:四页**纯黑段均为 0**;y=0..135 是壁纸(时钟浮在上面,正是示例工程的效果)
y=2662+ 壁纸铺到底、底栏浮在手势区之上
## 改了什么
- `entryability/EntryAbility.ets`:拆出 `setupFullScreenWindow()`,
在 `loadContent` **回调里**调(与示例工程同一位置 —— `px2vp` 要用 `getUIContext()`,
那要有已加载内容才拿得到)。全屏 + 读两个避让区 + 订 `avoidAreaChange`。
`setWindowSystemBarEnable(['status'])` 保留(状态栏要看得见),但**不再靠它**消黑边。
- `model/WindowInsets.ts`(新):纯逻辑 `insetsFromAvoidArea()`,不 import SDK ——
判据才能在 node 里直接喂样本验换算。形参叫 `toVp` 而**不是** `px2vp`:
后者是 SDK 已废弃的全局函数名,`harmony-system-api` 按名字扫,同名形参会误报。
- `pages/MainPage.ets`:`@StorageLink(KEY_WINDOW_INSETS)` 订阅;状态栏高度当
**内容层**的 `padding-top`(**不是**根 Stack —— 壁纸必须铺到屏幕四边,根上加
padding 会把壁纸一起缩进去,黑边只是换个地方出现);底栏与内容末尾让开手势条。
reserve 收成**一个** `recomputeNavReserve()`,宽度变化与避让变化两个触发点共用
(转屏只改避让不改宽度,各写一份迟早漏一个)。
## 判据:`test/harmony-window.test.mjs`(8 条)
钉的是"两半必须同时存在"——**只钉一半的话,"退回某一半"照样能全绿通过**,
而那次退回恰恰就是删了一半。纯逻辑 3 条(换算/取不到就是 0 不猜/键名是常量)
+ 接线 4 条(全屏在、避让在、布局真消费了值、纯逻辑模块不许 import SDK)
+ 设备行为 1 条(全屏没把应用搞成白屏)。
**变异自检两个方向都跑过**(这是本轮最该记的一步):
- 删掉 `setWindowLayoutFullScreen`(重演 6861934)⇒ 2 条红 ✓
- 删掉 `getWindowAvoidArea`(只全屏不让位)⇒ 1 条红 ✓
★ 第一版判据**锚错了**:正则直接扫全文,而注释里正好有 `setWindowLayoutFullScreen(true)`
这个串 —— 把真正的调用删掉后判据**仍然全绿**。锚落在"代码对自己的描述"上了。
加 `stripped()` 剥注释后,变异才咬得住。这条与仓里那条"判据的锚不能落在
被守对象的自述上"是同一件事,这次是它的实例。
## 顺带修的两条既有判据(不是放宽,是它们把"当时的字符串"当成了"要守的坑")
- `harmony-nav` ④:留白断言写死了 `bottom: NAV_BAR_BOTTOM` 那个字面串。
它本来要守的是"留白靠 padding 不靠 margin"(margin 在 ArkUI 里加在宽度外面,
100% + margin 会顶出父容器)—— 那是另一件事。改成剥注释后验三段在不在、
离底是否**从** `NAV_BAR_BOTTOM` **起**。变异(padding→margin)仍判红 ✓
- `harmony-widescreen` ④:同上,写死了 `? 0 : NAV_CONTENT_RESERVE`。
改成"宽屏必为 0、窄屏含 NAV_CONTENT_RESERVE(可再加避让)"。
两处都是"加一个正当的避让"与"真犯那个错"会红得一模一样 —— 那就不再是守坑,
是守字符串。
另:`align-refs` / `build-stamp` / `packaging` 三条 broken 是前端 `99a2d7a`
(另一个人改的 `CalendarView.tsx`)带来的,与本轮无关,留给他。
This commit is contained in:
@ -71,6 +71,7 @@ import { PushService, PushRoute } from '../api/PushService';
|
||||
import { CalendarPage } from './CalendarPage';
|
||||
import { SettingsPane } from './SettingsPage';
|
||||
import { LengthMetrics } from '@kit.ArkUI';
|
||||
import { Insets, KEY_WINDOW_INSETS } from '../model/WindowInsets';
|
||||
import {
|
||||
LIST_FADE_LENGTH,
|
||||
NAV_BAR_BOTTOM,
|
||||
@ -1829,6 +1830,19 @@ struct MainPage {
|
||||
* 那是真的;但文档里把它列成"未验渲染",听着像已经画出来了,这是我说得比证据强,已改)。
|
||||
* 现在补上:预设档画渐变、图片档画图 + 压暗。
|
||||
*/
|
||||
/**
|
||||
* 窗口避让区(vp)—— 由 `EntryAbility.setupFullScreenWindow` 读窗口写进来。
|
||||
*
|
||||
* ★ 全屏布局的**另一半**:`setWindowLayoutFullScreen(true)` 让内容铺到屏幕四边
|
||||
* (黑边因此消失),代价是内容会跑到状态栏/手势条底下 —— 这个值就是用来让开的。
|
||||
* 少了它,页签会被时钟盖住(上一次退回的原因);
|
||||
* 少了全屏,黑边就一直在(用户三次报修的原因)。
|
||||
*
|
||||
* `@StorageLink` 而不是 `@StorageProp`:跟随变化(横竖屏/折叠改避让高度)。
|
||||
* 键名走 `KEY_WINDOW_INSETS` 常量 —— `@StorageLink('xxx')` 拼错不报错、只会恒为默认 0,
|
||||
* 那正好是本轮要修的病症(避让永远 0 = 黑边照旧),不能用会静默失效的写法。
|
||||
*/
|
||||
@StorageLink(KEY_WINDOW_INSETS) @Watch('recomputeNavReserve') windowInsets: Insets = new Insets();
|
||||
@State bgPlan: BackgroundPlan = new BackgroundPlan();
|
||||
/**
|
||||
* 背景是否开着 —— 传给每个页面,让它们把**页面底**让出来(变成透明)。
|
||||
@ -1847,6 +1861,11 @@ struct MainPage {
|
||||
* ★ 这个值加在滚动容器的 `contentEndOffset`,**不是**加在窗格的 padding 上:
|
||||
* 窗格必须**满高**,内容才滑得到条底下 —— 否则玻璃条背后只剩壁纸,
|
||||
* 系统材质无东西可糊,看起来就是一块普通浅色面板(用户 2026-09-17 的反馈)。
|
||||
*
|
||||
* ★ 它由 `recomputeNavReserve()` **一处**算出来,两个触发点都调它:
|
||||
* ① 窗口宽度变(`onAreaChange` → isWide 可能变);
|
||||
* ② 避让区变(`@Watch` 订到 windowInsets —— **横竖屏/折叠会只改避让不改宽度**,
|
||||
* 只挂在 ① 上的话,转屏后手势条高度变了而 reserve 没变)。
|
||||
*/
|
||||
@State navReserve: number = NAV_CONTENT_RESERVE;
|
||||
/** 环境变化回调 id(-1 = 没订阅);`lastColorMode` 用来只在真的换向时重算 */
|
||||
@ -1871,6 +1890,25 @@ struct MainPage {
|
||||
this.unwatchEnvironment();
|
||||
}
|
||||
|
||||
/**
|
||||
* 算一遍「内容末尾要让多少位」——**唯一**的算法,两个触发点共用。
|
||||
*
|
||||
* 宽屏:0(没有底部悬浮条,列表不必让位)。
|
||||
* 窄屏:`NAV_CONTENT_RESERVE`(条高 + 离底留白 + 余量)**再加系统手势条**。
|
||||
*
|
||||
* ★ 为什么要加手势条:全屏(`setupFullScreenWindow`)之后屏幕最底那段是系统的
|
||||
* 手势条(实测 20px ≈ 6vp)。底栏自己已经为它让开了(`NavBar` 的
|
||||
* `bottom: NAV_BAR_BOTTOM + navIndicator`),而 reserve 是给**内容末尾**让位的量 ——
|
||||
* 不加的话,条向上移了而内容没跟着,最后一行会被条的下缘压住
|
||||
* (正是 2026-09-16 那轮修过的"看得见、点不到")。
|
||||
*
|
||||
* ★ 两处触发点、一个算法:宽度与避让**是两件独立的事**,
|
||||
* 写成两处赋值就迟早出现"改了一处忘了另一处"(今天就是这样:转屏只改避让不改宽度)。
|
||||
*/
|
||||
private recomputeNavReserve(): void {
|
||||
this.navReserve = this.isWide ? 0 : (NAV_CONTENT_RESERVE + this.windowInsets.navIndicator);
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅系统环境变化(深浅色切换),**重算我们自己算出来的那部分**。
|
||||
*
|
||||
@ -2241,7 +2279,17 @@ struct MainPage {
|
||||
.padding({
|
||||
left: NAV_BAR_SIDE,
|
||||
right: NAV_BAR_SIDE,
|
||||
bottom: NAV_BAR_BOTTOM
|
||||
/*
|
||||
* ★ 底部要让**两段**:自身留白 `NAV_BAR_BOTTOM` + 系统手势条高度。
|
||||
*
|
||||
* 全屏(`setWindowLayoutFullScreen(true)`)之后,屏幕最底那段是系统的
|
||||
* 手势条(实测 20px ≈ 6vp)—— 不额外让开,自绘的玻璃条会压在它上面,
|
||||
* 看起来就是"底栏贴着屏幕边",而且手势区会盖住条的下缘。
|
||||
*
|
||||
* 示例工程同一口径:`MineView.ets:252` 的
|
||||
* `.margin({ bottom: this.globalInfoModel.naviIndicatorHeight })`。
|
||||
*/
|
||||
bottom: NAV_BAR_BOTTOM + this.windowInsets.navIndicator
|
||||
})
|
||||
}
|
||||
|
||||
@ -2391,7 +2439,17 @@ struct MainPage {
|
||||
.padding({
|
||||
left: this.isWide ? Theme.paneGap : 0,
|
||||
right: this.isWide ? Theme.paneGap : 0,
|
||||
top: this.isWide ? Theme.paneGap : 0,
|
||||
/*
|
||||
* ★ 避让区就在这里:状态栏高度当 padding-top。
|
||||
*
|
||||
* 全屏(`setWindowLayoutFullScreen(true)`)之后内容的画布从 y=0 开始 ——
|
||||
* 而 y=0..statusBar 被时钟/电量占着。把这个高度当 padding-top,
|
||||
* 实际绘制的就回到避让区下面了(上一次退回,正是因为少了这一行)。
|
||||
*
|
||||
* 加在**内容层**而不是根 `Stack`:壁纸层是 `Stack` 的底层兄弟,
|
||||
* 根上加了 padding 会把壁纸一起缩进去,黑边只是换个地方出现。
|
||||
*/
|
||||
top: this.isWide ? Theme.paneGap : this.windowInsets.statusBar,
|
||||
bottom: this.isWide ? Theme.paneGap : 0
|
||||
})
|
||||
|
||||
@ -2402,10 +2460,13 @@ struct MainPage {
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
/*
|
||||
* ★ 根 `Stack` **不带避让 padding** —— 壁纸层要铺到屏幕四边(全屏的意义就在这里)。
|
||||
* 避让加在内容层与底栏上(见各自的 padding),这样黑边才真的消失。
|
||||
*/
|
||||
.onAreaChange((oldValue: Area, newValue: Area) => {
|
||||
this.isWide = (newValue.width as number) >= 768;
|
||||
/* 宽屏没有底部导航条 ⇒ 内容不需要为它让位 */
|
||||
this.navReserve = this.isWide ? 0 : NAV_CONTENT_RESERVE;
|
||||
this.recomputeNavReserve();
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user