跨端: 上下黑边真的消了 —— 全屏 + 避让是"同一套东西的两半",上次只删了一半
用户第三次报同一条:「你再看看页面底部,那么大的黑色,你看从头到尾都没
修好,你能不能好好看看我给你的示例工程怎么处理上下黑边的」。
## 根因:上一次把"两半"当成了"一件事",删掉一半就以为修好了
`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:
@ -16,9 +16,11 @@
|
||||
import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
|
||||
import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||
import { window } from '@kit.ArkUI';
|
||||
import { BusinessError } from '@kit.BasicServicesKit';
|
||||
import { ApiClient } from '../api/ApiClient';
|
||||
import { PushService } from '../api/PushService';
|
||||
import { NotificationLedger } from '../model/PushContract';
|
||||
import { Insets, KEY_WINDOW_INSETS, insetsFromAvoidArea } from '../model/WindowInsets';
|
||||
|
||||
const DOMAIN = 0x0000;
|
||||
|
||||
@ -102,41 +104,102 @@ export default class EntryAbility extends UIAbility {
|
||||
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
|
||||
|
||||
/*
|
||||
* ★ 底部黑带 = 系统的**导航栏区域**(手势条),不是我们画的。
|
||||
* ★ 窗口配置(全屏 + 避让)改到 loadContent 之后 —— 见 setupFullScreenWindow。
|
||||
*
|
||||
* 症状(用户 2026-09-17 真机截图):屏幕最下面一条纯黑。
|
||||
* 根因:窗口默认给系统导航条留了位置,而那块区域在深色主题下是黑的;
|
||||
* 界面于是看起来底下多出一条黑带,壁纸/面板到不了屏幕底部。
|
||||
* ── 这里曾经写着「刻意不用 setWindowLayoutFullScreen(true)」并记了一个**错误的结论** ──
|
||||
* 原文:「实测过:它确实也消掉黑带,但会连状态栏区域一起吃进布局,于是页签栏被
|
||||
* 时钟/电量盖住(截图硬证「07:43」与「收件箱」重叠)。」
|
||||
*
|
||||
* 做法:**隐藏系统导航条**(`setWindowSystemBarEnable(['status'])`)——
|
||||
* 底部那条本来就是我们自绘的悬浮玻璃条(用户要求「悬浮 + 四周留白」),
|
||||
* 系统那条黑底手势条不是设计的一部分。隐藏后内容铺满全高(到屏幕底)。
|
||||
* 那次实测本身是真的,**结论下错了**:被盖住不是"不该全屏",而是
|
||||
* **只做了全屏、没做避让**。示例工程(`/tmp/harmonyos-samples-reference`)的
|
||||
* `WindowUtil` 里,`setWindowLayoutFullScreen` 与 `getWindowAvoidArea` 是
|
||||
* **同一套东西的两半** —— 少了后一半,前一半当然是灾难。
|
||||
*
|
||||
* ★ **刻意不用 `setWindowLayoutFullScreen(true)`。**
|
||||
* 实测过:它确实也消掉黑带,但会连状态栏区域一起吃进布局,
|
||||
* 于是页签栏被时钟/电量盖住(截图硬证:「07:43」与「收件箱」重叠)。
|
||||
* 而我们并不需要内容跑到状态栏底下 —— 那要多一层 AvoidArea 避让才收得住,
|
||||
* 为一个没人要求的效果引入一层布局风险不划算。
|
||||
* 状态栏**保留**(时间/电量要看得见),且它占的位置由系统留好。
|
||||
* 而那次退回的代价是**黑边一直在**(用户 2026-09-17 报、2026-09-18 又报
|
||||
* 「你看从头到尾都没修好」)。2026-09-18 实测 1256x2760 四页一致:
|
||||
* 顶部纯黑 136px、底部 60px + 手势条 20px。
|
||||
*
|
||||
* 现在回到示例工程的做法:全屏 + 读避让 + 布局让位(三处配套,见 setupFullScreenWindow)。
|
||||
*/
|
||||
try {
|
||||
const win: window.Window = windowStage.getMainWindowSync();
|
||||
win.setWindowSystemBarEnable(['status']).catch((err: Object) => {
|
||||
hilog.error(DOMAIN, 'testTag', 'setWindowSystemBarEnable failed: %{public}s', JSON.stringify(err));
|
||||
});
|
||||
} catch (err) {
|
||||
hilog.error(DOMAIN, 'testTag', 'window setup failed: %{public}s', JSON.stringify(err));
|
||||
}
|
||||
|
||||
windowStage.loadContent('pages/LoginPage', (err) => {
|
||||
if (err.code) {
|
||||
hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
|
||||
return;
|
||||
}
|
||||
hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
|
||||
/*
|
||||
* ★ 在 loadContent **之后**才配窗口(与示例工程同一位置):
|
||||
* 示例 `BaseAbilityHelper.doOnWindowStageCreate` 在 loadContent 回调里调
|
||||
* `WindowUtil.initialize(windowStage)`。
|
||||
* 理由:`getUIContext()`(`px2vp` 要用)要有已加载的内容才拿得到。
|
||||
*/
|
||||
this.setupFullScreenWindow(windowStage);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 全屏布局 + 读出避让区 —— 消掉上下黑边的**两半**。
|
||||
*
|
||||
* ① `setWindowLayoutFullScreen(true)`:内容铺到屏幕四边。**这就是**消黑边的动作。
|
||||
* ② `getWindowAvoidArea`:读出被状态栏/导航条遮住的高度,写进 AppStorage。
|
||||
* ③ `MainPage` 根容器把它当 padding 的 top/bottom 用 ⇒ 内容让开时钟/手势区。
|
||||
*
|
||||
* 少了 ②③,① 会让页签被时钟盖住(这正是上一次退回的原因);
|
||||
* 少了 ①,黑边就一直在(这正是三次报修的原因)。
|
||||
*/
|
||||
private setupFullScreenWindow(windowStage: window.WindowStage): void {
|
||||
let win: window.Window;
|
||||
try {
|
||||
win = windowStage.getMainWindowSync();
|
||||
} catch (err) {
|
||||
hilog.error(DOMAIN, 'testTag', '取主窗口失败,全屏/避让配置跳过:%{public}s', JSON.stringify(err));
|
||||
return;
|
||||
}
|
||||
/* ── ① 全屏 ── */
|
||||
win.setWindowLayoutFullScreen(true).then(() => {
|
||||
hilog.info(DOMAIN, 'testTag', 'setWindowLayoutFullScreen(true) ok');
|
||||
}).catch((err: BusinessError) => {
|
||||
hilog.error(DOMAIN, 'testTag', 'setWindowLayoutFullScreen failed: %{public}s', JSON.stringify(err));
|
||||
});
|
||||
/*
|
||||
* 状态栏**保留可见**(时间/电量要看得见),只是内容铺到它底下。
|
||||
* 下面这条 `setWindowSystemBarEnable(['status'])` 是上一位留下的:
|
||||
* 它对状态栏仍有效,且留着不会更糟 —— 但它**不是**消黑边的手段
|
||||
* (实测没能消掉底部那 60px + 20px)。一并保留,不再靠它。
|
||||
*/
|
||||
win.setWindowSystemBarEnable(['status']).catch((err: BusinessError) => {
|
||||
hilog.error(DOMAIN, 'testTag', 'setWindowSystemBarEnable failed: %{public}s', JSON.stringify(err));
|
||||
});
|
||||
/* ── ② 读避让 + 监听变化 ── */
|
||||
this.publishInsets(win);
|
||||
try {
|
||||
win.on('avoidAreaChange', () => { this.publishInsets(win); });
|
||||
} catch (err) {
|
||||
hilog.error(DOMAIN, 'testTag', 'avoidAreaChange 订阅失败:%{public}s', JSON.stringify(err));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读一次避让区并写进 `AppStorage`(键 `KEY_WINDOW_INSETS`)。
|
||||
*
|
||||
* ★ 换算必须用 `win.getUIContext().px2vp`,**不能**用全局 `px2vp()` ——
|
||||
* 全局那个已被 SDK 标 `@deprecated`,判据 `harmony-system-api` 对全局调用默认判红
|
||||
* (本仓已踩过这次:`harmony-system-api.test.mjs`)。
|
||||
*/
|
||||
private publishInsets(win: window.Window): void {
|
||||
try {
|
||||
const system: window.AvoidArea = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);
|
||||
const navIndicator: window.AvoidArea = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
|
||||
const uiContext = win.getUIContext();
|
||||
const next: Insets = insetsFromAvoidArea(system, navIndicator, (px: number) => uiContext.px2vp(px));
|
||||
AppStorage.setOrCreate<Insets>(KEY_WINDOW_INSETS, next);
|
||||
hilog.info(DOMAIN, 'testTag', 'insets: statusBar=%{public}d navIndicator=%{public}d',
|
||||
next.statusBar, next.navIndicator);
|
||||
} catch (err) {
|
||||
hilog.error(DOMAIN, 'testTag', '读避让区失败(保持默认 0,黑边照旧):%{public}s', JSON.stringify(err));
|
||||
}
|
||||
}
|
||||
|
||||
onWindowStageDestroy(): void {
|
||||
// Main window is destroyed, release UI related resources
|
||||
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
|
||||
|
||||
Reference in New Issue
Block a user