食物列表页是在之前的实物记录页面的基础上实现的一个路由页面,就是在食物记录页面点击某个食物便可跳转到该食物的列表页。
首先食物列表页分为三个部分包括:一.头部导航 、二..食物列表 、三.底部面板,整个页面则是一个column列式布局。
我们来看第一部分头部导航,他就是一个简单的row行式布局,其中包含了一个Text文本和一个image组件,下面便是相关的封装后的代码:
@Builder Header(){
Row(){
Image($r('app.media.ic_public_back'))
.width(30)
.onClick(() => router.back())
Blank()
Text('早餐').fontSize(20).fontWeight(CommonConstants.FONT_WEIGHT_600)
}
.width(CommonConstants.THOUSANDTH_940)
}
接着便是第二部分食物列表,由于其代码比较复杂,便单独一个页面来写,然后在导出后再路由页面使用。实物列表主要用了Tabs容器组件,可以实现通过页签进行内容视图切换,其内部有多个TabContent来实现每个页签的内容编写。之后便没有新要学习的东西了,皆是之前用过的。下面便是相应的代码:
import { CommonConstants } from '../../common/constants/CommonConstants'
@Component
export default struct ItemList {
showPanel: () => void
build() {
Tabs(){
TabContent(){
this.TabContentBuilder()
}
.tabBar('全部')
TabContent(){
this.TabContentBuilder()
}
.tabBar('主食')
TabContent(){
this.TabContentBuilder()
}
.tabBar('肉蛋奶')
}
.width(CommonConstants.THOUSANDTH_940)
.height('100%')
}
@Builder TabContentBuilder(){
List({space:CommonConstants.SPACE_10}){
ForEach([1,2,3,4,5,6], (item) => {
ListItem(){
Row({space: CommonConstants.SPACE_6}){
Image($r('app.media.toast')).width(50)
Column(){
Text('全麦吐司').fontWeight(CommonConstants.FONT_WEIGHT_500)
Text('91千卡/片').fontSize(14).fontColor($r('app.color.light_gray'))
}
Blank()
Image($r('app.media.ic_public_add_norm_filled'))
.width(18)
.fillColor($r('app.color.primary_color'))
}
.width('100%')
.padding(CommonConstants.SPACE_6)
}
.onClick(()=> this.showPanel())
})
}
.width('100%')
.height('100%')
}
}
再者便是第三部分底部面板,面板上的内容构建分为四个部分包括:1.顶部日期 、2.记录卡片、3. 数字键盘 、4.按钮。1.顶部日期和之前的头部导航差不多,都是row行式布局,其内包含着Text文本组件和image图片组件,按理说顶部日期应该也需要有一个弹窗来实现日期的勾选,可惜自己时间有限而且期末也快到了,便没再进步完善。下面便是相应的的代码:
import { CommonConstants } from '../../common/constants/CommonConstants'
@Component
export default struct ItemPanelHeader {
build() {
Row(){
Text('2024年1月25日 早餐')
.fontSize(18).fontWeight(CommonConstants.FONT_WEIGHT_600)
Image($r('app.media.ic_public_spinner'))
.width(20)
.fillColor(Color.Black)
}
.width(CommonConstants.THOUSANDTH_940)
.justifyContent(FlexAlign.Center)
}
}
2.记录卡片和顶部日期差不多,只不过是复杂了点,其内部列式布局和行式布局相互套用、Text文本组件和image图片组件不断的调用,来实现更好效果的页面布局和分布。在其中运用两个比较新的知识,一个是分隔器组件Divider(),实现分隔不同内容块/内容元素,另一个便是透明度属性.opacity(),通过设置透明比率来达到对应的透明效果,使用Promise形式返回。下面便是相应的的代码:
import { CommonConstants } from '../../common/constants/CommonConstants'
@Component
export default struct ItemCard {
@Prop amount: number //状态变量,从自定义键盘中获取数据
build() {
Column({space: CommonConstants.SPACE_8}){
//1.图片
Image($r('app.media.toast'))
.width(150)
//2.名称
Row(){
Text('全麦吐司')
.fontSize(22)
.fontWeight(CommonConstants.FONT_WEIGHT_700)
}
.backgroundColor($r('app.color.lightest_primary_color'))
.padding({top:5,bottom:5,left:12,right:12})
Divider().width(CommonConstants.THOUSANDTH_940).opacity(0.6)
//3.营养素
Row({space: CommonConstants.SPACE_8}){
this.NutrientInfo('热量(千卡)', 91.0)
this.NutrientInfo('碳水(克)', 15.5)
this.NutrientInfo('蛋白质(克)', 4.4)
this.NutrientInfo('脂肪(克)', 1.3)
}
//分隔器组件,分隔不同内容块/内容元素。 //通过设置透明比率来让PixelMap达到对应的透明效果,使用Promise形式返回。
Divider().width(CommonConstants.THOUSANDTH_940).opacity(0.6)
//4.数量
Row(){
Column({space:CommonConstants.SPACE_4}){
Text(this.amount.toFixed(1))//toFixed()是指将数字转化为指定小数位数的字符串,1是指保留一位小数
.fontSize(50).fontColor($r('app.color.primary_color'))
.fontWeight(CommonConstants.FONT_WEIGHT_600)
Divider().color($r('app.color.primary_color'))
}
.width(150)
Text('片')
.fontSize(25)
.fontColor($r('app.color.light_gray'))
.fontWeight(CommonConstants.FONT_WEIGHT_600)
}
}
}
@Builder NutrientInfo(label: string,value:number){
Column({space: CommonConstants.SPACE_8}){
Text(label).fontSize(14).fontColor($r('app.color.light_gray'))
Text((value * this.amount).toFixed(1)).fontSize(18).fontWeight(CommonConstants.FONT_WEIGHT_700)
}
}
}
3.数字键盘,运用Grid网格容器和GridItem网格容器中单项内容容器,来实现自定义键盘的构建,下面便是相应的代码:
import { CommonConstants } from '../../common/constants/CommonConstants'
@Component
export default struct NumberKeyboard {
numbers: string[] = ['1','2','3','4','5','6','7','8','9','0','.']
@Link amount: number
@Link value: string //用来记录用户输入的内容
@Styles keyBoxStyle(){
.backgroundColor(Color.White)
.borderRadius(8)
.height(60)
}
build() {
Grid(){
ForEach(this.numbers, num => {
GridItem(){
Text(num).fontSize(20).fontWeight(CommonConstants.FONT_WEIGHT_900)
}
.keyBoxStyle()
.onClick(() => this.clickNumber(num))
})
GridItem(){
Text('删除').fontSize(20).fontWeight(CommonConstants.FONT_WEIGHT_900)
}
.keyBoxStyle()
.onClick(() => this.clickDelete())
}
.width('100%')
.height(280)
.backgroundColor($r('app.color.index_page_background'))
.columnsTemplate('1fr 1fr 1fr')
.columnsGap(8)
.rowsGap(8)
.padding(8)
.margin({top:20})
}
clickNumber(num: string){
///1.拼接用户输入的内容
let val = this.value + num
//2.校验输入格式是否正确
let firstIndex = val.indexOf('.')
let lastIndex = val.lastIndexOf('.')
if(firstIndex !== lastIndex || (lastIndex != -1 && lastIndex < val.length - 2)){
//非法输入
return
}
//3.将字符串转为数值
let amount = this.parseFloat(val)
//4.保存
if(amount >= 999.9){
this.amount = 999.9
this.value = '999'
}else{
this.amount = amount
this.value = val
}
}
clickDelete(){
if(this.value.length <= 0){
this.value = ''
this.amount = 0
return
}
this.value = this.value.substring(0,this.value.length -1)
this.amount = this.parseFloat(this.value)
}
parseFloat(str: string){
if (!str){
return 0
}
if(str.endsWith('.')){
str = str.substring(0, str.length - 1)
}
return parseFloat(str)
}
}
4.按钮就比较简单,一件简单的行式布局,其内包含两个按钮,一个取消、一个提交。然后给两个按钮添加点击事件onclick()。下面便是相关的代码:
Row({space: CommonConstants.SPACE_8}){
Button('取消')
.width(120)
.backgroundColor($r('app.color.primary_color'))
.type(ButtonType.Normal)
.borderRadius(6)
.onClick(()=> this.showPanel = false)
Button('提交')
.width(120)
.backgroundColor($r('app.color.primary_color'))
.type(ButtonType.Normal)
.borderRadius(6)
.onClick(()=> this.showPanel = false)
}
.margin({top:10})
最后便是整个路由页面的代码和一些运行截图:
import router from '@ohos.router'
import { CommonConstants } from '../common/constants/CommonConstants'
import ItemCard from '../view/Item/ItemCard'
import ItemList from '../view/Item/ItemList'
import ItemPanelHeader from '../view/Item/ItemPanelHeader'
import NumberKeyboard from '../view/Item/NumberKeyboard'
@Entry
@Component
struct ItemIndexPage {
@State amount: number = 1
@State value: string = ''
@State showPanel: boolean = false
onPanelShow(){
this.showPanel = true
this.amount = 1
this.value = ''
}
build() {
Column() {
//1.头部导航
this.Header()
//2.列表
ItemList({showPanel: this.onPanelShow.bind(this)})
.layoutWeight(1)
//3.底部面板 //Panel面板不占高度,是浮在容器的表面,要求是它所在容器的高度和其里面元素的高度
//是保持固定,而列表则是动态,通过.layoutWeight来实现高度的固定。
Panel(this.showPanel){
//3.1 顶部日期
ItemPanelHeader()
//3.2 记录卡片
ItemCard({amount: this.amount})
//3.3 数字键盘
NumberKeyboard({amount: $amount,value: $value})
//3.4 按钮
Row({space: CommonConstants.SPACE_8}){
Button('取消')
.width(120)
.backgroundColor($r('app.color.primary_color'))
.type(ButtonType.Normal)
.borderRadius(6)
.onClick(()=> this.showPanel = false)
Button('提交')
.width(120)
.backgroundColor($r('app.color.primary_color'))
.type(ButtonType.Normal)
.borderRadius(6)
.onClick(()=> this.showPanel = false)
}
.margin({top:10})
}
.mode(PanelMode.Full)
.dragBar(false)
.backgroundMask($r('app.color.light_gray'))
.backgroundColor(Color.White)
}
.width('100%')
.height('100%')
}
@Builder Header(){
Row(){
Image($r('app.media.ic_public_back'))
.width(30)
.onClick(() => router.back())
Blank()
Text('早餐').fontSize(20).fontWeight(CommonConstants.FONT_WEIGHT_600)
}
.width(CommonConstants.THOUSANDTH_940)
}
}