鸿蒙HeimaHealthy学习记录--饮食记录

目录

一、 效果展示

二、 功能概述

1.  顶部搜索栏

2.  统计卡片    

3. 记录列表

三、 组件实现

1.  顶部搜索栏

2.  统计卡片

3. 记录列表

四、 完整代码展示

1.  食物记录页RecordIndex.ets

2. 头部搜索栏SearchHeader.ets

3. 统计卡片 StatCard.ets

(1) 统计卡片总视图StatsCard.ets 

(2) 日期信息

        b  日期组件--返回选择日期,返回开始日期   DateUtil.ets

(3)统计信息

4. 记录列表 RecordList.ets

五、 问题记录与收获

1.  日期选择器DatePicker组件学习

2. 如何在滑动选择日期后将日期全局保存下来?

3. AppStorage.SetOrCreate('selectedDate', this.selectedDate)发生错误。

4. Swiper滑块视图显示蓝色,无法修改背景色为白色


 


一、 效果展示

6374aa4c0287406292a6ffc4f0f0d6b0.png9510800b1bbf4fcda1182f19e0c7bdd0.png90ca2e8347c140be89e19f3013518531.png

二、 功能概述

1.  顶部搜索栏

        页面里边包含一个头部搜索栏组件,一个统计卡片和食物记录列表。头部搜索栏组件由一行布局(Row) 组成,包含一个搜索栏和一个实现带有角标/小数字显示的信封图标,显示有多少未读消息。

2.  统计卡片    

        然后搜索栏下边是一个统计卡片组件,包括顶部有一个日期信息显示和下面的热量统计和营养素统计。卡片上方是一个日期选择器,用户可以滑动选择日期。然后下面是两个可以横向滑动切换的视图--热量统计视图和营养素统计视图。热量统计视图显示饮食摄入、还可以吃和运动消耗的统计信息。使用环形进度条直观显示摄入量和推荐值的比例。计算还可以吃多少,并显示在界面上。营养素统计视图显示碳水化合物、蛋白质和脂肪的摄入量和推荐值的比例,用环形进度条显示摄入量和推荐值的比例。

3. 记录列表

        记录列表里面包含餐食类别(早餐,晚餐等),每种类别下面有对应的食物种类。每个食物记录项包括一个分组标题和组内记录列表(食物种类)。组内记录列表展示了具体的食物名称、数量和热量。向左拉食物选项会显示删除按钮。

三、 组件实现

1.  顶部搜索栏

        整个组件包含在一个Row容器里面,罕有一个Search搜索栏组件,和一个信封图标且带有小角标(角标/小数字显示使用Badge组件),为了显示有多少未读消息。

2.  统计卡片

        在统计卡片视图StatsCard组件里边用Column容器包住日期选择组件DatePickDialog,和两个统计视图组件切换。日期弹窗DatePickDialog来滑动选择日期,同时用DateUtil日期组件来返回传递日期。然后是Swiper()组件切换两个统计视图--热量统计组件CalorieStats与营养统计组件NutrientStats。热量统计组件CalorieStats里边remainCalorie()方法用于计算还可以摄入多少热量,通过推荐值减去摄入量再加上运动消耗量得出结果。StatsBuilder()方法用于统一构建展示统计数据的样式,包括标签、数值和提示信息的显示。营养统计组件NutrientStats里边build()方法包括碳水化合物、蛋白质和脂肪的展示。用StatsBuilder()方法来实现展示营养素统计数据的样式。

3. 记录列表

        使用ForEach循环生成多个ListItem,每个ListItem包含分组标题(早餐等)和组内记录列表(食物种类)。分组标题使用Row和Text组件展示食物类别、建议热量和总热量,并设置相应的样式。组内记录列表使用嵌套的List和ForEach循环生成多个记录项,每个记录项包含食物图片、名称、数量和热量。每个记录项右侧设置一个删除按钮,点击按钮触发deleteButton函数,生成一个带有删除图标的Image组件。

四、 完整代码展示

1.  食物记录页RecordIndex.ets

/**
 * 食物记录页面
 */
import DateUtil from '../../common/utils/DateUtil'
import RecordService from '../../service/RecordService'
import RecordVO from '../../viewmodel/RecordVO'
import RecordList from './RecordList'
import SearchHeader from './SearchHeader'
import StatsCard from './StatsCard'
@Component
  //饮食记录组件
export default struct RecordIndex {

  @StorageProp('selectedDate')
  selectedDate: number = DateUtil.beginTimeOfDay(new Date())

