> 文档中心 > 用 HarmonyOS ArkUI 来开发一个健康饮食应用

用 HarmonyOS ArkUI 来开发一个健康饮食应用

本文演示如果在DevEco Studio 3里面,用HarmonyOS的ArkUI来开发一个健康饮食应用。体验HarmonyOS 3最新API 9!

获取HarmonyOS应用

HarmonyOS的ArkUI来开发一个健康饮食的ArkUI程序“ArkUIHealthyDiet”,基础代码已经有了[1],个人只需要在基础代码上稍作修改,就能运行了。

通过DevEco Studio 3导入应用

有关DevEco Studio 3的安装配置,可以参考前文《玩转HarmonyOS 3必装DevEco Studio 3,注意避弹[2]》这里就不在赘述。

首选是打开DevEco Studio 3,可以看到如下界面。

点击“Open Project”来导入我们实行装备好的ArkUI程序“ArkUIHealthyDiet”。

导入程序之后,就能在该程序基础上进行代码开发、运行。

通过DevEco Studio 3创建应用

如果想从0开始学习ArkUI,体验完整的HarmonyOS的开发过程,那么建议跟随本文一起来开启ArkUI开发之旅吧。

首选是打开DevEco Studio 3,可以看到如下界面。

点击“Create Project”来创建ArkUI程序“ArkUIHealthyDiet”。

选择模板

选择空模板Empty Ability,点击“Next”执行下一步。

配置项目

配置项目信息,重要是以下圈中部分。其他配置按照默认配置即可。点击“Finish”执行下一步。

运行HarmonyOS应用

打开Device Manager

登入华为账号

点击“Sign In”登入个人注册的华为账号。如果没有,则参考本文最后的链接进行注册。

启动远程模拟器

运行应用

点击下命的三角形按钮以启动应用

应用运行效果图如下。

完善应用

接下来是进入正题,开始我们的健康饮食应用的核心功能的开发了。

构建食物数据模型

要创建食物数据模型来统一存储和管理食物的数据。食物的信息包括:食物名称、卡路里、蛋白质、脂肪、碳水和维生素C等。

在ets目录下新建model文件夹,用于存放数据模型文件。

在model目录下创建DataModels.ets,用于存放数据模型。

定义食物数据的存储模型FoodInfo和枚举变量CategoryId,FoodData类包含食物id、名称(name)、分类(category)、图片(image)、热量(calories)、蛋白质(protein)、脂肪(fat)、碳水(carbohydrates)和维生素C(vitaminC)属性等等。

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}复制

创建食物资源数据。在ets目录下创建mock文件夹,并在mock文件夹下创建MockData.ets。在MockData.ets中声明食物成分数组代码如下:

import { FoodInfo, CategoryId} from '../model/DataModels'// 构造数据的mock数据export let mockFoods: Array = [  {    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  },  {    id: 1,    letter: 'Walnut',    name: $r('app.string.food_name_walnut'),    image: $r('app.media.walnut'),    categoryId: CategoryId.Nut,    calories: 646,    protein: 14.9,    fat: 58.8,    carbohydrates: 19.1,    vitaminC: 1.0  },  {    id: 2,    letter: 'Cucumber',    name: $r('app.string.food_name_cucumber'),    image: $r('app.media.cucumber'),    categoryId: CategoryId.Vegetable,    calories: 16,    protein: 0.8,    fat: 0.2,    carbohydrates: 2.9,    vitaminC: 9.0  },  {    id: 3,    letter: 'Blueberry',    name: $r('app.string.food_name_blueberry'),    image: $r('app.media.blueberry'),    categoryId: CategoryId.Fruit,    calories: 57,    protein: 0.7,    fat: 0.3,    carbohydrates: 14.5,    vitaminC: 9.7  },  {    id: 4,    letter: 'Crab',    name: $r('app.string.food_name_crab'),    image: $r('app.media.crab'),    categoryId: CategoryId.Seafood,    calories: 97,    protein: 19,    fat: 1.5,    carbohydrates: 0,    vitaminC: 7.6  },  {    id: 5,    letter: 'IceCream',    name: $r('app.string.food_name_ice_cream'),    image: $r('app.media.icecream'),    categoryId: CategoryId.Dessert,    calories: 150,    protein: 3.5,    fat: 11,    carbohydrates: 24,    vitaminC: 0.6  },  {    id: 6,    letter: 'Onion',    name: $r('app.string.food_name_onion'),    image: $r('app.media.onion'),    categoryId: CategoryId.Vegetable,    calories: 40,    protein: 1.1,    fat: 0.2,    carbohydrates: 9,    vitaminC: 8.0  },  {    id: 7,    letter: 'Mushroom',    name: $r('app.string.food_name_mushroom'),    image: $r('app.media.mushroom'),    categoryId: CategoryId.Vegetable,    calories: 20,    protein: 3.1,    fat: 0.3,    carbohydrates: 3.3,    vitaminC: 206  },  {    id: 8,    letter: 'Tomato',    name: $r('app.string.food_name_tomato'),    image: $r('app.media.tomato'),    categoryId: CategoryId.Vegetable,    calories: 15,    protein: 0.9,    fat: 0.2,    carbohydrates: 3.3,    vitaminC: 14.0  },  {    id: 9,    letter: 'Pitaya',    name: $r('app.string.food_name_pitaya'),    image: $r('app.media.pitaya'),    categoryId: CategoryId.Fruit,    calories: 55,    protein: 1.1,    fat: 0.2,    carbohydrates: 13.3,    vitaminC: 3.0  },  {    id: 10,    letter: 'Avocado',    name: $r('app.string.food_name_avocado'),    image: $r('app.media.avocado'),    categoryId: CategoryId.Fruit,    calories: 171,    protein: 2.0,    fat: 15.3,    carbohydrates: 7.4,    vitaminC: 8.0  },  {    id: 11,    letter: 'Strawberry',    name: $r('app.string.food_name_strawberry'),    image: $r('app.media.strawberry'),    categoryId: CategoryId.Fruit,    calories: 32,    protein: 1.0,    fat: 0.2,    carbohydrates: 7.1,    vitaminC: 47.0  }]复制

