鸿蒙案例-饮食记录-统计卡片的实现

前言

饮食记录应用中的统计卡片系统是该应用的核心功能之一。这个统计卡片系统旨在为用户提供一个直观、清晰且全面的饮食数据概览。通过精心设计的界面和智能的数据分析算法,用户可以快速了解自己的饮食规律、营养摄入情况以及与健康目标的差距。


实现过程

1.StatsCard组件

可以通过日期选择对话框选择一个特定的日期,默认为当前日期的开始时间。选定的日期将用于统计和展示该日的热量和营养素摄入情况。

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热量统计:创建新组件进行调用

2.2营养素统计:创建新组件进行调用

 Swiper(){  
        //2.1.热量统计
        CalorieStats()
        //2.2.营养素统计
        NutrientStats()
      }
      .width('100%')
      .backgroundColor(Color.White)
      .borderRadius(CommonConstants.DEFAULT_18)
      .indicatorStyle({selectedColor: $r('app.color.primary_color')})

2.DatePickDialog组件

对话框的创建

1.日期选择器:使用了DatePicker日期选择器组件,用于根据指定日期范围创建日期滑动选择器。

 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()) 
            //2.关闭窗口
            this.controller.close()
          })
      }

3.CalorieStats组件

热量统计的实现,展示用户的饮食摄入、运动消耗和推荐摄入量等信息,计算剩余卡路里(推荐摄入量减去饮食摄入加上运动消耗)

1.饮食摄入

 this.StatsBuilder('饮食摄入', this.intake)

2.还可以吃:使用stack堆叠容器,子组件按照顺序依次入栈,后一个子组件覆盖前一个子组件。实现层叠关系

2.1.进度条:实现的是一个环形进度条的效果

2.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)

4.NutrientStats组件

展示用户摄入的营养成分(碳水化合物、蛋白质和脂肪)以及推荐的摄入量。

1.碳水化合物;2.蛋白质;3.脂肪

   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'))

代码展示

1.StatsCard.ets

import { CommonConstants } from '../../common/constants/CommonConstants'
import DateUtil from '../../common/utils/DateUtil'
import CalorieStats from './CalorieStats'
import DatePickDialog from './DatePickDialog'
import NutrientStats from './NutrientStats'
@Component
export default struct StatsCard {

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

 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(){  //Swiper滑动组件
        //2.1.热量统计
        CalorieStats()
        //2.2.营养素统计
        NutrientStats()
      }
      .width('100%')
      .backgroundColor(Color.White)
      .borderRadius(CommonConstants.DEFAULT_18)
      .indicatorStyle({selectedColor: $r('app.color.primary_color')}) //穿梭框样式
    }
    .width(CommonConstants.THOUSANDTH_940)
    .backgroundColor($r('app.color.stats_title_bgc'))
    .borderRadius(CommonConstants.DEFAULT_18)
  }
}

2.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()) //内部存储,存储的是毫秒值
            //2.关闭窗口
            this.controller.close()
          })
      }
    }
    .padding(CommonConstants.SPACE_12)
  }
}

3.CalorieStats.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({
          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(12)
          .fontColor($r('app.color.light_gray'))
      }
    }
  }
}

4.NutrientStats.ets

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}){
      // 1.碳水化合物
      this.StatsBuilder('碳水化合物', this.carbon,this.recommendCarbon,$r('app.color.carbon_color'))
      // 2.蛋白质
      this.StatsBuilder('蛋白质', this.protein,this.recommendProtein,$r('app.color.protein_color'))
      // 3.脂肪
      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){
    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'))
    }
  }
}

页面效果

总结

    该统计卡片包括日期选择、热量统计和营养素统计等功能。StatsCard主要包含了三个组件:DatePickDialog,CalorieStats和NutrientStats。StatsCard为统计卡片的主组件,包含日期选择、热量统计和营养素统计;DatePickDialog组件用于对话框的创建,包含日期选择器和按钮的功能;NutrientStats组件展示用户摄入的营养成分(碳水化合物、蛋白质和脂肪)以及推荐的摄入量。

    在设计此项目时也学习了很多新组件的用法。例如Swiper组件可以实现手机、平板等移动端设备上的图片轮播效果,支持无缝轮播、自动播放、响应式布局等功能。Swiper轮播图具有使用简单、样式可定制、功能丰富、兼容性好等优点,是很多网站和移动应用中常用的轮播图插件。Swiper是一个容器组件,如果自身尺寸没有被设置,它会根据子组件大小自动调整自身尺寸。如果开发者给Swiper设置了固定尺寸,那么在轮播过程中,Swiper的尺寸将一直保持设置的固定尺寸。如果未设置固定尺寸,Swiper会根据子组件大小自动调整自身尺寸。

    层叠布局通过Stack容器组件实现位置的固定定位与层叠,容器中的子元素(子组件)依次入栈,后一个子元素覆盖前一个子元素,子元素可以叠加,也可以设置位置。层叠布局具有较强的页面层叠、位置定位能力,其使用场景有广告、卡片层叠效果等。Stack容器中兄弟组件显示层级关系可以通过Z序控制的zIndex属性改变。zIndex值越大,显示层级越高,即zIndex值大的组件会覆盖在zIndex值小的组件上方。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值