news 2026/10/3 2:11:17

用 HarmonyOS ArkUI 开发健康饮食应用:ArkUIHealthyDiet 列表与详情页实战剖析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用 HarmonyOS ArkUI 开发健康饮食应用:ArkUIHealthyDiet 列表与详情页实战剖析
  • 示例工程

【免费下载链接】harmonyos-tutorial

HarmonyOS Tutorial. 《跟老卫学HarmonyOS开发》

项目地址:https://gitcode.com/GitHub_Trending/ha/harmonyos-tutorial
点击查看免费下载

本文以开源仓库 harmonyos-tutorial 中的 ArkUIHealthyDiet 示例为研究对象,完整讲解如何用 HarmonyOS ArkUI(声明式 UI + eTS/ArkTS)从零搭建一个「食物列表 → 食物详情」的健康饮食应用。读完本文,你将掌握 List + ForEach 列表渲染、Navigator 页面跳转与参数传递、@Builder 复用组件、@ohos.router 参数回读、$r() 资源引用以及 mock 数据分层等一套可复用的 ArkUI 开发套路,可直接迁移到购物、资讯、菜谱等同类列表详情型应用。

一、示例定位:一个最小可用的「列表 + 详情」ArkUI 应用

ArkUIHealthyDiet 是 harmonyos-tutorial 仓库(《跟老卫学HarmonyOS开发》配套示例)中基于ArkTS(eTS 声明式开发范式)编写的健康饮食应用示例。整个工程不依赖任何第三方业务库,仅用 ArkUI 核心组件完成了一个完整闭环:

  • 首页(Index.ets):以列表形式展示 12 种常见食物(猕猴桃、核桃、黄瓜、蓝莓、螃蟹、冰淇淋、洋葱、蘑菇、番茄、火龙果、牛油果、草莓)的名称、热量与图标;
  • 详情页(FoodDetail.ets):点击列表项跳转后,展示对应食物的图片、名称,以及 Calories(热量)、Protein(蛋白质)、Fat(脂肪)、Carbohydrates(碳水化合物)、VitaminC(维生素 C)五维营养成分表。

从工程结构看(samples/ArkUIHealthyDiet),它遵循标准 Stage 模型工程布局:AppScope存放应用级配置,entry是唯一的 entry 类型模块,entry/src/main/ets下按entryability、mock、model、pages四个职责目录组织代码:

samples/ArkUIHealthyDiet/ ├── AppScope/app.json5 # 应用级配置(bundleName、版本等) ├── entry/ │ ├── src/main/ │ │ ├── ets/ │ │ │ ├── entryability/EntryAbility.ts # 应用入口 Ability │ │ │ ├── mock/MockData.ets # 食物 mock 数据 │ │ │ ├── model/DataModels.ets # 类型与枚举定义 │ │ │ ├── model/DataUtil.ets # 数据获取入口 │ │ │ └── pages/Index.ets # 食物列表页 │ │ │ └── pages/FoodDetail.ets # 食物详情页 │ │ └── module.json5 # 模块级配置 │ └── src/ohosTest/ # 本地单元测试目录 ├── hvigorfile.ts # 构建脚本 └── package.json

这种model(模型)→ mock(数据)→ pages(页面)的分层,是小型 ArkUI 应用非常实用的组织范式:UI 层只依赖数据接口,数据层可以随时替换为真实网络请求。

二、数据层设计:用类型、枚举与 mock 数据支撑 UI

2.1 FoodInfo 类型定义

核心数据结构定义在 DataModels.ets,通过 ArkTS 的export type定义了一个食物信息联合结构:

export enum CategoryId { Fruit = 0, Vegetable, Nut, Seafood, Dessert } export type FoodInfo = { id: number letter: string name: string | Resource image: Resource categoryId: CategoryId calories: number protein: number fat: number carbohydrates: number vitaminC: number }

几个值得注意的设计点:

  • name: string | Resource:联合类型,既允许直接传字符串,也允许传$r('app.string.xxx')生成的 Resource 资源对象,兼顾国际化与硬编码两种场景;
  • image: Resource:图片统一使用资源引用类型,配合$r()在编译期完成资源校验;
  • categoryId: CategoryId:用枚举对食物做分类(水果/蔬菜/坚果/海鲜/甜点),为后续扩展「按分类筛选」等能力预留了字段;
  • 营养成分字段统一为number,便于详情页直接拼接单位字符串。

2.2 Mock 数据:12 种食物的完整数据集

