三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

第19篇|沉浸式首页:地图、玻璃层、信息卡片的层级关系

第19篇|沉浸式首页:地图、玻璃层、信息卡片的层级关系

第19篇|沉浸式首页:地图、玻璃层、信息卡片的层级关系

第 5 天开始看「双镜记忆相机」的首页体验。这个首页不是把地图放满屏这么简单,它同时承担了四件事:地图可操作、顶部信息可读、中间浮动控件不挡手、底部记忆入口和导航不遮住地图。

本文会按“运行环境、视觉层级、源码验证、失效风险”四步拆开。先看页面效果,再回到Index.ets对照MapComponentgetMapBottomOverlayInset()HitTestMode,这样读者能知道每一层为什么放在当前位置。

配图说明:这张图用于观察地图可拖动区域、附近景点按钮、最近记忆 Dock 和底部导航之间的遮挡关系。

官方依据

官方 MapKit 文档《MapComponent(地图组件)》说明,MapComponent需要传入mapOptions,并通过回调获取MapComponentController。这决定了首页第一层必须是稳定的地图容器;其他 UI 只是覆盖层,不能反过来破坏地图手势。

本文讨论的是 HarmonyOS Stage 模型下的 MapKit 首页,不是 Web 地图,也不是静态背景图。判断页面质量时,不能只看“像不像沉浸式”,还要看地图手势、覆盖层触摸和底部安全区域是否同时成立。

开发环境版本号与失效风险提示

先把本文的可复现边界放在前面,避免只看截图就迁移到不一致的工程里。

项目本文使用的版本或配置验证依据
DevEco Studio6.1+README.md的开发环境说明
HarmonyOS SDK6.1.0(23)README.mdbuild-profile.json5
默认产品targetSdkVersion 6.1.0(23)compatibleSdkVersion 6.1.0(23)build-profile.json5
模拟器产品targetSdkVersion 6.0.2(22)compatibleSdkVersion 6.0.2(22)build-profile.json5
工程模型Stage 模型,apiType: stageModeentry/build-profile.json5
设备类型phonetablet2in1entry/src/main/module.json5
地图能力com.huawei.service.mapkit,名称为Map Kitbuild-profile.json5

失效风险也要写清楚:缺少 MapKit 能力、定位权限被拒绝、网络不可用、窗口尺寸在手机/平板/2in1 之间切换,都会影响首页表现。后文会分别给出配置检查、触摸命中检查和降级策略。

首页四层结构

沉浸式首页最容易乱的地方,是把“视觉层级”和“触摸层级”混在一起。这个页面的正确拆法是:地图底图层先保证可交互,顶部信息层只接管自己的卡片区域,浮动控件层只接管按钮,底部入口层通过 inset 处理遮挡。

配图说明:四层结构对应MapComponent、顶部信息、浮动控件、底部 Dock 和导航。重点不是叠得多,而是每一层只拿自己该拿的触摸。

主要文件是entry/src/main/ets/pages/Index.ets

  • mapOptions:地图初始经纬度、缩放级别、手势开关。
  • buildMapTab():首页地图和覆盖层的组合入口。
  • getMapBottomOverlayInset():为底部 Dock 和导航预留地图区域。
  • buildActiveTabContent():地图页叠加景点助手、详情弹层等更高层内容。

核心结构可以简化成这样:

