第5篇|窗口适配、安全区、暗色模式与隐私保护实战

摘要:这篇不是泛泛而谈的“5 个小技巧”,而是我在一个鸿蒙相册、地图、保险箱项目发布前,集中处理的 5 个真实问题:刘海屏遮挡、暗色模式不同步、多任务卡片泄露相册缩略图、保险箱离开后仍保持解锁、平板布局继续沿用手机底部导航。它们单看都不算大 bug,但一旦进入上架或真机联调阶段,几乎每一个都会掉分。我的处理原则只有一条:不要硬编码,不要只修当前页面,要把系统状态收进统一状态源,再按页面职责消费。

在这轮收口里,我把问题拆成了五类,先给结论表:

检查项 真实翻车方式 根因 修复动作
安全区 状态栏或手势条遮挡内容 用固定像素写死间距 从 Window 获取 avoid area,存入 AppStorage
暗色模式 系统切黑后页面仍是浅色 页面各自维护颜色状态 在 EntryAbility 统一监听,再用 @StorageLink 下发
相册防窥 多任务卡片暴露最近照片 进入和离开相册没有管理防窥状态 进入相册订阅,离开相册解除
保险箱锁定 切走再切回仍是解锁态 锁定状态只在页面内存里挂着 离开 Tab 立即回收解锁态
多设备布局 平板上仍是手机底部导航 布局没有按宽度分发 用窗口宽度切分手机和大屏布局

1. 安全区不是 UI 微调,而是系统输入

这个问题最先暴露在真机上:顶部标题被状态栏压住,底部 Tab 又被手势条顶上来。模拟器里看不明显,一换刘海屏和全面屏就暴露。

我一开始也想过直接补 padding({ top: 44 }) 之类的固定值,但这种写法只能在一台设备上看起来“差不多”,换设备马上翻车。更稳的做法是把系统窗口数据当成状态源,在 EntryAbility 统一取一次,再分发给页面。

// EntryAbility.ets
onWindowStageCreate(windowStage: window.WindowStage): void {
  const win = windowStage.getMainWindowSync();
  const props = win.getWindowProperties();

  AppStorage.setOrCreate('superImage.windowWidthPx', props.windowRect.width);
  AppStorage.setOrCreate('superImage.windowHeightPx', props.windowRect.height);

  const systemArea = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);
  AppStorage.setOrCreate('superImage.safeAreaTopPx', systemArea.topRect?.height ?? 0);
  AppStorage.setOrCreate('superImage.safeAreaBottomPx', systemArea.bottomRect?.height ?? 0);
}

页面里不要自己猜安全区,而是直接消费这几个状态:

@StorageLink('superImage.safeAreaTopPx') private safeAreaTopPx: number = 0;
@StorageLink('superImage.safeAreaBottomPx') private safeAreaBottomPx: number = 0;

这样顶栏、底栏、全屏图片区都能用同一套数据,不会出现 A 页修好了、B 页还在被挡的情况。

2. 暗色模式跟不跟随,关键不在颜色值,在状态归谁管

第二个问题更常见:系统已经切到深色,页面还是浅色背景;或者主页面跟随了,地图、弹窗、卡片却各用各的颜色,形成“阴阳界面”。

这类 bug 本质上不是配色没写完,而是主题状态没有统一入口。我的处理方式是:仍然在 EntryAbility 监听系统窗口状态变化,把是否深色写进 AppStorage,页面只消费,不自己判断。

onWindowStageCreate(windowStage: window.WindowStage): void {
  const win = windowStage.getMainWindowSync();
  const props = win.getWindowProperties();
  AppStorage.setOrCreate('superImage.isDarkMode', props.isDarkMode);

  win.on('windowEvent', (event) => {
    if (event === window.WindowEvent.DARK_MODE_CHANGED) {
      const updatedProps = win.getWindowProperties();
      AppStorage.setOrCreate('superImage.isDarkMode', updatedProps.isDarkMode);
    }
  });
}

页面侧只保留一份状态入口:

@StorageLink('superImage.isDarkMode') private isDarkMode: boolean = false;

然后背景色、文字色、地图模式都跟着这一份状态走:

Column() {
  // 页面内容
}
.backgroundColor(this.isDarkMode ? '#1a1a2e' : '#f5f5f5')
.foregroundColor(this.isDarkMode ? '#e0e0e0' : '#333333')
private mapOptions: mapCommon.MapOptions = {
  dayNightMode: mapCommon.DayNightMode.AUTO,
}

这一步的关键不是“写了深色主题”,而是保证整个应用只认一份主题状态。

3. 相册防窥要跟页面切换联动,不能只在相册页里写一个开关

第三个问题是在隐私巡检时暴露的:用户刚看过私密照片,切到后台或任务卡片时,缩略图可能被带出来。这种问题平时不一定有人提,但一旦真机演示,很容易直接判定为高风险体验。

我的处理不是给相册页单独加一个“防窥”按钮,而是把它和导航状态联动。进入相册时订阅防窥状态,离开相册时解除。

import { dlpAntiPeep } from '@kit.DeviceSecurityKit';

private galleryAntiPeepSubscribed: boolean = false;
@State private galleryAntiPeepActive: boolean = false;
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