news 2026/6/13 15:17:51

鸿蒙原生应用实战(二):首页与包裹列表开发——List组件、ForEach渲染与状态管理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
鸿蒙原生应用实战(二):首页与包裹列表开发——List组件、ForEach渲染与状态管理

鸿蒙原生应用实战(二):首页与包裹列表开发——List组件、ForEach渲染与状态管理

本文是系列第二篇,深入讲解快递追踪 App 首页的开发全过程,包括 List 列表渲染、@State 状态管理、条件渲染空状态、圆形状态标签以及页面路由导航等核心内容。


一、首页需求分析

首页是 App 的门面,我们的快递追踪 App 首页需要实现:

  1. 标题栏:显示"我的包裹"标题 + 右上角快捷操作图标(搜索、统计、公司管理)
  2. 包裹列表:展示所有包裹,每个卡片显示快递公司、单号、状态标签、备注、更新时间
  3. 状态区分:三种状态使用不同颜色标签——运输中(橙色)、已签收(绿色)、异常(红色)
  4. 空状态:无包裹时展示友好提示
  5. 点击跳转:点击卡片进入物流详情页
  6. 底部按钮:添加包裹按钮

二、数据结构设计

2.1 定义数据模型

在 ArkTS 中,我们使用interface定义数据结构:

// Index.ets — 数据接口定义interfacePackageItem{id:number;trackingNo:string;// 快递单号company:string;// 快递公司status:string;// 状态码: transit | delivered | exceptionstatusText:string;// 状态文本: 运输中 | 已签收 | 异常note:string;// 备注updateTime:string;// 更新时间events:TrackEvent[];// 物流事件列表}interfaceTrackEvent{time:string;desc:string;location:string;}

2.2 为什么要把 status 和 statusText 分开?

status是状态码(枚举值),用于逻辑判断和条件渲染;statusText是展示文本。这样做的好处:

// 通过 status 状态码决定颜色和文本,而不是写死backgroundColor(item.status==='transit'?$r('app.color.status_transit'):item.status==='delivered'?$r('app.color.status_delivered'):$r('app.color.status_exception'))// 如果要国际化,只需替换状态文本,状态码不变

三、首页布局结构

3.1 整体框架