数据源位于 MockData.ets,mockFoods是一个Array<FoodInfo>,每条数据都通过$r()引用资源:

{ id: 0, letter: 'Kiwi', name: $r('app.string.food_name_kiwi'), image: $r('app.media.kiwi'), categoryId: CategoryId.Fruit, calories: 61, protein: 0.8, fat: 0.6, carbohydrates: 14.5, vitaminC: 62 },

这 12 条数据覆盖了CategoryId中的全部五个分类(Fruit、Vegetable、Nut、Seafood、Dessert),数据内容为真实可考的营养成分参考值,例如核桃(Walnut)热量 646 kcal、蛋白质 14.9 g、脂肪 58.8 g,蘑菇(Mushroom)维生素 C 高达 206 mg。这种「mock 层 + $r 资源引用」的做法意味着:

  • 页面代码不出现任何硬编码数据;
  • 图片与文案集中在resources目录管理;
  • 后续接入真实数据源时,只需替换getFoods()的实现。

2.3 数据获取入口

DataUtil.ets 提供了一个极简的工厂方法:

import { FoodInfo } from './DataModels' import { mockFoods } from '../mock/MockData' export function getFoods(): Array<FoodInfo> { return mockFoods }

列表页通过private foodItems: FoodInfo[] = getFoods()一次性取数。从源码结构看,DataUtil相当于 UI 与 mock 数据之间的门面(Facade),若将来改为从服务端拉取,只需在此函数内部改为异步请求并返回 Promise,UI 层即可无感升级。

三、首页实现:List + ForEach 渲染食物列表

首页 Index.ets 由两个组件构成:内嵌的FoodListItem负责单个列表项,@Entry FoodList是页面根组件。

3.1 页面骨架:标题栏 + List

@Entry @Component struct FoodList { private foodItems: FoodInfo[] = getFoods() build() { Column() { Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { Text('Food List') .fontSize(20) .margin({ left: 20 }) } .height('7%') .backgroundColor('#FFf1f3f5') List() { ForEach(this.foodItems, item => { ListItem() { FoodListItem({ foodItem: item }) } }, item => item.id.toString()) } .height('93%') } } }

关键写法拆解:

  • 页面用Column纵向布局,标题区占7%、列表区占93%,用百分比高度实现简单的自适应分配;
  • List+ForEach是 ArkUI 列表页的标准组合,第三个参数item => item.id.toString()是键值生成器(Key Generator),用于高效 diff 复用,必须以稳定唯一值(这里取 id)作为键;
  • ListItem包裹每个条目,FoodListItem({ foodItem: item })以构造参数方式向子组件传值。

3.2 列表项:Navigator 实现页面跳转并携带参数

FoodListItem是整个示例最值得学习的部分——它演示了 ArkUI 最轻量的页面跳转方式Navigator:

@Component struct FoodListItem { private foodItem: FoodInfo build() { // 增加路由导航 Navigator({ target: 'pages/FoodDetail' }) { Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { Image(this.foodItem.image) .objectFit(ImageFit.Contain) .height(40) .width(40) .backgroundColor('#FFf1f3f5') .margin({ right: 16 }) Text(this.foodItem.name) .fontSize(14) .flexGrow(1) Text(this.foodItem.calories + ' kcal') .fontSize(14) } .height(64) } // 页面间数据传递 .params({ foodInfo: this.foodItem }) .margin({ right: 24, left: 32 }) } }

要点说明:

