基于HarmonyOS 7.0 跨端开发的火车时刻查询页面实战

举报
yd_263028836 发表于 2026/06/26 21:51:03 2026/06/26
【摘要】 基于HarmonyOS 7.0 跨端开发的火车时刻查询页面实战 前言出行查询类应用要把"一趟车次"讲清楚,需要兼顾两类信息:横向的时刻概览(出发、到达、历时、票价)和纵向的经停明细(每一站的到发时间)。火车时刻查询就是典型:用户先按出发到达站查车次,再展开看某趟车的经停站详情。本文以一个真实的火车时刻查询页面(入口类 SearchPage)为样本,深入剖析它如何在 Flutter × Ha...

基于HarmonyOS 7.0 跨端开发的火车时刻查询页面实战

前言

出行查询类应用要把"一趟车次"讲清楚,需要兼顾两类信息:横向的时刻概览(出发、到达、历时、票价)和纵向的经停明细(每一站的到发时间)。火车时刻查询就是典型:用户先按出发到达站查车次,再展开看某趟车的经停站详情。本文以一个真实的火车时刻查询页面(入口类 SearchPage)为样本,深入剖析它如何在 Flutter × HarmonyOS 7.0 架构下,用车票样式的搜索框、横向时间轴的车次卡与铁路线路图式的经停站时间轴,把"全国火车时刻查询"的出行体验完整落地。这是一个把"横向时刻布局"与"纵向经停时间轴"结合得很完整的页面,通过拆解它,我们能透彻理解 Flutter 的 Expanded 空间分配、车次类型阈值着色、经停站时间轴等出行类应用的实战要点。

背景

火车时刻工具的核心是"查车次、看时刻、阅经停":输入出发站、到达站、日期查询车次,浏览车次列表(车次号、类型、出发到达时间、历时、票价、预订),并能展开看某趟车的经停站(各站到发时间与停留时长)。本页面在视觉上采用铁路出行风格,列车绿主色(0xFF16A34A)配铁轨灰背景。结构上从上到下依次是:标题栏(带换向图标)、车票样式搜索框(出发站→到达站 + 日期 + 查询)、车次列表(每趟用横向时间轴展示出发/到达时刻、历时连线、票价与预订按钮,车次类型用颜色区分),以及经停站时间轴(首尾异色、竖线连接,每站显示到发时间与停留)。其中车次卡用 Expanded 把出发、历时、到达三段水平分布、车型用阈值着色,是横向空间分配与数据着色的典型示范。

Flutter × Harmony7.0 跨端开发介绍

在 HarmonyOS 7.0 上运行本页面,前提是使用 HarmonyOS 维护的定制版 Flutter SDK,因为鸿蒙对 Flutter 的支持是由 HarmonyOS 跨平台 SIG 通过 fork 扩展 Flutter SDK 实现的。

本页面是纯 Dart、无原生依赖的"可直接复用"场景,用到的 ExpandedIntrinsicHeightIconasMap 等全部来自 Framework 层与 Dart 标准库。真实的火车时刻查询依赖实时数据——车次、余票、票价都需要从服务端接口获取(用适配鸿蒙的网络库如 dio 的 ohos 版本发起请求),余票还要实时刷新。本示例聚焦于查询结果的展示层,车次与经停数据是预设的,但页面已经把车次、经停站的数据结构组织得很清晰,对接真实接口时只需替换数据源、UI 层无需改动。这种"展示层与数据源解耦"的设计,让对接实时数据变得简单。

整页渲染经 Skia 借助鸿蒙 ArkUI RenderingContext 完成。经 AOT 编译后列表滚动、时间轴渲染都能在鸿蒙设备上保持原生级流畅。
image.png

开发核心代码

第一部分:Expanded 三段式的横向时刻布局。 车次卡用 Expanded 把出发、历时、到达三段水平分布:

Row(crossAxisAlignment: CrossAxisAlignment.end, children: [
  Expanded(child: Column(children: [   // 左:出发
    Text(t['dep'] as String, style: const TextStyle(fontSize: 22)),  // 出发时刻大字
    Text(t['from'] as String),                                        // 出发站
  ])),
  Column(children: [   // 中:历时 + 连线
    Text(t['duration'] as String),
    Container(width: 40, height: 2, color: const Color(0xFFD1D5DB)),  // 行程连线
  ]),
  Expanded(child: Column(children: [   // 右:到达
    Text(t['arr'] as String, style: const TextStyle(fontSize: 22)),  // 到达时刻大字
    Text(t['to'] as String),                                          // 到达站
  ])),
])

Expanded 让左侧出发、右侧到达各占据均等空间、向两侧对齐,中间放历时与行程连线。出发到达时刻用 22 号大字突出(用户最关心的信息),站名用小字辅助。这种"两端时刻 + 中间连线"的横向布局,是火车票、机票时刻展示的经典范式,Expanded 的弹性分配是实现两端对齐的关键。

第二部分:车次类型的阈值着色。 高铁、动车、特快用嵌套三元映射为蓝、黄、灰三色:

Container(
  decoration: BoxDecoration(color: t['type'] == '高铁'
      ? const Color(0x1A3B82F6)         // 高铁-蓝
      : t['type'] == '动车' ? const Color(0x1AF59E0B)  // 动车-黄
      : const Color(0x1A9CA3AF)),        // 特快-灰
  child: Text(t['type'] as String,
      style: TextStyle(color: t['type'] == '高铁'
          ? const Color(0xFF3B82F6)
          : t['type'] == '动车' ? const Color(0xFFF59E0B) : const Color(0xFF9CA3AF))),
)