@Entry@Componentstruct Index{@Statepackages:PackageItem[]=[/* 模拟数据 */];build(){Column(){// 1. 标题栏 Row// 2. 包裹列表 List (或空状态)// 3. 添加按钮 Button}.width('100%').height('100%').backgroundColor($r('app.color.background'))}}

三层布局:Column作为垂直容器,从上到下排列标题栏、列表(可滚动)、底部按钮。

3.2 标题栏设计

Row(){Text($r('app.string.title_home')).fontSize($r('app.float.page_title_font_size')).fontWeight(FontWeight.Bold).fontColor($r('app.color.text_primary'))Blank()// 弹性空间,将右侧图标推到右边Row(){Text('🔍').onClick(()=>{/* 跳转搜索页 */})Text('📊').onClick(()=>{/* 跳转统计页 */})Text('🏢').onClick(()=>{/* 跳转公司管理页 */})}}.width('100%').padding($r('app.float.padding_medium')).justifyContent(FlexAlign.SpaceBetween)

使用 Emoji 作为图标是一种快速原型的方式,生产环境建议替换为 SVG 图标组件。

3.3 空状态处理

if(this.packages.length===0){Column(){Text('📦').fontSize(60)Text($r('app.string.no_packages')).fontSize($r('app.float.body_font_size')).fontColor($r('app.color.text_hint')).margin({top:16})}.width('100%').height('80%').justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)}else{// 列表渲染...}

设计要点

  • 空状态居中显示,不干扰其他布局
  • 使用大号 Emoji + 提示文案,视觉友好
  • height('80%')占满空间但给底部按钮留位置

四、List 列表组件详解

4.1 基础结构

List(){ForEach(this.packages,(item:PackageItem)=>{ListItem(){// 卡片内容}.onClick(()=>{/* 跳转详情 */})},(item:PackageItem)=>item.id.toString())}.layoutWeight(1)// 列表占据剩余空间

4.2 ForEach 的第三个参数:keyGenerator

ForEach(arr, itemGenerator, keyGenerator)的第三个参数非常关键:

ForEach(this.packages,(item:PackageItem)=>{/* 构建 UI */},(item:PackageItem)=>item.id.toString()// ← key 生成器)
  • 提供稳定的 key,ArkTS 才能高效 diff 更新列表
  • key 必须唯一且稳定,不推荐使用索引
  • 没有 key 或 key 不稳定会导致列表闪烁或性能问题

4.3 卡片布局设计

每个包裹卡片是一个Column嵌套Row

ListItem(){Column(){// 第一行:公司名 + 状态标签Row(){Column(){Text(item.company)// 公司名Text(item.trackingNo)// 单号}.alignItems(HorizontalAlign.Start)Blank()// 状态标签 — 圆角矩形背景Text(item.statusText).fontSize($r('app.float.badge_font_size')).fontColor(Color.White).backgroundColor(item.status==='transit'?$r('app.color.status_transit'):item.status==='delivered'?$r('app.color.status_delivered'):$r('app.color.status_exception')).padding({left:10,right:10,top:4,bottom:4}).borderRadius(12)}.width('100%')// 条件渲染:备注if(item.note.length>0){Text(item.note)}// 更新时间Text('更新: '+item.updateTime)}.width('100%').padding($r('app.float.padding_medium')).backgroundColor($r('app.color.card_bg')).borderRadius($r('app.float.card_corner_radius'))}

4.4 状态标签颜色映射

三种状态三种颜色,代码复用:

状态码状态文本颜色色值
transit运输中橙色#FFFF8C00
delivered已签收绿色#FF4CAF50
exception异常红色#FFF44336

通过三元运算符链实现条件颜色,代码简洁但要注意可读性。


五、@State 状态管理详解

5.1 @State 的作用

@State是 ArkTS 中最核心的装饰器,标记的变量变化时会自动触发 UI 重渲染

@Componentstruct Index{@Statepackages:PackageItem[]=[/* ... */];// 当 packages 的内容变化时,UI 自动更新}

注意@State监听的是引用变化,对于数组:

  • this.packages = newArray→ 触发更新(赋值新数组)
  • this.packages.push(newItem)→ 触发更新(数组变异方法)
  • this.packages[0].note = 'xxx'不触发更新(直接修改数组元素属性)

5.2 Array 操作的响应性

操作方式是否触发 UI 更新说明
this.packages = []✅ 触发赋值新数组
this.packages.push(x)✅ 触发数组变异方法
this.packages.splice(i,1)✅ 触发数组变异方法
this.packages[i] = x❌ 不触发直接索引赋值
this.packages[i].note = x❌ 不触发修改嵌套属性

解决方案:修改嵌套属性时使用对象展开创建新对象:

// 正确方式:创建新对象替换letnewItem={...this.packages[i],note:'新备注'};letnewArr=[...this.packages];newArr[i]=newItem;this.packages=newArr;

六、页面路由跳转

6.1 定义 RouteOpt 接口

interfaceRouteOpt{url:string;params?:Object;}

6.2 无参数跳转

// 底部按钮 — 跳转添加页Button($r('app.string.btn_save')).onClick(()=>{letopt:RouteOpt={url:'pages/AddPackagePage'};router.pushUrl(opt);})

6.3 带参数跳转

// 点击卡片 — 跳转详情页,携带包裹数据ListItem(){// ...}.onClick(()=>{letparams={packageData:item};// 参数letopt:RouteOpt={url:'pages/TrackDetailPage',params:params};router.pushUrl(opt);})

6.4 接收参数(在详情页)

// TrackDetailPage.etsaboutToAppear():void{constparams=router.getParams()asRecord<string,Object>;if(params&&params['packageData']){this.packageData=params['packageData']asPackageItem;}}

重要安全措施

  • 使用as Record<string, Object>断言
  • params判空
  • params['packageData']判空

这样即使在跳转时忘记传参,页面也不会崩溃,只会显示默认空数据。


七、实战技巧与避坑

7.1 响应式布局设计

使用layoutWeight(1)让列表占满剩余空间,避免底部按钮被挤出屏幕:

List(){/* ... */}.width('100%').layoutWeight(1)// ← 关键:占据 Column 中的剩余空间// 底部按钮Button('添加包裹').margin({bottom:16})// ← 底部安全间距

7.2 Card 设计规范

  • 背景色:白色#FFFFFF(card_bg)
  • 圆角:12vp(card_corner_radius)
  • 内边距:16vp(padding_medium)
  • 列表项间距:通过 ListItem 的 padding 控制(上下 6vp)
  • 阴影:鸿蒙的 Card 组件自带阴影效果

7.3 内联函数优化

图标点击的跳转逻辑可以内联写入,减少函数定义:

// 不推荐(额外函数)searchClick(){router.pushUrl({url:'pages/SearchPage'});}Text('🔍').onClick(()=>this.searchClick())// 推荐(直接内联)Text('🔍').onClick(()=>{letopt:RouteOpt={url:'pages/SearchPage'};router.pushUrl(opt);})

7.4 $r() 资源引用的优势

使用$r('app.float.xxx')而非硬编码尺寸:

// ✅ 使用资源引用,统一管理.fontSize($r('app.float.body_font_size')).padding($r('app.float.padding_medium'))// ❌ 硬编码,不利于维护.fontSize(16).padding(16)

这样当需要调整全局字体或间距时,只需修改float.json一处即可。


八、完整首页代码结构

// pages/Index.ets — 完整结构importrouterfrom'@ohos.router';interfaceRouteOpt{url:string;params?:Object;}interfacePackageItem{/* ... */}interfaceTrackEvent{/* ... */}@Entry@Componentstruct Index{@Statepackages:PackageItem[]=[/* 3条模拟数据 */];build(){Column(){// 1. 标题栏 Row// 2. 空状态 / 包裹列表 List// 3. 添加按钮 Button}.width('100%').height('100%').backgroundColor($r('app.color.background'))}}

九、小结

本篇我们完成了首页的全部开发,核心要点:

  1. List + ForEach:列表渲染的标准范式,keyGenerator 的重要性
  2. @State 状态管理:响应式数据驱动 UI,数组操作的响应性限制
  3. 条件渲染:空状态 vs 列表的切换
  4. 路由跳转router.pushUrl带参数跳转与接收
  5. 三种状态标签:通过状态码动态渲染颜色
  6. 资源引用$r()统一管理字体、颜色、尺寸

下一篇将进入表单交互与搜索筛选,涵盖添加包裹表单验证、搜索页面多条件筛选、快递公司管理等实战功能。


系列索引

  • 第一篇:项目初始化与工程架构
  • 第二篇:首页与列表开发实战(本文)
  • 第三篇:表单交互与搜索筛选
  • 第四篇:物流时间线与历史记录
  • 第五篇:数据统计与个人中心
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/13 15:11:57

猫抓Cat-Catch:浏览器资源嗅探的架构哲学与技术实现深度解析

猫抓Cat-Catch&#xff1a;浏览器资源嗅探的架构哲学与技术实现深度解析 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 在现代Web生态系统中&…

作者头像 李华
网站建设 2026/6/13 15:06:53

告别手动标注!用PhaseNet+Python实战地震波P/S波自动拾取(附完整代码与数据集处理)

深度学习赋能地震波分析&#xff1a;PhaseNet实战指南与自动化拾取技巧地震波P波和S波的精确拾取一直是地球物理研究中的基础性难题。传统依赖人工标注的方式不仅效率低下&#xff0c;还容易受到主观判断的影响。记得去年参与某次地震数据分析项目时&#xff0c;团队花了整整两…

作者头像 李华
网站建设 2026/6/13 15:06:53

MC56F823xx DSC开发实战:从内核架构到外设配置全解析

1. 从手册到实战&#xff1a;MC56F823xx DSC深度开发指南如果你正在为电机控制、数字电源或者任何需要实时信号处理的项目选型&#xff0c;那么“数字信号控制器”这个名词一定不陌生。它不像传统的单片机那样在复杂数学运算上捉襟见肘&#xff0c;也不像纯粹的DSP那样在控制逻…

作者头像 李华
网站建设 2026/6/13 15:04:51

高效核销网点小程序开发指南

核销网点小程序开发的关键要素核销网点小程序通常用于线下门店或服务点的核销管理&#xff0c;如优惠券、会员卡、电子票务等。开发时需要关注以下核心功能模块&#xff1a;用户端功能 注册登录&#xff08;支持手机号、微信授权&#xff09; 核销码展示&#xff08;动态二维码…

作者头像 李华