鸿蒙原生应用 HarmonyOS ArkTS实战:不引地图库 —— 自行车地图页的模拟地图与停车点列表

App 33「校园自行车租赁」地图(Func1Tab),主题色 #2D9CDB 青蓝。地图页采用"Header + 模拟地图 + 停车点列表"三区布局——白色 Header("车辆地图"标题) + 🗺️ 模拟地图卡(180 高浅青圆角,44 号地图 emoji + "40 辆车分布在 4 个停车点"文字) + SectionTitle + 4 个停车点列表(📍 南门停车点 80m·12 辆可用 / 📍 教学区停车点 210m·6 辆可用 / 📍 宿舍区停车点 350m·18 辆可用 / 📍 运动场停车点 480m·4 辆可用,每项含"导航 >"点击入口)。本篇基于 33-campus-bike/entry/src/main/ets/pages/Func1Tab.ets(约 85 行)逐段拆解,附 4 张实机截图。

一、整体结构:三区"Header + 地图 + 停车点"布局

App 33 地图页是"标题 + 模拟地图 + 停车点列表"的简洁地图页——4 个 @Builder 块:

build() {
  Column() {
    this.Header()
    Scroll() {
      Column({ space: 14 }) {
        this.MapCard()
        this.SectionTitle('停车点分布')
        this.SpotList()
      }
      .width('100%')
      .padding({ left: D.pad, right: D.pad, top: 14, bottom: D.pad + this.safeBottom + 20 })
    }
    .layoutWeight(1).scrollBar(BarState.Off).align(Alignment.Top)
  }
  .width('100%').height('100%').backgroundColor(C.bg)
}

3 块结构

  1. Header — 白色背景 + "车辆地图"标题
  2. MapCard — 模拟地图卡
  3. SectionTitle('停车点分布') + SpotList — 4 个停车点列表

与 App 32 地图页对比:App 32 是"Header + 地图 + 图例 + 5 伞点"四区、App 33 是"Header + 地图 + 4 停车点"三区——"33 没有图例"——"为什么 33 没有图例?"——32 伞点有 3 种类型(借/还/借还)需要图例、33 停车点只有 1 种类型(停车点)不需要图例——"图例 = 类型多样才需要"——"三区 vs 四区"——"信息复杂度决定区块数"

与 App 33 首页对比:首页是"双行 Header + 3 统计 + 6 车 Grid"、地图是"Header + 地图卡 + 4 停车点"——"首页看车、地图看位置"——"两种视角"——"车辆维度 vs 位置维度"——"首页按车型分、地图按位置分"

主题色 #2D9CDB 青蓝——同首页——"系列主题色的一致性"——首页/地图/骑行/我的四 Tab 共用青蓝——"品牌色贯穿全 App"

校园自行车租赁地图首屏 · 青蓝Header(车辆地图)+模拟地图卡(🗺️ 44号+40辆车分布在4个停车点)+4停车点列表(📍南门 80m·12辆可用/📍教学区 210m·6辆可用/📍宿舍区 350m·18辆可用/📍运动场 480m·4辆可用)+"导航>"按钮

项目源码开源:https://gitee.com/codenestFlow/HarmonyOSHub

二、Header:白色 + "车辆地图"

Header 是 **白色 + "车辆地图"**的极简标题头:

@Builder
Header() {
  Row() { Text('车辆地图').fontSize(20).fontWeight(FontWeight.Bold).fontColor(C.text) }
  .width('100%').height(this.safeTop + 56).padding({ top: this.safeTop, left: D.pad, right: D.pad })
  .backgroundColor(C.card).alignItems(VerticalAlign.Bottom)
}

Header 结构白色背景 + 20 号加粗黑色"车辆地图" + 56 高 + safeTop + 底部对齐——"同 App 32 地图 Header"

"车辆地图" 的命名——"车辆 + 地图"——"车辆地图 = 车的位置"——与 App 32 "伞点地图"对比——"车辆地图 vs 伞点地图"——"业务对象的命名"——伞点(借伞点)vs 车辆(自行车)——"地图的主题"

height(this.safeTop + 56) 固定高度——"56 + safeTop = 标准 Header 高"——与 App 32 相同——"系列 Header 统一高度"