Stack({ alignContent: Alignment.TopStart }) { Column() { MapComponent({ mapOptions: this.mapOptions, mapCallback: this.mapCallback }) .width('100%') .layoutWeight(1) if (this.getMapBottomOverlayInset() > 0) { Column() .height(this.getMapBottomOverlayInset()) .backgroundColor($r('app.color.ml_background')) } } // 顶部信息层:周年回忆、错误提示、详情卡片 // 中间控件层:定位、地图操作 // 底部入口层:最近记忆 Dock + 底部导航 }

这里最关键的不是Stack,而是每一层的职责边界。地图层负责可交互底图;顶部Scroll只在有内容时出现,并设置hitTestBehavior(HitTestMode.Block);浮动控件和底部容器使用HitTestMode.Transparent,把空白区域的触摸还给地图。

源码定位

为了避免读者只看文字猜实现,下面这张图给出源码搜索入口。打开项目后优先搜索mapOptionsbuildMapTabgetMapBottomOverlayInset,基本就能定位首页地图的主线。

配图说明:源码截图用于确认本文不是凭截图推断,而是直接对应Index.ets中的状态、函数和资源引用。

mapOptions不是装饰配置,它决定地图是否真能承担首页底图的角色。项目里保留了滚动、缩放、旋转、倾斜四类手势,同时关闭系统默认定位控件,把定位入口交给自定义浮层:

private mapOptions: mapCommon.MapOptions = { position: { target: { latitude: 30.25113, longitude: 120.15515 }, zoom: 12.6 }, dayNightMode: mapCommon.DayNightMode.AUTO, scrollGesturesEnabled: true, zoomGesturesEnabled: true, rotateGesturesEnabled: true, tiltGesturesEnabled: true, scaleControlsEnabled: false, myLocationControlsEnabled: false, logoScale: 0.9 };

这段代码和官方MapComponent文档是对应的:地图组件通过mapOptions获得初始位置、缩放和交互能力,通过mapCallback拿到控制器。如果后续为了避免误触把手势关掉,首页就只剩一张“像地图的背景图”,产品价值会下降。

底部 inset 为什么必须单独算

getMapBottomOverlayInset()的逻辑很直白:

private getMapBottomOverlayInset(): number { if (this.shouldUseSideNavigation()) { return 0; } const baseInset = this.getMapPhotoDockMemories().length > 0 ? 272 : 128; return baseInset + this.getSafeAreaBottomVp(); }

手机形态下,底部同时可能出现「最近记忆」和四个 Tab。如果地图仍然铺到底部,用户会看到 Marker、定位按钮和导航挤在一起。这里用272/128 + safeArea明确预留空间:有最近记忆时留更多,没有时只给导航留底。切到侧边导航后返回 0,因为底部已经不承担主导航。

信息卡片和地图如何共存

地图页不只有地图,还会出现附近景点、智能助手、记忆详情等更高层内容。它们可以覆盖在地图上,但不能把整张地图变成不可拖拽的背景。

配图说明:这张图展示了信息卡片覆盖地图后的阅读形态。卡片区域需要接管滚动和点击,卡片之外的地图区域仍然应该可拖拽。

buildMapTab()里有三种命中策略:

Scroll() { // 周年回忆、错误提示、详情卡片 } .hitTestBehavior(HitTestMode.Block) Column() { Blank() this.buildMapFloatingControls() Blank() } .hitTestBehavior(HitTestMode.Transparent) Column({ space: 14 }) { if (this.getMapPhotoDockMemories().length > 0) { this.buildMapPhotoGroupDock() } this.buildBottomNavigation() } .hitTestBehavior(HitTestMode.Transparent)

顶部信息层使用Block,因为卡片内部需要滚动和点击,不能把事件透传给地图。浮动控件和底部容器使用Transparent,因为只有按钮或卡片本身应该响应,空白区域要还给地图。

复现矩阵

场景预期结果对应代码
手机竖屏,无最近记忆地图底部只预留导航高度baseInset = 128
手机竖屏,有最近记忆最近记忆 Dock 和导航都不遮地图baseInset = 272
平板或 2in1 宽窗口地图不再追加底部 insetshouldUseSideNavigation()返回 true
详情卡片打开卡片滚动不拖动地图HitTestMode.Block
底部空白区域拖动地图仍响应拖拽HitTestMode.Transparent
未授权定位顶部出现可解释错误,不影响地图底图mapErrorText信息层
无 MapKit 能力或地图初始化失败显示降级内容,不能让页面空白mapCallback/错误状态

如果要把这篇文章里的实现迁到自己的应用,建议先跑这张表,而不是先改视觉。只要这些场景稳定,后续换图标、换玻璃色、改卡片圆角,都不容易伤到地图交互。

自测清单

这篇对应的自测很适合做成真机检查单:

  • 拒绝定位权限,确认mapErrorText出现在顶部信息层。
  • 选择一个记忆点,确认showDetailPanel打开后卡片可以滚动。
  • 有最近记忆和无最近记忆两种状态下,分别观察底部留白。
  • 横屏或平板宽度触发侧边导航后,确认getMapBottomOverlayInset()为 0。
  • 暂时关闭网络或 MapKit 能力时,确认页面有降级内容,不出现空白页。

本地还可以用下面这条命令核对版本和能力:

rg -n "targetSdkVersion|compatibleSdkVersion|apiType|deviceTypes|com.huawei.service.mapkit" ` build-profile.json5 entry/build-profile.json5 entry/src/main/module.json5

当前核对结果包含targetSdkVersion: 6.1.0(23)compatibleSdkVersion: 6.1.0(23)apiType: stageModedeviceTypes: phone/tablet/2in1capability: com.huawei.service.mapkit。这就是本文后面讨论 MapKit 首页层级、触摸命中和底部占位的前提。

小结

沉浸式首页的质量,不在于把组件都堆到一张图上,而在于层级之间互不抢职责。地图是第一层,信息卡是状态投影,浮动控件是快捷操作,底部导航是路径入口。

只要这四层的触摸、占位和显隐规则清楚,页面就能从手机扩展到平板和 2in1,而不是每个设备重新写一套首页。

参考依据:

  • 华为开发者文档:《MapComponent(地图组件)》
  • 项目源码:entry/src/main/ets/pages/Index.ets
← 返回列表