总体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)
}
}
运行截图:
总结:
- 布局组件:
Row
是Flutter中常用的布局组件之一,用于水平排列子组件。 - 自定义组件:
Search
和Badge
组件是自定义组件,用于提供特定的UI功能。 - 资源管理:通过
$r
函数引用资源,有助于资源的统一管理和多语言支持。 - 样式定制:通过
.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)
}
}
列表记录小结:
- 组件化:理解如何在Flutter应用中创建和使用自定义组件。
- 状态管理:掌握使用
@State
、@Consume
和@Watch
注解进行状态管理和数据监听。 - 列表渲染:熟悉如何使用
List
和ListItem
组件以及ForEach
方法来渲染列表数据。 - 事件处理:理解如何为组件添加点击事件和侧滑删除事件。
- 路由导航:掌握如何在应用中进行页面跳转和参数传递。
- 资源管理:熟悉资源文件的使用和管理,以及如何引用图标和颜色资源。
- 样式定制:能够使用样式方法定制组件的外观。
- 扩展和自定义函数:理解如何扩展现有组件并创建自定义函数来简化样式设置。
饮食卡片总结:
1.日期文本点击运用到的组件:DatePicker;滑动卡片用:Swiper
2.使用@State
注解定义状态变量,如info
和selectedDate
。
3.对话框(Dialog)**:使用@CustomDialog
注解定义自定义对话框,如DatePickDialog
4.
日期选择(DatePicker)**:使用DatePicker
组件实现日期选择功能。
5.进度条(Progress)**:使用Progress
组件展示进度信息。
6.利用DatePicker和Swiper
参数名 | 参数类型 | 必填 | 参数描述 |
controller | 否 | 给组件绑定一个控制器,用来控制组件翻页。 |