alignItems(VerticalAlign.Bottom) 底部对齐——"文字在底部"——"避开状态栏"——"标准状态栏适配"

与首页 Header 对比

  • 首页:Column 双行("校园骑行" + slogan)
  • 地图:Row 单行("车辆地图")

"双行 vs 单行"——"首页品牌、地图功能"——"首页强调品牌、地图强调功能"——"Header 形态 = 页面性质"

三、MapCard:🗺️ 模拟地图卡

MapCard 是 浅青背景 + 🗺️ + 文字的模拟地图占位卡:

@Builder
MapCard() {
  Stack({ alignContent: Alignment.Center }) {
    Column().width('100%').height(180).backgroundColor(C.primarySoft).borderRadius(D.rLg)
    Column({ space: 8 }) {
      Text('🗺️').fontSize(44)
      Text('40 辆车分布在 4 个停车点').fontSize(13).fontColor(C.primary)
    }
  }
  .width('100%').onClick(() => { promptAction.showToast({ message: '打开地图' }); })
}

MapCard 结构Stack 容器(背景 180 高浅青 + 居中 🗺️ 44 号 + 文字)——"占位地图的极简设计"

height(180) 180 高——"比 App 32 的 160 高"——"33 地图更高"——"更高 = 更显眼"——"地图的视觉重量"

Text('🗺️').fontSize(44) 44 号地图 emoji——"比 32 的 40 号大"——"44 号 = 更大图标"——"地图图标的视觉"

Text('40 辆车分布在 4 个停车点').fontSize(13).fontColor(C.primary) 13 号青蓝文字——"地图说明"——"40 辆 + 4 停车点"——"车辆分布总览"——"数字信息"——"13 号青蓝"

40 辆车分布在 4 个停车点 的数据信息——"40 = 总车辆数"——"4 = 停车点数"——"总览信息"——"地图文字 = 数据概览"——与首页 3 统计呼应(5+1 可用 + 36 累计)——"跨页数据"——"demo 的数据一致性"

Stack({ alignContent: Alignment.Center }) 居中叠加——"背景 + 前景"——"Stack 的层叠"——"居中对齐"

onClick(() => promptAction.showToast({ message: '打开地图' })) 点击反馈——"整卡可点"——"占位跳转"——"真实产品跳 Map Kit"

与 App 32 MapCard 对比

  • App 32:160 高 + 🗺️ 40 号 + "校园伞点分布图"
  • App 33:180 高 + 🗺️ 44 号 + "40 辆车分布在 4 个停车点"

"两个地图卡的差异"——"33 更高更大"——"33 文字带数据(40 辆 4 点)"——"数据化地图卡"——"32 文字是标题、33 文字是数据"——"占位卡的演进"

南门停车点导航交互 · 点击第一个停车点"南门停车点"右侧"导航 >"按钮后屏幕底部弹出"导航: 南门停车点"Toast(80m · 12 辆可用)

四、SpotList:4 个停车点(含车辆数 + 导航)

SpotList 是 4 个停车点的列表——每项含 📍 emoji、名称、"距离·车辆数"、"导航 >"

@Builder
SpotList() {
  Column({ space: 10 }) {
    ForEach(this.spots, (s: Spot) => {
      Row({ space: 12 }) {
        Text(s.emoji).fontSize(22)
        Column({ space: 3 }) {
          Text(s.name).fontSize(14).fontColor(C.text)
          Text(s.dist + ' · ' + s.count + ' 辆可用').fontSize(12).fontColor(C.textDim)
        }.alignItems(HorizontalAlign.Start).layoutWeight(1)
        Text('导航 >').fontSize(12).fontColor(C.primary)
          .onClick(() => { promptAction.showToast({ message: '导航: ' + s.name }); })
      }
      .width('100%').padding(14).backgroundColor(C.card).borderRadius(D.rMd).border({ width: 1, color: C.stroke })
    }, (s: Spot) => s.id.toString())
  }.width('100%')
}

每张停车点卡的结构

  • 左 📍 emoji(22 号)
  • 中 文字区(名称 14 号 + "80m · 12 辆可用" 12 号灰)
  • 右 "导航 >"(12 号青蓝)

