黑马健康3 实现饮食记录

总体UI设计:

一:顶部搜索栏的实现:

代码

SerachHeader.ets

import { CommonConstants } from '../../common/constants/CommonConstants'
@Component
export default struct SearchHeader {
  build() {
    Row({space:CommonConstants.SPACE_6}){//两个组件 搜索组件 角标组件
      Search({placeholder:'搜索饮食或者运动信息'})//搜做输入框
        .textFont({size:18})
      Badge({count:1,position:BadgePosition.RightTop,style:{fontSize:14}})//容器型
      {
        Image($r('app.media.ic_public_email'))
          .width(28)
      }

    }
    .width(CommonConstants.THOUSANDTH_800)
  }
}

运行截图:

总结:

  1. 布局组件Row是Flutter中常用的布局组件之一,用于水平排列子组件。
  2. 自定义组件SearchBadge组件是自定义组件,用于提供特定的UI功能。
  3. 资源管理:通过$r函数引用资源,有助于资源的统一管理和多语言支持。
  4. 样式定制:通过.textFont.width等方法对组件的字体大小和宽度进行定制。

二:统计卡片

这里面包括一个日期弹窗,以及一个滑动卡片

代码:

统计卡片

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 RecordService from '../../RecordService/RecordService'
import RecordVO from '../../viewmodel/RecordVO'
import StatsInfo from '../../viewmodel/StatsInfo'
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())
@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'))
          .fillColor($r('app.color.secondary_color'))
          .width(20)//不添加的话就会铺满整个屏幕
          .padding(CommonConstants.SPACE_12)//内边距
          .onClick(()=>this.controller.open())
      }
      // 2.统计信息
      Swiper()
      {
        //2.1热量统计
        CalorieStats({intake:this.info.intake,expend:this.info.expend})
        //2.2营养素统计
 NutrientStats({carbon:this.info.carbon,protein:this.info.protein,fat:this.info.fat})
      }
      .width('100%')
      .backgroundColor(Color.White)
      .borderRadius(CommonConstants.DEFAULT_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)

  }
}

日期选择弹窗组件:

DatePickDialog.ets

//弹窗组件
import { CommonConstants } from '../../common/constants/CommonConstants'
@CustomDialog
export default struct DatePickDialog {
  //对话框一定要有
  controller:CustomDialogController
  selectedDate:Date = new Date()
  build() {
    Column(){
      //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(){
        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)
  }
}

运行截图:

滑动卡片:

因为是个滑动组件,有两个卡片的内容,所以这里面也包含两部分,一部分是热量消耗(CalorieStats),一部分是营养素(NutrientStats)。

CalorieStats.ets

import { CommonConstants } from '../../common/constants/CommonConstants'
@Component//热量统计

export default struct CalorieStats {
  @Prop intake:number
  @Prop expend:number
  recommend:number = CommonConstants.RECOMMEND_CALORIE
  remainCalorie()
  {
    return this.recommend-this.intake+this.expend
  }

  build() {
    Row({space:CommonConstants.SPACE_6})
    {
      //1.饮食摄入
      this.statsBuilder({label:'饮食摄入',value:this.intake})
      //2.还可以吃
      Stack(){
        //2.1进度条
        Progress({
          value:this.intake,
          total:this.recommend,
          type:ProgressType.Ring
        })
          .width(130)
          .style({strokeWidth:CommonConstants.DEFAULT_10})//环的粗细
          .color($r('app.color.primary_color'))

        //2.2统计数据
      this.statsBuilder({label:'还可以吃',value:this.remainCalorie(),tips:`推荐摄入${this.remainCalorie()}`})}
      //3.运动消耗
      this.statsBuilder({label:'运动消耗',value: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'))
      }
    }

  }}

NutrientStats.ets

import { CommonConstants } from '../../common/constants/CommonConstants'
@Component//热量统计

export default struct NutrientStats {
  @Prop carbon: number
  @Prop protein: number
  @Prop fat: number
  recommendCarbon: number = CommonConstants.RECOMMEND_CARBON
  recommendProtein: number = CommonConstants.RECOMMEND_PROTEIN
  recommendFat: number = CommonConstants.RECOMMEND_FAT

