首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
〖共创稿事节〗HarmonyOS 7 新特性实战(41):地图何时全屏——分栏里的临时合并与退出复原
📅 2026/10/31 17:43:14
✍️ 爱科研究院
👁 阅读 3,247
# 〖共创稿事节〗HarmonyOS 7 新特性实战41地图何时全屏——分栏里的临时合并与退出复原平板分栏做好之后点「在地图上选」打开 3D 地图屏幕上出现了三样东西左栏被地图盖住右栏还挂着另一张地图页中间那条 4vp 的拖动手柄还骑在分隔线上。一张地图两个窗口一条缝。这件事的难点不在「怎么让地图变大」。把覆盖层的容器宽度从 948 改成整窗是一行的事。难的是三个问题什么时候该变大、变大之后怎么回去、以及变大期间那些按宽度摆位的东西怎么办。一、分栏之后地图变成了两张先把现场说清楚。首页探索页和右栏那张地图页是两个不同的组件左栏是ShanhaiWorldPageContent地图覆盖层长在它内部右栏是MAP_PAGE这个 destination渲染的是ShanhaiWorldMapPage。分栏状态下点开地图覆盖层只铺满它所在的容器也就是左栏。于是位置打开地图前打开地图后左栏navBar探索页地图覆盖层948vp 宽右栏content地图常驻页地图常驻页没变栈没动过中间4vp 拖动手柄4vp 拖动手柄还骑着屏幕宽 1440vp左栏 948vp右栏 492vp中间一条线。用户点的是「看地图」看到的是一张被切成两半的地图。二、先回答「谁该全屏」分栏不是全局开关是逐页决定的。这个应用里各页的归属很明确页面该不该全屏理由3D 地图沉浸探索全屏看的是整幅地貌切一半就没法读异兽详情分栏左栏列表要一直在方便连点着看古籍目录 / 篇章分栏目录是导航正文是被导航的内容主题弹层 / 超分查看器全屏弹层天然盖住一切判据其实只有一句这一页的内容是不是「一张图」。是图就全屏是「列表 内容」就分栏。地图属于前者而且是唯一一个「内容在左栏、右栏反而是另一张地图」的页面——这也是它最先暴露问题的原因。三、为什么不把覆盖层搬出去最直接的想法是把覆盖层从首页里搬出来挂到整窗那一层。试过三条路都不划算。搬到 Navigation 外面覆盖层的云幕过渡、区域停留定时器、场景就绪回调全绑在首页组件上搬出去等于把这些状态一起搬家云幕那套动效要重写。做成NavDestinationMode.DIALOG的 destination主题弹层和超分查看器就是这么做的hideNavBar(true)一收内容区拿到整窗宽度DIALOG 自然满屏。机制是现成的但同样要把云幕和停留定时器搬进 destination而且云幕是首页的招牌动效回归风险不小。清空栈再切Stack模式语义最直白但退出时重建栈会重放转场动画观感容易花。最后选的是第四条不搬组件把分栏临时合并。左栏拉满整窗、右栏让到 0覆盖层还铺在自己的容器里只是这个容器此刻就是整窗。代价是三个布局属性都要跟着改而且退出时必须能原样回去。好处是覆盖层的代码一行没动。四、三个属性缺一不可三个都是Navigation的布局属性语义见 Navigation 组件参考navBarWidth是导航栏宽度navBarWidthRange是它的合法区间minContentWidth是内容区的最小宽度。三者叠在一起才决定最终谁占多宽。.navBarWidth(this.effectiveNavBarWidth()) .navBarWidthRange([SPLIT_MIN_NAV_BAR, this.effectiveNavBarUpperBound()]) .minContentWidth(this.effectiveMinContentWidth())三个都走取值函数函数内部判断「现在是不是沉浸态」。少改任何一个都会留一条缝属性平时沉浸时漏改的表现navBarWidth用户拖出来的 948窗口宽 1440地图停在 948右边半屏还是地图页navBarWidthRange上限1000窗口宽1440 被夹回 1000右边剩 440minContentWidth3600右栏被硬留 360左栏最多到 1080第二条最容易被忽略navBarWidth传了整窗宽但navBarWidthRange的上限还是 1000框架会把它夹回去表现是「明明写了 1440量出来 1000」。第三条同理minContentWidth是硬约束不归零就永远差一截。三条改完实测布局树里NavBar [0,86][2879,1920]窗口 2880 宽右栏NavDestination数量从 1 变成 0——那一栏被压到零宽连节点都不渲染了。五、合并的时机在云幕里「沉浸态」用什么判据是这个改动里最容易写错的地方。第一版用的是showWorldMap也就是用户点下「在地图上选」那一刻。结果打开地图的瞬间右栏先被抽走、左栏先撑满云幕还没升起来——一次没有遮挡的重排肉眼就是「屏幕闪了一下」。正确的判据是mapOverlayMounted覆盖层的实际挂载态private isImmersiveMapActive(): boolean { return this.mapOverlayMounted this.shouldSplitLayout(); }这个值由云幕过渡在CLOUD_CLOSE_GATE_MS760ms那一刻翻转而云幕本身的上升动画是 1100ms。也就是说合并发生在云幕已经铺了大半的时候两张图对比就能看出差别400ms 时云幕只盖左栏、分隔线还在900ms 时云幕已经是整幅。合并这一下没有单独的视觉事件它搭在云幕上升的过程里。关闭时同理方向相反showWorldMap先翻 false云幕升起760ms 后才卸载覆盖层并复原分栏——复原也在云幕里。第二个条件是shouldSplitLayout()手机单栏时左栏本来就是整窗再去拉满只是白改一次布局属性。它只看窗口宽高不看测量结果。六、开关不能读结果判据里不能用isSplitLayout()。那个方法量的是「两栏是不是真的都在屏幕上」Navigation宽减去 navBar 宽差值超过 100vp 才算分栏。合并之后 navBar 就等于整窗差值是 0它会翻成 false——然后「是不是沉浸态」变成 false合并被取消navBar 缩回去差值又超过 100又变成沉浸……在两帧之间来回抖。这是这个项目里第二次栽在同一件事上。第一次是分栏开关本身拿测量结果去驱动mode()Stack态两栏等宽、判据恒为假永远进不了Split。结论是一样的——开关读窗口结果读测量两者不能混。七、复原不需要代码一开始打算「记住合并前的宽度退出时恢复」。写的时候发现根本不需要/** * 左栏实际生效的宽度。 * * 沉浸时拉满整窗其余时候就是用户拖出来的 splitNavBarWidth。 * **这里只读不写** —— 退出沉浸不需要「恢复」动作因为用户那个值从头到尾没被动过。 */ private effectiveNavBarWidth(): number { if (this.isImmersiveMapActive()) { return this.navigationWidthVp; } return this.splitNavBarWidth; } /** 左栏宽度上限。沉浸时必须放到整窗否则 navBarWidth 会被 range 夹回 1000。 */ private effectiveNavBarUpperBound(): number { if (this.isImmersiveMapActive()) { return this.navigationWidthVp; } return SPLIT_MAX_NAV_BAR; } /** 右栏最小宽度。沉浸时归零否则 Navigation 一定给右栏留 360vp。 */ private effectiveMinContentWidth(): number { if (this.isImmersiveMapActive()) { return SPLIT_IMMERSIVE_MIN_CONTENT; } return SPLIT_MIN_CONTENT; }沉浸时返回窗口宽但不把它写回splitNavBarWidth。用户拖出来的那个值从头到尾没被动过退出时函数走 else 分支自然就是原值。「不写回」这件事必须守住写回去的代价是永久的splitNavBarWidth会在下次拖动手势结束时被saveShanhaiSplitWidth()落盘存进去的就是那个被污染的满宽之后每次冷启左栏都是满的。实测退出后NavBar [0,86][1896,1920]与沉浸前的 1896 一致。八、还有两样东西杵在那儿左栏拉满之后有两样东西不会自动消失因为它们不是按 navBar 宽度排版的。拖动手柄。它按splitNavBarWidth定位合并后那个位置正好在地图中间而且它还能拖一拖就从地图里拽出一条缝。条件里加一条Builder SplitHandle() { // 沉浸地图开着时不画手柄此刻左栏被拉满整窗手柄仍按 splitNavBarWidth // 定位会明晃晃地骑在地图中间而且它还能拖一拖就从地图里拽出一条缝。 if (this.isSplitLayout() !this.isOverlayLayerVisible() !this.isImmersiveMapActive()) { Column() { Column() .width(this.splitDragging ? SPLIT_HANDLE_GRIP_WIDTH 1 : SPLIT_HANDLE_GRIP_WIDTH) .height(this.splitDragging ? SPLIT_HANDLE_GRIP_HEIGHT 16 : SPLIT_HANDLE_GRIP_HEIGHT) .borderRadius(SPLIT_HANDLE_GRIP_WIDTH / 2) .backgroundColor(this.isDarkMode ? #8CF2E9D8 : #663B3229) } .width(SPLIT_HANDLE_WIDTH) .height(100%) .justifyContent(FlexAlign.Center) .backgroundColor(#00000000) .position({ x: this.splitNavBarWidth - SPLIT_HANDLE_WIDTH / 2, y: 0 }) .gesture( PanGesture({ fingers: 1, direction: PanDirection.Horizontal, distance: 1 }) .onActionUpdate((event: GestureEvent) { const next: number this.splitDragStartWidth event.offsetX; this.splitNavBarWidth Math.min( this.splitNavBarUpperBound(), Math.max(SPLIT_MIN_NAV_BAR, next) ); }) ) } }宽屏侧栏。侧边导航是 109vp 固定宽在SideShell()里无条件渲染底栏那个hideBottomNav管不到它。不处理的话「全屏」就只是「地图变宽了」左边还立着一条导航。这里没有复用hideBottomNav它在宽屏下还代表「二级页」接进来会让所有二级页在平板上都丢掉侧栏改动面远大于这件事。新加一个hideSideNav只在地图挂载时传 trueProp hideSideNav: boolean false; Builder private SideShell() { Row() { if (!this.hideSideNav) { ShanhaiSideNav({ activeTab: this.activeTab, isDarkMode: this.isDarkMode, onSwitch: (index: number) { this.onSwitch(index); } }) Column() {} .width(1) .height(100%) .backgroundColor(this.dividerColor) .opacity(0.55) } Column() { // 当前 tab 的内容 } .layoutWeight(1) .height(100%) } .width(100%) .height(100%) .backgroundColor(this.shellBackground) }九、返回键把应用退了前面几节都改完地图能铺满整窗、能复原、手柄和侧栏都不见了。然后按了一下返回键——应用退到桌面。排查过程很短因为栈里的东西很清楚右栏那张常驻地图页仍然在栈里只是被压成零宽节点还在系统返回键先派给它而它的onBackPressed干的第一件事是terminateSelf().onBackPressed(() { // 右栏常驻页不吃「出栈」这一下出栈只会让右栏变成一片白。 // 栈里只剩它时按返回 离开应用。 try { const context this.getUIContext().getHostContext() as common.UIAbilityContext; context.terminateSelf(); } catch (err) { /* ... */ } return true; })它的设计前提是「栈里只剩我 用户在主界面按返回」。分栏之后这个前提不成立了左栏才是主界面右栏只是常驻页而地图覆盖层盖在两者之上。修法是在它前面补一句判据if (this.consumeOverlayBack()) { return true; }阅读流那四个 destination目录 / 篇章 / 专题 / 异兽详情早就调了这个方法只有这一页漏了——它是分栏之后才有的页面写的时候没考虑「上面还可能压着一个浮层」。改完再按返回地图收起、分栏复原、应用还在。十、验收三条退出路径地图有三条退出路径每条都要能把分栏复原。退出方式判据结果点页头返回箭头云幕收起后NavBar回到 1896右栏NavDestination回到 1 个通过按系统返回键同上改之前是terminateSelf()通过点地图上的区域停 900ms → 自动收起 → 分栏复原 → 当前区域变「西山」通过这些约束固化成了门禁六组断言合并条件必须同时看挂载态与窗口断点、三个布局属性都必须走取值函数、满宽不得写回状态值、手柄必须排除沉浸态、常驻页的返回键必须先消费浮层、宽屏侧栏必须由独立开关控制。反向验过两处删掉手柄的沉浸判据、把minContentWidth换回常量 360门禁都直接报错。全量 45 个校验脚本复跑 45/45 通过。还剩什么没验模拟器上 3D 场景不可用走的是平面地图回退。平面图按ImageFit.Contain保持比例所以在 1440vp 宽的窗口里地图本身只占中间一条竖带两侧是云海天幕——这不是分栏的问题是回退模式的取景方式但「3D 场景铺满整窗」这件事没有证据。手机档没上机。单栏时判据恒为 false三个布局属性与改前完全一致逻辑上不会有变化但没有截图。还有一处已知的不一致沉浸期间isSplitLayout()会翻成 false右栏宽度是 0测量结果自然不成立。目前受影响的只有「栈空补地图」那个守护而沉浸期间栈里本来就有常驻页不需要补。哪天有别的逻辑开始依赖这个判据得回来重新看一遍。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/10/31 17:43:14
课堂笔记3
2026/10/31 17:43:14
seata怎么使用?
2026/10/31 17:43:14
VibeHacks #06|10 位 Mentor 已集齐,报名截止最后2天!
2026/10/31 18:33:20
Claude Slides:语音驱动的专业幻灯片生成原理与设计工作流重构
2026/10/31 18:33:20
OpenTelemetry全栈日志监控实践指南
2026/10/31 18:33:20
安全合规内容生成的边界与原则
2026/10/31 18:33:19
caocao逆向思路:行为驱动与关键点收敛的逆向工程方法论
2026/10/31 18:33:19
计算机语言中daly什么意思?解析拼写错误与排查方法
2026/10/31 18:28:18
NoFuserEx 实战:ConfuserEx 混淆样本的静态还原与流水线构建
2026/10/31 0:00:18
猫狗目标检测数据集:VOC/COCO/YOLO三格式一体化实战包
2026/10/31 0:00:18
HDU2017多校标程复现:算法竞赛工程化真题沙盒
2026/10/31 0:00:18
Codex Sandbox 隔离环境拆解:AI Agent 的临时实验室如何运作
2026/10/31 0:57:03
全国计算机二级C语言真题PDF高效刷题指南
2026/10/31 0:56:56
requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南
2026/10/30 21:48:31
递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现
2026/10/30 3:44:27
我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
2026/10/30 3:44:25
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026/10/30 3:44:29
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)