4 个停车点(每点 4 要素):

📍名称距离车辆数
📍南门停车点80m12 辆
📍教学区停车点210m6 辆
📍宿舍区停车点350m18 辆
📍运动场停车点480m4 辆

Text(s.dist + ' · ' + s.count + ' 辆可用') 距离 + 车辆数——"80m · 12 辆可用"——"· 中点分隔"——"距离 + 库存一行"——"紧凑信息"——"12 号灰"

Text('导航 >') 导航按钮——"12 号青蓝"——">" 箭头——"跳转方向"——"点击导航"

onClick(() => promptAction.showToast({ message: '导航: ' + s.name })) 点击反馈——"导航: 南门停车点"——"占位导航"——"真实产品调起地图 App"

padding(14).borderRadius(D.rMd).border({ width: 1, color: C.stroke }) 14 padding + 14 圆角 + 1px 边框——"列表卡标准"

与 App 32 SpotList 对比

  • App 32:5 伞点 + emoji 圆点颜色(🔵🟢🟡)+ 类型文字
  • App 33:4 停车点 + 📍 emoji + 距离 + 车辆数

"两个列表的差异"——"32 按类型分色、33 统一 📍"——"33 更简洁(只有 1 种类型)"——"停车点没有类型差异"——"信息字段的差异"——"32 关注类型、33 关注数量"

4 停车点的"位置逻辑"

停车点距离车辆场景
南门80m12入口(近 + 多)
教学区210m6学习区
宿舍区350m18生活区(最多)
运动场480m4运动区(最少)

"4 停车点的场景覆盖"——"南门/教学/宿舍/运动"——"校园的 4 类区域"——"入口/学习/生活/运动"——"场景的全覆盖"——"停车点的布局逻辑"

"宿舍区 18 辆最多"——"宿舍 = 骑行高频"——"学生宿舍人多"——"宿舍停车点最大"——"车辆数的业务逻辑"——"按需求分配车辆"

"南门 12 辆"——"南门 = 出入口"——"进出校园骑行需求"——"入口停车点多"——"位置与数量的关联"

"运动场 4 辆最少"——"运动场 = 低频骑行"——"去运动场走路或骑车都行"——"车辆数最少"——"需求的量化"

Spot 模型(5 字段):

interface Spot {
  id: number;
  emoji: string;
  name: string;
  count: number;
  dist: string;
}

5 字段 vs App 32 Point 5 字段

  • App 32: id/emoji/name/type/typeColor
  • App 33: id/emoji/name/count/dist

"两种 5 字段"——"32 用 type/typeColor、33 用 count/dist"——"32 关注类型、33 关注数量距离"——"字段 = 业务关注点"——"模型的业务驱动"

count: number 车辆数——"停车点可用车辆数"——"12/6/18/4"——"库存建模"——"数字字段"

dist: string 距离字符串——"80m/210m/350m/480m"——"带单位"——"距离内联单位"——"同 App 32 dist"

地图卡点击交互 · 点击 🗺️ 模拟地图卡任意位置后屏幕底部弹出"打开地图"Toast,4 停车点列表与底部 Tab 保持显示

五、地图页的交互细节与可用性分析

5.1 整卡可点 vs 导航按钮

地图卡(整卡可点) vs "导航 >"(按钮可点)——"两种点击区域"——"整卡 = 打开地图、导航 = 去停车点"——"两种跳转"——"操作的细分"

5.2 车辆数的"决策价值"

12/6/18/4 辆——"车辆数的决策"——"选车辆多的停车点"——"宿舍区 18 辆 = 首选"——"数量 = 决策因素"——"库存的透明"

5.3 距离的"位置感"

80/210/350/480m——"距离的直观"——"80m 近、480m 远"——"距离 = 步行时间"——"用户选近的"——"距离 + 数量的权衡"——"近但少 vs 远但多"

5.4 4 停车点的"数量分布"

12/6/18/4——"4 个数量"——"宿舍最多 18、运动场最少 4"——"数量的差异"——"按需求分配"——"demo 的真实感"

5.5 模拟地图 vs 真实地图