  build() {
    Row({space: CommonConstants.SPACE_6}){
      this.StatsBuilder({
        label: '碳水化合物',
        value: this.carbon,
        recommend: this.recommendCarbon,
        color: $r('app.color.carbon_color')
      })
      this.StatsBuilder({
        label: '蛋白质',
        value: this.protein,
        recommend: this.recommendProtein,
        color: $r('app.color.protein_color')
      })
      this.StatsBuilder({
        label: '脂肪',
        value: this.fat,
        recommend: this.recommendFat,
        color: $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'))
    }
  }
}

运行截图:

记录列表:

RecordList.ets

import router from '@ohos.router'
import { CommonConstants } from '../../common/constants/CommonConstants'
import RecordService from '../../RecordService/RecordService'
import GroupInfo from '../../viewmodel/GroupInfo'
import RecordType from '../../viewmodel/RecordType'
import RecordVO from '../../viewmodel/RecordVO'
@Extend(Text) function grayText(){
  .fontSize(14)
  .fontColor($r('app.color.light_gray'))
}
@Component
//列表LIST
export default struct RecordList {
  @Consume @Watch('handleRecordsChange') records:RecordVO[]
  @State groups:GroupInfo<RecordType,RecordVO>[] = []
  handleRecordsChange(){
    this.groups = RecordService.calculateGroupInfo(this.records)
  }
  build() {
    List({space:CommonConstants.SPACE_10})
    {
      ForEach(this.groups,(group:GroupInfo<RecordType,RecordVO>)=>{
        ListItem()
        {
          Column()
          {
            //1.分组的标题
            Row({space:CommonConstants.SPACE_4})
            {
              Image(group.type.icon).width(24)
              Text(group.type.name).fontSize(16).fontWeight(CommonConstants.FONT_WEIGHT_700)
              Text(`建议${group.type.min}-${group.type.max}千卡`).grayText()
              Blank()
              Text(group.calorie.toFixed(0)).fontSize(12).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%')
            .onClick(()=>
            {
              router.pushUrl({
                url:'pages/ItemIndex',
                params:{type:group.type}//跳转信息
              })
            })
            //2.组内记录列表
            List()
            {
              ForEach(group.items/*组内数据*/,(item:RecordVO)=>{
                ListItem(){
                  Row({space:CommonConstants.SPACE_4}){
                    Image(item.recordItem.image).width(50)
                    Column()
                    {/*记录项的名字*/
                      Text(item.recordItem.name).fontWeight(CommonConstants.FONT_WEIGHT_500)
                      Text(`${item.amount}${item.recordItem.unit}`).grayText()
                    }
                    Blank()
                    Text(`${item.calorie.toFixed(0)}千卡`).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)
        }
      })
    }
    .height('100%')
    .width(CommonConstants.THOUSANDTH_940)//宽度 加一个百分之94
    .margin({top:10})
  }
  @Builder deleteButton(){//侧滑产生的图标是啥样式
    Image($r('app.media.ic_public_delete_filled'))
      .width(20)
      .fillColor(Color.Red)
      .margin(5)
  }
}

列表记录小结:

  1. 组件化:理解如何在Flutter应用中创建和使用自定义组件。
  2. 状态管理:掌握使用@State@Consume@Watch注解进行状态管理和数据监听。
  3. 列表渲染:熟悉如何使用ListListItem组件以及ForEach方法来渲染列表数据。
  4. 事件处理:理解如何为组件添加点击事件和侧滑删除事件。
  5. 路由导航:掌握如何在应用中进行页面跳转和参数传递。
  6. 资源管理:熟悉资源文件的使用和管理,以及如何引用图标和颜色资源。
  7. 样式定制:能够使用样式方法定制组件的外观。
  8. 扩展和自定义函数:理解如何扩展现有组件并创建自定义函数来简化样式设置。

饮食卡片总结:

1.日期文本点击运用到的组件:DatePicker;滑动卡片用:Swiper

2.使用@State注解定义状态变量,如infoselectedDate

3.对话框(Dialog)**:使用@CustomDialog注解定义自定义对话框,如DatePickDialog

4.日期选择(DatePicker)**:使用DatePicker组件实现日期选择功能。

5.进度条(Progress)**:使用Progress组件展示进度信息。

6.利用DatePicker和Swiper

参数名

参数类型

必填

参数描述

controller

SwiperController

给组件绑定一个控制器,用来控制组件翻页。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值