饮食记录——统计卡片
通过效果图可以看出,统计卡片是由上面的日期信息和下面的统计信息组成,是一个明显的列式布局,将统计卡片封装成为组件,在饮食记录中调用该组件。
import SearchHeader from './SearchHeader'
import StatsCard from './StatsCard'
@Component
export default struct RecordIndex {
build() {
Column(){
//1.头部搜索栏
SearchHeader()
//2.统计卡片
StatsCard()
//3.记录列表
}.width('100%')
.height('100%')
.backgroundColor($r('app.color.index_page_background'))
}
}
一.日期信息部分
日期信息部分是一个行式布局,由日期信息文本和一个图片按钮组成,实现点击该部分可以打开一个日期选择对话框,选择日期,选择的日期成为新的日期信息文本。以下为日期信息部分实现过程:
1.创建行式布局
在行式布局中,新建信息文本和图片按钮,设置其样式
效果如下:
2.创建日期选择对话框
创建对话框的方法在之前的隐私信息对话框中已经涉及,不再赘述,在这里将日期选择对话框封装成组件。
import { CommonConstants } from '../../common/constants/CommonConstants'
@CustomDialog
export default struct DatePickDialog {
controller:CustomDialogController
selectedDate:Date =new Date() //今天
build() {
Column({space:CommonConstants.SPACE_12}){
//1.日期选择器(组件)
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)
}
}({
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)
}
}
由效果图可知该日期选择器组件是一个列式布局,有上部分日期选择区域和下方两个按钮组成。日期选择部分由系统自带组件DatePicker日期选择器构成。如下是其使用方法:
接口
DatePicker(options?: {start?: Date, end?: Date, selected?: Date})
根据指定范围的Date创建可以选择日期的滑动选择器。
参数:
参数名 | 参数类型 | 必填 | 参数描述 |
---|---|---|---|
start | Date | 否 | 指定选择器的起始日期。默认值:Date(‘1970-1-1’) |
end | Date | 否 | 指定选择器的结束日期。默认值:Date(‘2100-12-31’) |
selected | Date | 否 | 设置选中项的日期。默认值:当前系统日期 |
属性
除支持通用属性外,还支持以下属性:
名称 | 参数类型 | 描述 |
---|---|---|
lunar | boolean | 日期是否显示农历。- true:展示农历。- false:不展示农历。默认值:false |
事件
除支持通用事件外,还支持以下事件:
名称 | 功能描述 |
---|---|
onChange(callback: (value: DatePickerResult) => void) | 选择日期时触发该事件。 |
在本项目中,我们设置了日期选择器的开始日期和结束日期,被选择日期默认为当前日期,定义了selectedDate来存储当前选中的日期。所以为日期选择器定义了事件改变的处理事件,当选择日期改变时selectedDate也改变为改变后的日期。
为两个按钮定义了点击事件,当点击“取消”按钮时,退出该对话框,当点击“确定”按钮时,使用AppStorage应用内保存所选择日期的毫秒值。
3.在统计卡片实现日期选择对话框
import { CommonConstants } from '../../common/constants/CommonConstants'
import DateUtil from '../../common/utils/DateUtil'
import DatePickDialog from './DatePickDialog'
@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.统计信息
}
.width(CommonConstants.THOUSANDTH_940)
.backgroundColor($r('app.color.stats_title_bgc'))
.borderRadius(CommonConstants.DEFAULT_18)
}
}
我们首先从应用存储中取出保存的日期选择器所选择日期的毫秒值,生成一个日期选择器对话框对象,设置当点击日期信息时打开日期选择器对话框,并设置日期信息文本框中的日期信息为保存的日期选择器所选择日期的毫秒值所对应的日期,即完成了所选日期设置为统计卡片日期功能,在此时要注意的是,在生成日期选择器对话框对象时,将selectedDate传递给对话框,可以实现在下次打开对话框时,默认选择日期与selectedDate所对应日期相同。
二、统计信息部分
通过效果图会发现,统计信息部分是一个滑动切换的组件,滑动的内容包括热量统计部分,营养素部分。两部分效果如下图所示
1.统计信息部分实现
为了实现滑动的效果,使用Swiper组件实现,同时设置了其穿梭框的颜色,将热量统计部分,营养素统计部分分别封装成不同的组件,在Swiper中调用,实现这两部分滑动切换的效果。
//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.热量统计部分实现
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)
Stack(){ //层叠
//2.还可以吃
//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'))
}
}
}
}
通过效果图,我们发现热量统计部分是一个行式布局,在一行中分为了三列,不难发现,除进度条外,这三部分大致相同,都是由“饮食摄入”等文字加上一个数字组成,只有中间一列多出了一行推荐值文字。所以我们封装了一个组件方法StatsBuilder,用来创建这三列的文字部分,并且在行式布局中,通过传递不同的文本和数字参数,实现这三列的文本和数字。然后使用 Progress组件创建一个进度条,并使用Stack组件将进度条与中间文本叠在一起。关于各数值在注释中已写明,不再赘述,需要注意的是,“还可以吃”的数值=推荐值-摄入值+运动消耗,其值计算已经封装成方法。
3.营养素统计部分实现
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'))
}
}
}
营养素统计部分实现过程与热量统计部分实现过程基本相同,封装成组件的三列结构也相同,通过效果图会发现我们需要传递的参数有下方的label,各种营养素的值和推荐值,进度条的颜色。然后在行式布局中调用传参即可实现该部分。
4.实现的效果