  build() {
    Column(){
      // 1.头部搜索栏
      SearchHeader()
      // 2.统计卡片
      StatsCard()
      // 3.记录列表
      RecordList()
        .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
    .backgroundColor($r('app.color.index_page_background'))
  }
}

2. 头部搜索栏SearchHeader.ets

/**
 * 头部搜索栏组件
 */
import { CommonConstants } from '../../common/constants/CommonConstants'
@Component
export default struct SearchHeader {
  build() {
    Row({space: CommonConstants.SPACE_6}){
      /**
       * 搜索栏组件Search和TextInput使用相似
       */
      Search({placeholder: '搜索饮食或运动信息'})//搜索提示
        .textFont({size: 18})//字体信息
        .layoutWeight(1)//占据剩余空间
      /**
       * 组件Bage  角标/小数字显示
       */
      Badge({count: 1, position: BadgePosition.RightTop, style: {fontSize: 12}}){
        Image($r('app.media.ic_public_email'))
          .width(24)
      }
    }
    .width(CommonConstants.THOUSANDTH_940)
  }
}

3. 统计卡片 StatCard.ets

(1) 统计卡片总视图StatsCard.ets 

/**
 * 统计卡片组件
 */
import BreakpointType from '../../common/bean/BreanpointType'
import BreakpointConstants from '../../common/constants/BreakpointConstants'
import { CommonConstants } from '../../common/constants/CommonConstants'
import DateUtil from '../../common/utils/DateUtil'
import CalorieStats from './CalorieStats'
import DatePickDialog from './DatePickDialog'
import NutrientStats from './NutrientStats'
/*
import RecordService from '../../service/RecordService'
import RecordVO from '../../viewmodel/RecordVO'
import StatsInfo from '../../viewmodel/StatsInfo'
* */
@Component
export default struct StatsCard {
  //读取AppStorage存储的值
  @StorageProp('selectedDate') selectedDate: number = DateUtil.beginTimeOfDay(new Date())//DateUtil.beginTimeOfDay获取开始日期的功能
  @StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointConstants.BREAKPOINT_SM
  /*
  @Consume @Watch('handleRecordsChange') records: RecordVO[]
  @State info: StatsInfo = new StatsInfo()

  handleRecordsChange(){
    this.info = RecordService.calculateStatsInfo(this.records)
  }
  */
  controller: CustomDialogController = new CustomDialogController({
    builder: DatePickDialog({selectedDate: new Date(this.selectedDate)})
  })
  build() {
    Column(){
      // 1.日期信息
      Row(){
        Text(DateUtil.formatDate(this.selectedDate))
          .fontColor($r('app.color.secondary_color'))
        Image($r('app.media.ic_public_spinner'))//向下拉的图片
          .width(20)
          .fillColor($r('app.color.secondary_color'))
      }
      .padding(CommonConstants.SPACE_8)//内边距
      .onClick(() => this.controller.open())

      // 2.统计信息
      Swiper(){
        // 2.1.热量统计
        CalorieStats()
        // 2.2.营养素统计
        NutrientStats()
      }
      .width('100%')
      .backgroundColor(Color.White)
      .borderRadius(CommonConstants.DEFAULT_18)//边框圆角18
      .indicatorStyle({selectedColor: $r('app.color.primary_color')})//穿梭框的样式,颜色从蓝色改为绿色
      .displayCount(new BreakpointType({
        sm: 1,
        md: 1,
        lg: 2
      }).getValue(this.currentBreakpoint))
    }
    .width(CommonConstants.THOUSANDTH_940)//常量值
    .backgroundColor($r('app.color.stats_title_bgc'))//颜色
    .borderRadius(CommonConstants.DEFAULT_18)//边框角度
  }
}

(2) 日期信息

        a  日期滑动选择组件DatePickDialog.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('selectedDate', this.selectedDate.getTime())//这里不要存日期data对象,因为日期data对象将来去作为状态变量去监控的时候会有问题,就是用@State/@Prop等对日期对象进行监控的时候会出现错误。
            //gettime是一个时间number可以监控。
            // 2.关闭窗口
            this.controller.close()
          })
      }
    }
    .padding(CommonConstants.SPACE_12)
  }
}

        b  日期组件--返回选择日期,返回开始日期   DateUtil.ets

/**
 * 日期组件
 */
class DateUtil{
  //返回显示日期
  formatDate(num: number): string{
    let date = new Date(num)
    let year = date.getFullYear()//获取年份
    let month = date.getMonth()+1//获取月份  data是0-11月
    let day = date.getDate()//获取日
    let m = month < 10 ? '0' + month : month//三目选择语句
    let d = day < 10 ? '0' + day : day
    return `${year}/${m}/${d}`
  }
  //返回开始日期。
  beginTimeOfDay(date: Date){
    let d = new Date(date.getFullYear(), date.getMonth(), date.getDate())
    return d.getTime()
  }
}

let dateUtil = new DateUtil()

export default dateUtil as DateUtil

(3)统计信息

        热量统计组件CalorieStats.ets与营养统计组件NutrientStats. ets   (这两个组件实现很相似)