  • Navigator({ target: 'pages/FoodDetail' }):声明式路由容器,target指向目标页面在main_pages.json中注册的路径(如pages/FoodDetail),点击其内部区域即触发跳转;
  • .params({ foodInfo: this.foodItem }):通过params属性把整个FoodInfo对象随路由传递,实现页面间数据传递,这是 ArkUI 页面传参最直接的方式之一;
  • .flexGrow(1):让食物名称占据剩余宽度,右侧热量文本自然右对齐;
  • 图片使用ImageFit.Contain等比缩放,保证不同尺寸的食物图标不被拉伸变形。

与使用router.pushUrl的编程式跳转相比,Navigator的优势在于:跳转行为完全由声明式 UI 描述,无需在onClick中手写路由代码,代码更内聚、更易维护。

四、详情页实现:@Builder 复用与参数回读

点击列表项后进入 FoodDetail.ets,该页面由PageTitle(顶部导航栏)、FoodImageDisplay(图片与名称展示区)、ContentTable(营养成分表)三个子组件拼装而成,是「组件拆分 + 复用」的典型示范。

4.1 用 router.getParams 回读页面参数

详情页在组件属性初始化阶段直接读取路由参数:

import router from '@ohos.router' @Entry @Component struct FoodDetail { private foodItem: FoodInfo = router.getParams()['foodInfo'] ... }
  • 与列表页用Navigator.params发送参数对应,详情页用router.getParams()['foodInfo']接收参数;
  • 这样Index页跳转时传入的foodItem对象被完整还原,详情页即可直接渲染该食物的图片、名称与营养数据;
  • 注意router来自系统能力模块@ohos.router,使用前需显式 import。

4.2 返回导航栏:onClick 触发 router.back

PageTitle组件展示了「返回键 + 标题」的实现:

@Component struct PageTitle { build() { Flex({ alignItems: ItemAlign.Start }) { Image($r('app.media.back')) .width(21.8) .height(19.6) Text('Food Detail') .fontSize(21.8) .margin({left: 17.4}) } .height(61) .backgroundColor('#FFedf2f5') .padding({ top: 13, bottom: 15, left: 28.3 }) .onClick(() => { router.back() }) } }
  • 返回图标同样通过$r('app.media.back')引用资源;
  • 整个导航栏注册onClick,点击任意位置调用router.back()返回上一页,与Navigator的入栈跳转形成完整的路由闭环(push/back)。

4.3 图片展示区:Stack 叠加布局

FoodImageDisplay用Stack把食物图片与名称文字叠加,名称固定于左下角:

@Component struct FoodImageDisplay { private foodItem: FoodInfo build() { Stack({ alignContent: Alignment.BottomStart }) { Image(this.foodItem.image) .objectFit(ImageFit.Contain) Text(this.foodItem.name) .fontSize(26) .fontWeight(500) .margin({ left: 26, bottom: 17.4 }) } .height(357) .backgroundColor('#FFedf2f5') } }

alignContent: Alignment.BottomStart使子组件按「左下角」对齐,是 ArkUI 中实现水印、角标、悬浮文案的通用手法。

4.4 营养成分表:@Builder 抽离可复用 UI 片段

ContentTable内部定义了一个@Builder修饰的IngredientItem,把「左侧标签 + 右侧名称/数值」的重复行布局抽成可复用片段:

@Component struct ContentTable { private foodItem: FoodInfo @Builder IngredientItem(title:string, name: string, value: string) { Flex() { Text(title) .fontSize(17.4) .fontWeight(FontWeight.Bold) .layoutWeight(1) Flex() { Text(name) .fontSize(17.4) .flexGrow(1) Text(value) .fontSize(17.4) } .layoutWeight(2) } } build() { Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Start }) { this.IngredientItem('Calories', 'Calories', this.foodItem.calories + 'kcal') this.IngredientItem('Nutrition', 'Protein', this.foodItem.protein + 'g') this.IngredientItem('', 'Fat', this.foodItem.fat + 'g') this.IngredientItem('', 'Carbohydrates', this.foodItem.carbohydrates + 'g') this.IngredientItem('', 'VitaminC', this.foodItem.vitaminC + 'mg') } .height(280) .padding({ top: 30, right: 30, left: 30 }) } }

这段代码集中体现了 ArkUI 的两个布局与复用机制:

  • @Builder:把「标题 + 名称 + 数值」三列布局抽象为带参函数,this.IngredientItem('Calories', 'Calories', ...)式调用,五行数据共用同一套行样式,消除重复代码;
  • layoutWeight与flexGrow配合:标签列layoutWeight(1)、数值列layoutWeight(2)按 1:2 分配宽度,flexGrow(1)让名称自适应伸缩,构成典型的「两端对齐表格行」;
  • 外层Flex纵向排列、justifyContent: FlexAlign.SpaceBetween,五行营养数据在 280 高度内均匀分布。

五、资源引用与页面注册:让 $r() 和路由配置闭环

5.1 $r() 资源引用体系

示例中所有文案与图片均通过$r()引用,例如:

  • $r('app.string.food_name_kiwi')引用resources/base/element/string.json中的字符串资源;
  • $r('app.media.kiwi')引用resources/base/media/下的图片资源;
  • $r('app.media.back')引用返回键图标。

$r()的优势在于编译期资源校验:资源缺失、拼写错误都会在构建阶段直接暴露,且天然支持多语言/多设备资源限定词(如resources/zh_CN、resources/en_US目录),是 ArkUI 推荐的标准资源引用方式。

5.2 路由页面注册