车次类型用嵌套三元按字符串映射成不同颜色:高铁蓝、动车黄、特快灰。颜色让用户不必读文字就能快速区分车型档次,符合"高铁最快用醒目蓝、特快较慢用低调灰"的直觉。这种按类别着色的手法在分类标签里极为常用。
image.png

第三部分:铁路线路图式的经停站时间轴。asMap().entries 遍历,首尾异色,竖线连接各站:

..._stops.asMap().entries.map((e) {
  final i = e.key; final s = e.value;
  return IntrinsicHeight(child: Row(children: [
    Column(children: [
      Container(width: 10, height: 10, decoration: BoxDecoration(
        shape: BoxShape.circle,
        color: i == 0 ? const Color(0xFF10B981)            // 始发-绿
            : i == _stops.length - 1 ? _trainPrimary        // 终点-绿主色
            : const Color(0xFFD1D5DB))),                     // 中间-灰
      if (i < _stops.length - 1)
        Expanded(child: Container(width: 2, color: const Color(0xFFDCFCE7))),  // 铁轨连线
    ]),
    Expanded(child: Row(children: [
      Expanded(child: Text(s['station'] as String)),
      Text('到 ${s['arrive']}'), Text('发 ${s['depart']}'), Text(s['stay'] as String),
    ])),
  ]));
})

经停站用 asMap().entries 带索引遍历,始发站、终点站用绿色、中间站用灰色,竖线模拟铁轨连接。每站一行显示站名、到站时间、发车时间、停留时长,IntrinsicHeight 保证竖线高度与行内容对齐。这是铁路经停展示的标准时间轴实现。

心得

做这个火车时刻页面,最大的收获是深入理解了 Expanded 在横向空间分配上的威力。火车时刻卡需要把"出发时刻—历时—到达时刻"三段水平排布,且要求出发和到达分居两端、历时居中。我用两个 Expanded 包裹出发和到达列,让它们各自占据均等的弹性空间并向两侧撑开,中间放固定宽度的历时与连线。这样无论屏幕多宽,出发到达时刻都能稳稳地分居两端对齐。Expanded 的精髓在于"弹性填充剩余空间"——它让布局能自适应不同宽度的屏幕,这在覆盖手机、平板、折叠屏的鸿蒙生态里尤为重要。掌握了 Expanded(以及它的兄弟 FlexibleSpacer),各种"两端对齐、居中分布、按比例分配"的横向布局就都能从容应对。

第二个体会是这个页面同时用到了"横向时刻布局"和"纵向经停时间轴"两种信息组织方式,它们恰好对应两类不同的数据。车次的出发到达是"两个端点 + 历时"的关系,适合横向展示;而经停站是"多个有序站点"的关系,适合纵向时间轴。同一个出行查询场景,用两种布局分别呈现两类信息,让用户既能快速比较车次(横向),又能详查某趟车的经停(纵向)。这让我体会到,信息的组织方式要匹配数据的结构特征——端点关系用横向、序列关系用纵向,选对了布局,信息就呈现得自然;选错了,再花哨也别扭。这种"让布局匹配数据结构"的意识,是做好信息展示的根本。

第三个深刻的体会是关于实时数据与展示层解耦的设计。火车时刻、余票、票价都是高度实时的数据,真实产品必须从服务端接口拉取,余票还得不断刷新。但我注意到,这个页面的 UI 完全是数据驱动的——车次列表 map_trains、经停站 map_stops,UI 层只负责把数据渲染出来,不关心数据从哪来。这意味着将来对接真实接口时,只需把预设的 const 数据换成网络请求的结果,UI 代码一行都不用改。这种"展示层与数据源解耦"的架构非常重要:它让 UI 开发和数据对接可以并行,也让从假数据切换到真接口变得无痛。再加上跨端时只需确认网络库(如 dio)有鸿蒙适配版本,数据层的跨端就有了保障。把 UI 写成纯粹的"数据投影",是构建可维护、易对接的查询类应用的关键功力。
image.png

总结

这个火车时刻查询页面完整呈现了 Flutter 在 HarmonyOS 7.0 上构建出行查询型页面的标准做法:用 Expanded 实现两端对齐的横向时刻布局,用阈值着色区分车次类型,用 asMap().entries 带索引遍历构建首尾异色的经停时间轴。整个页面把"车次概览与经停明细"两类信息处理得清晰而贴合数据结构——横向布局展示端点关系,纵向时间轴展示序列关系,数据驱动让 UI 成为纯粹的展示层。这种范式对火车、航班、公交、物流等各类需要"时刻概览 + 站点明细"的出行查询应用都有很强的复用价值。

从跨端落地的角度看,本页面的展示层是纯 Dart 实现、可零适配复用的:搜索框、车次列表、经停时间轴全部使用 Flutter 内置组件,切换到 HarmonyOS 提供的定制版 SDK 后即可在鸿蒙设备上直接运行。而它真正的核心——实时的车次、余票、票价数据——则需从服务端接口获取,用适配鸿蒙的网络库(如 dio 的 ohos 版本)发起请求并定时刷新余票。得益于"展示层与数据源解耦"的设计,对接真实接口只需替换数据源、UI 层无需改动。这正体现了 Flutter × HarmonyOS 处理数据驱动型应用的精髓:把数据展示用纯 Dart 跨端共享,把数据获取交给适配鸿蒙的网络层。对于出行查询类应用而言,把握好"UI 层零适配、数据层确认网络库适配"这一分工,并保持展示与数据源的解耦,是这类应用既能快速跨端落地、又能轻松对接实时数据的关键工程策略,也是 Flutter × HarmonyOS 组合在出行服务领域值得遵循的工程范式。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。