鸿蒙原生应用实战(四):物流时间线与历史记录——路由传参、Canvas 绘制与列表统计

发布时间:2026/6/13 14:38:25

鸿蒙原生应用实战(四):物流时间线与历史记录——路由传参、Canvas 绘制与列表统计 鸿蒙原生应用实战四物流时间线与历史记录——路由传参、Canvas 绘制与列表统计本文是系列第四篇讲解快递追踪 App 中两个关键页面物流详情时间线TrackDetailPage和历史记录HistoryPage。涵盖路由参数传递、时间线 UI 绘制、空状态设计、底部统计栏等核心内容。一、Traffic Detail — 物流详情页1.1 功能需求物流详情页是用户最常查看的页面需要展示包裹信息卡片快递公司、单号、状态标签、备注物流时间线按时间倒序展示所有物流事件时间线样式竖线 圆点连接最新事件高亮1.2 路由参数接收详情页的数据来自首页列表的点击事件通过router.pushUrl传递// 首页点击跳转ListItem().onClick((){letparams{packageData:item};letopt:RouteOpt{url:pages/TrackDetailPage,params:params};router.pushUrl(opt);})详情页在aboutToAppear生命周期中接收参数StatepackageData:PackageItem{id:0,trackingNo:,company:,status:,statusText:,note:,updateTime:,events:[]};aboutToAppear():void{constparamsrouter.getParams()asRecordstring,Object;if(paramsparams[packageData]){this.packageDataparams[packageData]asPackageItem;}}aboutToAppear vs aboutToDisappear生命周期触发时机用途aboutToAppear组件即将显示初始化数据、接收参数aboutToDisappear组件即将销毁清理资源、保存状态1.3 包裹信息卡片Column(){Row(){Text(this.packageData.company).fontSize(18).fontWeight(FontWeight.Bold)Blank()// 状态标签与首页相同逻辑Text(this.packageData.statusText).fontColor(Color.White).backgroundColor(this.packageData.statustransit?$r(app.color.status_transit):this.packageData.statusdelivered?$r(app.color.status_delivered):$r(app.color.status_exception)).borderRadius(12)}Text(this.packageData.trackingNo)// 单号if(this.packageData.note.length0){Text(备注: this.packageData.note)}}设计要点公司名使用 18fp 大号字体状态标签与首页保持一致的颜色映射备注有条件渲染无备注时不占位二、时间线 UI 组件时间线是物流详情页最核心的 UI 组件我们用 ArkTS 原生组件绘制。2.1 结构分析每条物流记录的结构● ─── 快件已到达深圳分拣中心 │ 2025-01-15 14:30 深圳 │ ● ─── 快件已从广州发出 │ 2025-01-15 10:00 广州 │ ● ─── 已揽收 2025-01-14 20:00 广州三条视觉元素左侧圆点 竖线构成时间线右侧事件描述 时间 地点2.2 实现方案List(){ForEach(this.packageData.events,(event:TrackEvent,index:number){ListItem(){Row(){// 左侧时间线 Column(){Circle().width(12).height(12).fill(index0?$r(app.color.primary):$r(app.color.divider))if(indexthis.packageData.events.length-1){Line().width(2).height(40).backgroundColor($r(app.color.divider))}else{Column().height(40)// 最后一项占位保持对齐}}.alignItems(HorizontalAlign.Center)// 右侧事件信息 Column(){Text(event.desc).fontWeight(index0?FontWeight.Medium:FontWeight.Regular)Row(){Text(event.time)Blank()Text(event.location)}}.margin({left:12})}.alignItems(VerticalAlign.Top)}},(event:TrackEvent,index:number)event.timeindex)}2.3 关键设计细节1. 最新事件高亮// 第一个事件索引0用主题色圆点 加粗文字Circle().fill(index0?$r(app.color.primary):$r(app.color.divider))Text(event.desc).fontWeight(index0?FontWeight.Medium:FontWeight.Regular)2. 竖线组件Line().width(2).height(40).backgroundColor($r(app.color.divider))Line组件是鸿蒙原生的线条绘制组件这里简化为竖线。但 Line 默认水平方向我们需要利用 Column 布局使其垂直。3. 最后一项的处理// 最后一项不画竖线用一个空白 Column 占位保持布局对齐if(indexthis.packageData.events.length-1){Line()...}else{Column().height(40)// 等高的空白占位}4. 垂直对齐// Row 内顶部对齐确保圆点在第二列内容的顶部Row().alignItems(VerticalAlign.Top)2.4 时间线的演进思考当前实现有一个小问题height(40)是固定值如果事件描述文本换行竖线高度会不够。更健壮的方案是使用layoutWeight或动态测量但会增加复杂度。对于大多数物流记录一行文本40vp 的高度是足够的。如果需要更完美的时间线可以考虑// 使用 Column 和 layoutWeight 自适应高度Column(){Circle()...Column().width(2).layoutWeight(1)// 占满剩余空间.backgroundColor($r(app.color.divider))}.width(12)三、历史记录页面HistoryPage3.1 功能需求历史记录页面展示已签收的包裹历史需要历史列表展示公司、单号、备注、签收时间清空按钮一键清空历史空状态无历史时友好提示底部统计总件数、快递公司数、覆盖周期3.2 数据模型interfaceHistoryItem{id:number;trackingNo:string;company:string;note:string;signTime:string;// 签收时间}3.3 清空功能Button($r(app.string.clear_history)).fontColor($r(app.color.status_exception))// 红色文字.backgroundColor(Color.Transparent).onClick((){this.historyList[];// 赋值为空数组触发 State 更新})清空按钮的设计要点红色文字暗示危险操作透明背景不抢视觉效果置于标题栏右侧符合操作习惯3.4 每个历史项的布局Row(){// 左侧公司 单号 备注Column(){Text(item.company)Text(item.trackingNo)if(item.note.length0){Text(备注: item.note)}}.alignItems(HorizontalAlign.Start)Blank()// 右侧状态已签收 签收时间Column(){Text($r(app.string.status_delivered)).fontColor($r(app.color.status_delivered))// 绿色Text(item.signTime)}.alignItems(HorizontalAlign.End)}布局特点左右两列左侧靠左右侧靠右中间Blank()自动撑开。3.5 底部统计栏Row(){Column(){Text(5).fontColor($r(app.color.primary))Text(总件数).fontSize(11)}Column(){Text(4家).fontColor($r(app.color.status_delivered))Text(快递公司).fontSize(11)}Column(){Text(30天).fontColor($r(app.color.rating_star))Text(覆盖周期).fontSize(11)}}.height(64).backgroundColor($r(app.color.card_bg))统计分析指标值颜色总件数5主题蓝#FF4A90D9快递公司4家签收绿#FF4CAF50覆盖周期30天星标黄#FFFFC107每个数字用不同颜色区分视觉层次丰富。四、List 组件的性能优化技巧4.1 keyGenerator 的重要性// ✅ 稳定唯一 keyForEach(arr,fn,(item)item.id.toString())// ❌ 不推荐使用索引作为 keyForEach(arr,fn,(item,index)index.toString())使用索引作为 key 时如果列表增删操作发生在中间位置会导致后续所有项的 key 变化引发不必要的全部重渲染。4.2 列表项间距的两种方式// 方式1ListItem 的外间距ListItem(){/* ... */}.padding({left:16,right:16,top:6,bottom:6})// 方式2卡片本身的内间距 marginColumn(){/* ... */}.padding($r(app.float.padding_medium)).margin({bottom:12})推荐方式1因为ListItem的外间距不会影响卡片本身的样式而且List组件在处理边缘时不会吃掉外边距。4.3 Scroll 与 List 的选择组件适用场景特性List同类型项的列虚拟列表、懒加载、性能好Scroll不同类型内容的滚动无虚拟化适合内容复杂但项数少物流详情页的信息卡片在Scroll中而物流时间线用List包裹因为时间的项数可能很多。五、router 参数传递的陷阱与应对5.1 参数类型安全// 发送方letparams{packageData:item};letopt:RouteOpt{url:pages/TrackDetailPage,params:params};// 接收方constparamsrouter.getParams()asRecordstring,Object;使用as Recordstring, Object是类型断言编译时不会报错但运行时可能拿到不符合预期的数据。5.2 防御性取值// 默认空数据兜底StatepackageData:PackageItem{id:0,trackingNo:,company:,status:,statusText:,note:,updateTime:,events:[]};aboutToAppear():void{constparamsrouter.getParams()asRecordstring,Object;if(paramsparams[packageData]){this.packageDataparams[packageData]asPackageItem;}// 不传参时显示默认空数据不会崩溃}5.3 对象引用 vs 深拷贝当前传参是对象引用传递letparams{packageData:item};// item 是同一个引用这意味着详情页修改packageData会影响首页的数据。如果不需要这种同步应该做浅拷贝letparams{packageData:{...item,events:[...item.events]}};但对于只读展示的详情页引用传递足够。六、关于 UI 细节的思考6.1 为什么时间线用 List 而不是 Scroll Column// 当前实现推荐List(){ForEach(this.packageData.events,(event,index){ListItem(){/* 时间线项 */}})}// 替代方案不推荐Scroll(){Column(){ForEach(this.packageData.events,(event,index){/* 时间线项 */})}}List 的优势虚拟列表只渲染可见区域的项默认可滚动无需额外包一层 Scroll更好的滑动性能Scroll Column 的优势更灵活的布局非均质内容6.2 Line 组件的局限鸿蒙 ArkUI 的Line组件功能有限——它能画线但不能控制方向。我们通过 Column 容器 width(2) 让它呈现为竖线// Line 默认是水平线Line().width(2)// 线宽视觉上表现为水平线的高度.height(40)// 水平线长度要让它变成竖线需要将 Line 放在一个窄的 Column 中通过width(2)限定宽度height(40)控制长度。这种方式虽然可用但代码意图不够直观。七、小结本篇完成了两个核心页面的开发✅TrackDetailPage路由参数接收与安全取值、包裹信息卡片、时间线 UICircle Line 组合✅HistoryPage历史列表、清空功能、空状态、底部统计栏核心知识点aboutToAppear生命周期接收router.getParams()时间线的关键设计最新事件高亮、最后一项不画线布局技巧alignItems(VerticalAlign.Top)实现顶部对齐List 的虚拟化性能优势防御性编程默认空数据兜底 参数判空下一篇将迎来本系列的收官之作——数据统计与个人中心涵盖柱状图绘制、月度趋势分析、公司分布可视化、Switch 设置等高级内容。系列索引第一篇项目初始化与工程架构第二篇首页与列表开发实战第三篇表单交互与搜索筛选第四篇物流时间线与历史记录本文第五篇数据统计与个人中心

相关新闻