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