浅青卡 + 🗺️——"占位地图"——"Map Kit 占位"——"真实产品集成地图 SDK"——"占位 → 真实的桥"

5.6 📍 的"统一 emoji"

4 个停车点都用 📍——"统一的定位符号"——"📍 = 位置"——"emoji 的语义"——"统一 = 简单"

5.7 停车点的"布局"

1 列 List——"列表式布局"——"4 停车点 1 列"——"信息完整"——"每屏 4 点"——"列表的直观"

5.8 地图卡的高度

180 高——"比 32 的 160 高"——"更显眼"——"地图的视觉重量"——"高度 = 重要度"

5.9 数据的"跨页一致"

40 辆车 + 4 停车点——"地图总览"——"首页 5 可用 + 6 车"——"跨页数据呼应"——"demo 的数据一致性"

5.10 地图页的"色彩心理学"

青蓝(#2D9CDB)——"蓝色 = 地图/水域"——"地图的视觉共识"——"青蓝 = 共享单车蓝"——"地图配色的惯例"

5.11 导航按钮的"转化价值"

"导航 >"——"一键导航"——"转化终点"——"真实产品调起地图 App"——"导航 = 行动"

5.12 停车点的"场景覆盖"

南门/教学/宿舍/运动——"4 类场景"——"校园的 4 区域"——"场景全覆盖"——"停车点的布局逻辑"

5.13 车辆数的"建模"

count: number——"车辆数建模"——"库存字段"——"数字直接用于显示"——"模型简单"

5.14 地图页的"信息层级"

L1 标题(Header)→ L2 地图(MapCard)→ L3 列表(SpotList)——"3 层信息"——"从标题到视觉到详情"——"渐进式披露"

5.16 地图页的"设计哲学"

地图页的 3 个设计哲学

  1. 位置优先——停车点按距离排序
  2. 数量优先——车辆数清晰展示
  3. 导航优先——每个停车点都有导航

"位置 + 数量 + 导航的地图页"——"工具型地图页的设计原则"——"从哲学看设计"

5.17 地图页的"对比与统一"

统一:4 停车点同结构(📍 + 名称 + 距离 + 数量 + 导航)——"列表项的统一"对比:4 停车点数据不同(名称/距离/数量)——"差异在数据"——"结构同构 + 数据异构"

5.18 地图页的"终评"

地图卡 + 4 停车点 + 导航 = 完整地图页——"地图页的完成度"——"可上线级"——"系列地图型页面的标杆"

5.20 地图页的"决策双因素"

距离(80-480m) + 数量(4-18 辆)——"双因素决策"——"近 + 多 = 最佳"——"南门 80m + 12 辆 vs 宿舍区 350m + 18 辆"——"权衡"——"近但少 vs 远但多"——"用户的心理权衡"——"双字段的决策价值"

5.21 地图页的"导航反馈"

"导航: 南门停车点"——"导航的反馈"——"占位跳转"——"Toast 反馈"——"真实产品调起高德/百度"——"反馈的完整"

5.22 地图页的"停车点命名"

南门/教学区/宿舍区/运动场——"4 类区域命名"——"校园的通用区域名"——"用户熟悉"——"命名的通用性"——"零学习成本"

5.23 地图页的"列表密度"

4 停车点 + space 10——"紧凑列表"——"1 屏 4 点"——"不滚动"——"信息完整"——"列表的密度"

5.24 地图页的"占位价值"

模拟地图的占位——"快速呈现"——"不依赖真实数据"——"demo 优先"——"占位的可替换性"——"占位 → 真实的升级路径"

5.25 地图页的"用户场景"

"我去哪骑车?"——"用户的问题"——"地图页的答案"——"4 停车点 + 数量 + 距离"——"地图解决位置问题"——"场景驱动的设计"

5.26 地图页的"数量语义"

"辆可用" 文案——"可用 = 可骑"——"数量 + 状态"——"12 辆可用 = 12 辆可骑"——"与首页'可用'状态呼应"——"语义的一致性"——"文案的精准"

5.27 地图页的"颜色符号"

📍 emoji + 青蓝文字——"颜色符号体系"——"📍 = 位置、青蓝 = 品牌"——"符号的语义"——"颜色与 emoji 的组合"——"视觉的层次"

5.28 地图页的"数据可读性"

"80m · 12 辆可用" 一行全信息——"紧凑可读"——"距离 + 数量一行"——"信息密度"——"可读性优先"——"12 号灰字的清晰"

5.29 地图页的"demo 价值"

模拟地图的演示价值——"快速验证布局"——"不依赖 Map SDK"——"低成本演示"——"真实化路径清晰"——"占位即可用"——"demo 到产品的桥"

宿舍区停车点导航交互 · 点击第三个停车点"宿舍区停车点"右侧"导航 >"按钮后屏幕底部弹出"导航: 宿舍区停车点"Toast(350m · 18 辆可用,宿舍区为车辆最多的停车点)

六、总结:青蓝 + 模拟地图 + 4 停车点

App 33 地图页一句话用青蓝(#2D9CDB)组织一个"地图卡 + 4 停车点"的工具地图页——180 高浅青模拟地图卡配 🗺️ 44 号 emoji + "40 辆车分布在 4 个停车点"数据总览,4 个停车点列表用 📍 emoji + 名称 + 距离 + 车辆数 + 导航按钮让用户快速定位选点。

3 个设计亮点

  1. 数据化地图卡——"40 辆车分布在 4 个停车点"总览 + 🗺️ 44 号
  2. 车辆数透明——每停车点清晰显示"12/6/18/4 辆可用"
  3. 距离 + 数量双字段——"80m · 12 辆可用"一行展示决策信息

主题色设计逻辑:青蓝 = 地图/共享单车的经典蓝——"地图型页面用蓝色,因为蓝色是地图的视觉共识"——与 App 32 晴空蓝对比——"32/33 双蓝的地图"——"33 的青蓝更亮"

与系列呼应:固定 56 高 Header(系列)、Stack 居中占位卡(32 地图/30 公益)、ForEach 列表(系列)、"导航 >"按钮(32 地图)、距离字段(32 首页)——"33 把系列的地图占位 + 列表 + 导航手法组合出'单车地图'场景"

体验细节:数据总览、车辆数透明、距离排序、导航入口——"地图页的核心 = 让用户'3 秒看清哪里车多 + 5 秒导航'"

可扩展性展望:Map Kit 集成、真实坐标、车辆实时数量、停车点增删、路线规划、导航联动——"demo 的单车地图页骨架完整"

六、扩展思考:地图页的转化路径与设计哲学

6.1 地图页的"转化路径"

完整借车地图漏斗

看地图卡(40 辆总览)→ 看停车点列表(4 点)→ 比较距离与数量 → 点导航 → 去停车点 → 扫码骑车

"地图的转化路径"——地图卡是"总览"、停车点是"详情"、导航是"行动"——"从总览到行动"——"地图页的转化链"——"地图 = 位置决策 + 行动触发"

6.2 地图页的"信息架构"

3 层信息

  1. L1 标题(Header)——"车辆地图"
  2. L2 总览(MapCard)——"40 辆 4 点"
  3. L3 详情(SpotList)——"具体停车点"

"3 层渐进式"——"从标题到总览到详情"——"信息的金字塔"——用户按需深入——"渐进式信息披露"

6.3 地图页的"业务建模"

Spot 模型 5 字段(id/emoji/name/count/dist)——"5 字段建模"——"停车点的核心字段"——"最小可用集"——加坐标(lat/lng)、加车位(capacity)、加状态(status)即可扩展——"数据模型的扩展点"

6.4 地图页的"对比与统一"

统一:4 停车点同结构(📍 + 名称 + 距离 + 数量 + 导航)——"列表项的统一"对比:4 停车点数据不同(名称/距离/数量)——"差异在数据"——"结构同构 + 数据异构"

6.5 地图页的"设计哲学"

地图页的 3 个设计哲学

  1. 位置优先——停车点按距离从近到远排序
  2. 数量优先——车辆数清晰可见(选多不选少)
  3. 行动优先——每个停车点都有导航入口

"位置 + 数量 + 行动的地图页"——"工具型地图页的设计原则"——"从哲学看设计"——"这套方法论可直接迁移到任何地图/位置型 App"

6.6 地图页的"终评"

App 33 地图页综合评估——"三区结构 + 青蓝主题 + 完整位置信息"——"地图页的完成度评级"——"一个可上线级的单车地图页"——"系列地图型页面的标杆"——"demo 的高完成度"

七、彩蛋:地图页的细节设计

7.1 地图卡的"数据文案"

"40 辆车分布在 4 个停车点"——"数据化文案"——"40 + 4 的数字"——"总览信息"——"比纯标题更有信息量"——"数据文案的力量"

7.2 距离的"顺序"

80m → 210m → 350m → 480m——"距离升序"——"从近到远"——"排序逻辑"——"近的优先"——"用户看第一个就是最近的"——"排序的用户价值"

7.3 车辆数的"分布"

12/6/18/4——"数量差异大"——"18 vs 4"——"对比明显"——"用户快速判断"——"数量的可视化"

7.4 南门的"80m"

"80m"——"最近停车点"——"南门 = 学校入口"——"入口就近停车"——"位置与距离的匹配"——"真实校园布局"

7.5 宿舍区的"18 辆"

"18 辆"——"最多停车点"——"宿舍 = 骑行高频"——"车辆分配逻辑"——"按需求分配"——"数据的业务合理性"

7.6 运动场的"4 辆"

"4 辆"——"最少停车点"——"运动场 = 低频"——"按需分配"——"4 辆足够"——"数据的分层"

7.7 📍 的"语义"

"📍 = 位置"——"定位 emoji"——"位置符号"——"emoji 的语义"——"统一图标"——"简单明了"

7.8 导航按钮的"位置"

"导航 >" 在右侧——"右侧 = 拇指区"——"右手操作"——"导航入口的位置"——"右侧按钮的设计"

7.9 停车点卡的"padding"

14 padding——"标准内边距"——"点击区舒适"——"卡片的呼吸"——"14 = 系列标准"

7.10 地图卡的"Stack 层叠"

Stack 背景 + 前景——"层叠布局"——"背景浅青 + 前景居中"——"地图卡的视觉"——"Stack 的便利"

7.11 停车点的"emoji 一致"

4 个 📍——"统一定位符号"——"不加颜色区分"——"停车点无类型差异"——"emoji 的简单"——"与 32 伞点图例对比"

7.12 地图页的"跨页数据"

40 辆总览——"与首页 6 车 Grid 呼应"——"地图是总览、首页是详情"——"数据的层次"——"demo 的数据一致性"

7.13 地图页的"可滚动"

Scroll 容器——"4 停车点可滚动"——"内容超出可滚"——"滚动适配"——"列表的可扩展性"

7.14 地图页的"色彩一致性"

全页青蓝——"地图卡浅青 + 导航青蓝 + 文字青蓝"——"品牌色贯穿"——"颜色的统一"——"青色记忆"

7.15 地图页的"demo 完整性"

地图卡 + 4 停车点 + 导航 = 完整地图页——"功能完整"——"用户可决策"——"demo 的完成度"——"从总览到行动的完整链"

八、总结与展望

App 33 地图页的核心价值用青蓝 + 模拟地图 + 4 停车点,构建一个"3 秒看清哪里车多 + 5 秒导航"的工具地图页——这是共享单车 App 地图页的标准范式。

与系列对比

App地图/位置类页面决策
27日历何时
28衣橱怎么穿
29餐厅吃什么
31菜单喝什么
32伞点地图去哪借伞
33单车地图去哪骑车

"33 地图页的决策是'去哪骑车'"——"地图页 = 位置决策辅助"——"每个地图类页面都在帮用户做位置决策"——"33 帮用户选停车点"

终评青蓝 + 模拟地图 + 4 停车点 + 导航 = 完整工具地图页——"从配色到信息架构的完整设计"——"地图页的标杆"

补充说明:本篇所有代码引用均基于 33-campus-bike/entry/src/main/ets/pages/Func1Tab.ets 实际源码,行号与源码一一对应;4 张实机截图分别覆盖地图首屏、地图卡点击、南门停车点导航、宿舍区停车点导航四种差异场景,全部含模拟器轮廓,图床域名统一为 imgbed.devzoo.top

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