三,饮食记录
1,顶部搜索栏
(1).实现效果图
(2).实现原理
首先,我们需要在视图目录下创建一个新的文件夹,命名为`record`,用于存放记录相关的组件。在这个文件夹中,我们将创建一个名为`RecordIndex.ets`的文件,该文件将作为记录首页的组件。
正如前文所述,Tabs组件目前仅包含一个显示文本内容的Text组件。在实际开发中,我们将这三个部分都封装成组件,以便进行调用。
接下来,我们将创建一个名为`SearchHeader`的组件,它将作为顶部的搜索栏。这个搜索栏不仅仅是一个简单的文本输入框,而是带有一个小图标的搜索框。鸿蒙操作系统已经提供了一个专门的搜索栏组件`Search`,我们可以通过将鼠标悬停在`Search`组件上来查看其具体的书写格式。
至于消息通知的数量,我们可以使用一个名为`Badge`的组件来实现小红点的效果。`Badge`是一个容器型组件,我们可以通过将需要添加小红点的组件直接复制(CV)进去。具体的数字需要我们通过参数传递来设置。我们还可以通过`position`属性来控制小红点的位置。此外,我们还可以通过`style`属性来自定义小红点的样式。
(3).SearchHeader.ets
import { CommonConstants } from '../../common/constants/CommonConstants' @Component export default struct SearchHeader { build() { Row({space:CommonConstants.SPACE_6}){ Search({placeholder: '搜索饮食或运动信息'}) .textFont({size:18}) .layoutWeight(1) Badge({count: 1,position:BadgePosition.RightTop,style:{fontSize: 14}}){ Image($r('app.media.ic_public_email')) .width(24) } } .width(CommonConstants.THOUSANDTH_940) } }
2,统计卡片
(1).实现效果图
(2).实现原理
1. **日期选择组件**:
- 我们将实现一个日期展示组件,它包含一个下拉框。用户点击下拉框时,将弹出一个日期选择器`DatePicker`。
- 用户在日期选择器中选择不同的日期后,页面将展示所选日期以及与该日期相关的统计信息。
- 选择日期后,我们可以使用ArkUI提供的组件功能,在日期选择弹窗中包含一个“保存”按钮。
- 当用户点击保存按钮,我们使用`SetOrCreate`命令将用户选择的时间进行全局存储,确保用户的选择可以被应用和保留。
2. **统计信息展示**:
- **热量统计**:我们将采用行式布局来展示热量摄入的统计信息,这包括饮食摄入、推荐摄入和运动消耗三个部分。推荐摄入部分将特别展示一个环形进度条,以直观显示用户的实际摄入与推荐值之间的差异。
- **营养素统计**:同样使用行式布局,展示碳水化合物、蛋白质和脂肪的摄入情况,每部分旁边也将展示一个环形进度条,帮助用户了解各类营养素的摄入比例。
3. **穿梭切换卡片**:
- 用户可以通过左右滑动在热量统计卡片和营养素统计卡片之间进行切换。这将通过`Swiper`滑块视图容器实现,它允许子组件在滑动时进行轮播显示。
- `Swiper`滑块视图容器是我们在布局分析中提到的穿梭切换卡片部分的关键组件,它为用户界面提供了流畅的滑动体验和动态的视图切换能力。
在设计这些组件时,我们需要确保它们不仅功能齐全,而且用户友好,易于操作。此外,全局存储用户的选择对于保持应用状态的一致性至关重要,因此`SetOrCreate`命令的使用需要谨慎以确保数据的正确性和完整性。
(3).DatePickDiaLog.ets,CalorieStats.ets和NutrientStats.ets
import { CommonConstants } from '../../common/constants/CommonConstants' @CustomDialog export default struct DatePickDialog { controller: CustomDialogController selectedDate: Date = new Date() build() { Column({space:CommonConstants.SPACE_12}){ //1.日期选择器 DatePicker({ start: new Date('2020-01-01'), end: new Date(), selected: this.selectedDate }) .onChange((value: DatePickerResult) => { this.selectedDate.setFullYear(value.year, value.month, value.day) }) //2.按钮 Row({space:CommonConstants.SPACE_12}){ Button('取消') .width(120) .backgroundColor($r('app.color.light_gray')) .onClick(() => this.controller.close()) Button('确定') .width(120) .backgroundColor($r('app.color.primary_color')) .onClick(() => { //1.保存日期到全局储存 AppStorage.SetOrCreate('selectDate',this.selectedDate.getTime()) //2.关闭窗口 this.controller.close() }) } } .padding(CommonConstants.SPACE_12) } }
import { WindowMode } from '@ohos.UiTest' import { CommonConstants } from '../../common/constants/CommonConstants' @Component export default struct CalorieStats { intake: number = 192 expend: number = 150 recommend: number = CommonConstants.RECOMMEND_CALORIE remainCalorie(){ return this.recommend - this.intake + this.expend } build() { Row({space:CommonConstants.SPACE_6}){ //1.饮食摄入 this.StatsBuilder('饮食摄入', this.intake) //2.还可以吃 Stack(){ //2.1进度条 Progress({ value:this.intake, total:this.recommend, type:ProgressType.Ring }) .width(120) .style({strokeWidth:CommonConstants.DEFAULT_10}) .color($r('app.color.primary_color')) //2.2统计数据 this.StatsBuilder('还可以吃',this.remainCalorie(),`推荐${this.recommend}`) } //3.运动消耗 this.StatsBuilder('饮食摄入', this.expend) } .width('100%') .justifyContent(FlexAlign.SpaceEvenly) .padding({top:30,bottom:35}) } @Builder StatsBuilder(label:string,value:number,tips?:string){ Column({space:CommonConstants.SPACE_6}){ Text(label) .fontColor($r('app.color.gray')) .fontWeight(CommonConstants.FONT_WEIGHT_600) Text(value.toFixed(0 )) .fontSize(20) .fontWeight(CommonConstants.FONT_WEIGHT_700) if (tips){ Text(tips) .fontSize(14) .fontColor($r('app.color.light_gray')) } } } }
import { CommonConstants } from '../../common/constants/CommonConstants' @Component export default struct NutrientStats { carbon :number=24 protein:number=10 fat:number=5 recommendCarbon :number=CommonConstants.RECOMMEND_CARBON recommendProtein :number=CommonConstants.RECOMMEND_PROTEIN recommendFat :number=CommonConstants.RECOMMEND_FAT build() { Row({space:6}){ this.StatsBuilder('碳水化合物',this.carbon,this.recommendCarbon,$r('app.color.carbon_color')) this.StatsBuilder('蛋白质',this.protein,this.recommendProtein,$r('app.color.protein_color')) this.StatsBuilder('脂肪',this.carbon,this.recommendFat,$r('app.color.fat_color')) } .width('100%') .justifyContent(FlexAlign.SpaceEvenly) .padding({top:30,bottom:35}) } @Builder StatsBuilder(label: string, value: number, recommend:number,color:ResourceColor){ Column({space: CommonConstants.SPACE_6}){ Stack() { //层叠关系 // 进度条 Progress({ value: value, total: recommend, type: ProgressType.Ring }) .width(95) .style({ strokeWidth: CommonConstants.DEFAULT_6 }) .color(color) Column({space:6}){ Text('摄入推荐') .fontColor($r('app.color.gray')) .fontSize(12) Text(`${value.toFixed(0)}/${recommend.toFixed(0)}`) .fontSize(18) .fontWeight(CommonConstants.FONT_WEIGHT_600) } } Text(`${label} (克)`) .fontSize(12) .fontColor($r('app.color.light_gray')) } } }
3,记录列表
(1).实现效果图
(2).实现原理
实际上,这是一个由图片和文字组成的list列表,每个列表项都配有相应的图像和描述文本,并且这些列表项被组织在不同的分组中。整个布局采用垂直的列式排列方式,使得内容展示清晰有序。
(3).RecordList.ets
import router from '@ohos.router' @Extend(Text) function grayText(){ .fontSize(14) .fontColor($r('app.color.light_gray')) } @Component export default struct RecordList { build() { List({space:10}){ ForEach([1,2,3,4,5],(item) =>{ ListItem(){ Column(){ //1.分组的标题 Row({space:4}){ Image($r('app.media.ic_breakfast')) .width(24) Text('早餐') .fontSize(18) .fontWeight(700) Text('建议423-592千卡').grayText() Blank() Text('190') .fontSize(14) .fontColor($r('app.color.primary_color')) Text('千卡').grayText() Image($r('app.media.ic_public_add_norm_filled')) .width(20) .fillColor($r('app.color.primary_color')) } .width('100%') //2.组内纪录列表 List(){ ForEach([1,2],(item)=>{ ListItem(){ Row({space:6}){ Image($r('app.media.toast')).width(50) Column({space:4}) { Text('全麦吐司').fontWeight(500) Text('1片').grayText() } Blank() Text('91千卡') .grayText() } .width('100%') .padding(6) }.swipeAction({end:this.deleteButton.bind(this)}) .onClick(()=>this.ToitemList()) }) } .width('100%') } .backgroundColor(Color.White) .width('100%') .borderRadius(18) .padding(12) } .width('100%') }) } .width('94%') .margin({top:10}) .height('100%') } @Builder deleteButton() { Image($r('app.media.ic_public_delete_filled')) .width(20) .fillColor(Color.Red) .margin(5) } ToitemList(){ setTimeout(()=>{ router.replaceUrl({ url:'pages/ItemIndex' }) },1000) } }