name需要考虑国际化,因此,该值是存储在string.json文件中。

image所引用的食物图片资源,放置在resources >base> media目录下。

在model目录下创建DataUtil.ets,用于加载健康饮食应用的数据。

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

已完成好健康饮食应用的数据资源准备,接下来将通过加载这些数据来创建食物列表页面

构建食物列表List布局

使用List组件和ForEach循环渲染,构建食物列表布局。

修改pages目录下的Index.ets文件,新建FoodList组件作为页面入口组件,FoodListItem为其子组件。List组件是列表组件,适用于重复同类数据的展示,其子组件为ListItem,适用于展示列表中的单元。

import { FoodInfo } from '../model/DataModels'import { getFoods } from '../model/DataUtil'@Componentstruct FoodListItem {  private foodItem: FoodInfo  build() {    Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {      Image(this.foodItem.image) .objectFit(ImageFit.Contain) .height(40) .width(40) .margin({ right: 16 })      Text(this.foodItem.name) .fontSize(14) .flexGrow(1)      Text(this.foodItem.calories + ' kcal') .fontSize(14)    }    .height(64)    .margin({ right: 24, left: 32 })  }}@Entry@Componentstruct 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%')    }  }}复制

运行应用,可以看到列表的效果如下。

构建食物详情页面

在pages目录下,创建FoodDetail.ets文件,FoodDetail页面的食物信息都是直接声明的常量,现在要用传递来的FoodData数据来对其进行重新赋值。整体的FoodDetail.ets代码如下。

import router from '@ohos.router'import { FoodInfo } from '../model/DataModels'@Componentstruct 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()    })  }}@Componentstruct 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')  }}@Componentstruct 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 })  }}@Entry@Componentstruct FoodDetail {  private foodItem: FoodInfo = router.getParams()[foodInfo]  build() {    Column() {      Stack( { alignContent: Alignment.TopStart }) { FoodImageDisplay({ foodItem: this.foodItem }) PageTitle()      }      ContentTable({ foodItem: this.foodItem })    }    .alignItems(HorizontalAlign.Center)  }}复制

上述代码引用了路由Router API的接口,通过在页面上引入router,可以调用router的各种接口,从而实现页面路由的各种操作。调用router.getParams()[foodInfo]来获取到列表页面跳转来时携带的foodData对应的数据。

列表与详情页面的跳转

上述详情页面已经引用了路由Router API,能否接受来自路由的参数。那么相应的,列表页面也需要做相应的调整,来触发路由跳转。点击Index后跳转到FoodDetail页面。在FoodListItem内创建Navigator组件,使其子组件都具有路由功能,目标页面target为'pages/FoodDetail'。

修改Index.ets文件,

@Componentstruct 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后,使其包裹的子组件都具有路由能力。.params方法用于页面间数据传递。

程序运行效果

完整演示视频见B站:【老卫搬砖】027期:用HarmonyOS ArkUI来开发一个健康饮食应用_哔哩哔哩_bilibili

源码

见 GitHub - waylau/harmonyos-tutorial: HarmonyOS Tutorial. 《跟老卫学HarmonyOS》 中的“ArkUIHealthyDiet”

相关问题

问题1:路由失效

报错如下:

[manifest_router.cpp(GetPagePath)-(0)] [Engine Log] can't find this page pages/FoodDetail path

解决方案:

main_pages中添加pages/FoodDetail

参考引用

  1. 《跟老卫学HarmonyOS开发》 开源免费教程,GitHub - waylau/harmonyos-tutorial: HarmonyOS Tutorial. 《跟老卫学HarmonyOS》 ↑

  2. 玩转HarmonyOS 3必装DevEco Studio 3,注意避弹 华为开发者论坛

  3. 《鸿蒙 HarmonyOS 应用开发从入门到精通战》(柳伟卫著,北京大学出版社)双十二满100减50