Navigator的target: 'pages/FoodDetail'之所以能解析,前提是两个页面都已在路由表main_pages.json(由 module.json5 中"pages": "$profile:main_pages"指定)中注册。从仓库源码看,Index 与 FoodDetail 均按pages/页面名的格式登记,EntryAbility在 onWindowStageCreate 中通过windowStage.loadContent('pages/Index', ...)指定首页。若新增页面忘记注册,Navigator跳转会因找不到路由而失败——这是初学者最容易踩的坑。

5.3 模块与应用级配置

模块级 module.json5 声明了deviceTypes: ["phone"](仅支持手机形态)、入口 AbilityEntryAbility及其skills(entity.system.home+action.system.home,即桌面启动图标);应用级 app.json5 定义了bundleName: "com.waylau.hmos.myapplication"、versionCode: 1000000、versionName: "1.0.0"等打包元数据。两者的label、icon同样使用$string:/$media:资源引用,与应用内资源体系保持一致。

六、工程化与测试:hvigor 构建与 ohosTest 用例

6.1 构建工程

该示例基于 HarmonyOS 的hvigor构建体系:工程根与模块各有一个 hvigorfile.ts,模块级 package.json 声明"buildTool": "hvigor"、"directoryLevel": "module"。在 DevEco Studio 中打开工程根目录即可自动完成同步与构建;命令行下通过hvigorw assembleHap一类任务产出 HAP 包,再部署到真机或模拟器运行(deviceTypes限定为 phone)。

6.2 单元测试骨架

工程内置了ohosTest测试模块(entry/src/ohosTest),包含:

  • List.test.ets:测试套件入口,聚合Ability.test;
  • Ability.test.ets:Ability 生命周期相关的用例;
  • TestAbility.ets/OpenHarmonyTestRunner.ts:测试专用 Ability 与测试运行器,说明该示例预留了完整的本地测试执行链路,可在 DevEco Studio 中直接运行 ohosTest 用例。

对本文主题而言,可在该测试骨架中为getFoods()补充断言(如返回 12 条数据、字段完整性校验),验证数据层在后续迭代中不被破坏。

七、运行与效果验证

  1. 使用 DevEco Studio 打开仓库中的 samples/ArkUIHealthyDiet 目录;
  2. 等待 hvigor 依赖同步完成,连接 API 版本兼容的 HarmonyOS 手机(或启动模拟器);
  3. 点击 Run 运行entry模块,首页即展示「Food List」列表;
  4. 点击任意食物条目,经Navigator跳转到详情页,查看该食物的图片与五维营养表;
  5. 点击左上角返回区域(router.back())回到列表,验证 push/back 路由闭环。

八、小结:从本示例可复用的 ArkUI 模式

ArkUIHealthyDiet 虽小,却浓缩了 ArkUI 声明式开发中最高频的六类模式,可直接复用到任意「列表 + 详情」型应用:

关注点本示例中的实现复用场景
列表渲染List+ForEach+ListItem,id 作键商品列表、资讯流、联系人
页面跳转Navigator+target+.params()列表到详情的导航
参数回读router.getParams()['foodInfo']详情页数据还原
返回导航onClick+router.back()统一返回栏
复用 UI@Builder带参函数重复行/卡片布局
数据分层DataModels+MockData+DataUtil数据源可替换架构

相关源码均可在仓库中继续深读:Index.ets、FoodDetail.ets、MockData.ets、DataModels.ets。

  • 示例工程

【免费下载链接】harmonyos-tutorial

HarmonyOS Tutorial. 《跟老卫学HarmonyOS开发》

项目地址:https://gitcode.com/GitHub_Trending/ha/harmonyos-tutorial
点击查看免费下载
上一篇:Sunshine 10 分钟跑通游戏串流上手指南
下一篇:3分钟解锁微信网页版:终极免费插件使用指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 2:10:22

智能血压计DIY全攻略:51/STM32方案与示波法算法详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/3 2:07:28

Pixhawk固定翼TECS调参指南:从能量控制原理到实战日志分析

1. 为什么固定翼飞控绕不开TECS&#xff1a;从一次惨痛炸机说起先讲个我早年的真实案例。那时候我刚从多旋翼转固定翼&#xff0c;第一次用Pixhawk飞一套自组的1.4米塞斯纳&#xff0c;当时对TECS完全没有概念&#xff0c;只按多旋翼那套思路调PID。结果起飞后一加油门&#xf…

作者头像 李华
网站建设 2026/10/3 2:03:51

ACD/Labs核磁数据分析实战:从谱图处理到结构验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华