/**
 * 热量统计组件
 * 饮食摄入、还可以吃、运动消耗。
 */
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(//进度条组件
          this.intake,//摄入
          this.recommend,//推荐值
          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})
  }
  //统一样式
  //label文字说明
  @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(12)
          .fontColor($r('app.color.light_gray'))
      }
    }
  }
}

/**
 * 营养素统计组件
 */
import { CommonConstants } from '../../common/constants/CommonConstants'
@Component
export default struct NutrientStats {
  //碳水、蛋白质、脂肪
  carbon: number =23
  protein: number=9
  fat: number =7

  //推荐碳水化合物
  recommendCarbon: number = CommonConstants.RECOMMEND_CARBON
  //推荐蛋白质
  recommendProtein: number = CommonConstants.RECOMMEND_PROTEIN
  //推荐脂肪
  recommendFat: number = CommonConstants.RECOMMEND_FAT

  build() {
    Row({space: CommonConstants.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.fat,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: ResourceStr}){
  @Builder StatsBuilder(label: string, value: number,recommend: number, color: ResourceStr){
    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: CommonConstants.SPACE_6}){
          Text('摄入推荐')
            .fontSize(12)
            .fontColor($r('app.color.gray'))

          Text(`${value.toFixed(0)}/${recommend.toFixed(0)}`)
            .fontSize(18)
            .fontWeight(CommonConstants.FONT_WEIGHT_600)
        }
      }
      Text(`${label}(克)`)
        .fontSize(12)
        .fontColor($r('app.color.light_gray'))
    }
  }
}

4. 记录列表 RecordList.ets

/*
食物记录组件
*/
import { CommonConstants } from '../../common/constants/CommonConstants'
@Extend(Text) function grayText(){
  .fontSize(14)
  .fontColor($r('app.color.light_gray'))
}
@Component
export default struct RecordList{
  build() {
    List({space: CommonConstants.SPACE_10}){
      ForEach([1,2,3,4,5],(item) => {
        ListItem(){
          Column(){
            // 1.分组的标题
            Row({space: CommonConstants.SPACE_4}){
              Image($r('app.media.ic_breakfast')).width(24)
              Text('早餐').fontSize(18).fontWeight(CommonConstants.FONT_WEIGHT_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: CommonConstants.SPACE_6}){
                    Image(item.recordItem.image).width(50)
                    Column({space: CommonConstants.SPACE_4}){
                      Text('全麦吐司').fontWeight(CommonConstants.FONT_WEIGHT_500)
                      Text('1片').grayText()
                    }
                    Blank()
                    Text('91千卡').grayText()
                  }
                  .width('100%')
                  .padding(CommonConstants.SPACE_6)
                }.swipeAction({end: this.deleteButton.bind(this)})
              })
            }
            .width('100%')
          }
          .width('100%')
          .backgroundColor(Color.White)
          .borderRadius(CommonConstants.DEFAULT_18)
          .padding(CommonConstants.SPACE_12)
        }
      })
    }
    .width(CommonConstants.THOUSANDTH_940)
    .height('100%')
    .margin({top: 10})
  }
  @Builder deleteButton(){
    Image($r('app.media.ic_public_delete_filled'))
      .width(20)
      .fillColor(Color.Red)
      .margin(5)
  }
}

 

 

五、 问题记录与收获

1.  日期选择器DatePicker组件学习

DatePicker日期选择器组件,用于根据指定日期范围创建日期滑动选择器。接口:DatePicker(options?: {start?: Date, end?: Date, selected?: Date})

satrt :指定选择器的起始日期。end:指定选择器的结束日期。selected:

设置选中项的日期。默认值:当前系统日期

然后它还有一个属性lunar用来选择是否展示农历。

7392ec550b244d2c9b5ca6992829f35d.png

2. 如何在滑动选择日期后将日期全局保存下来?

在Tabs组件里使用onChange方法记录传入tabBar的角标值,根据记录的角标值重新渲染。

3. AppStorage.SetOrCreate('selectedDate', this.selectedDate)发生错误。

//应用的内部存储
这里不要存日期data对象,因为日期data对象将来去作为状态变量去监控的时候会有问题,就是用@State/@Prop等对日期对象进行监控的时候会出现错误。

而gettime是返回一个时间 number类型 可以监控。

所以这里程序修改为AppStorage.SetOrCreate('selectedDate', this.selectedDate.getTime()).

4. Swiper滑块视图显示蓝色,无法修改背景色为白色

Swiper滑块视图容器,提供子组件滑动轮播显示的能力。

接口:Swiper(controller?: SwiperController)

初步代码:

4e9bb00b27e44ac58d2bb8bd0b6e4be5.png

效果4c73f6c872894440a35df813f494ee99.png6068e12eaddf454c926aa7074347e9bc.png

解决:回顾视频发现,整个蓝色背景色是文本自带的背景色,删除文本样式backgroundColor(0xAFEEEE)后,视图变为白色。

65c8530674de4eae93602b4d4556766e.png

 

 

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值