案例——食物列表

一、食物列表主页

主要功能:

前面记录页面完成,记录有早餐、午餐、晚餐和运动的分类,当点击某个分组时会跳转到食物列表案例,本模块实现的是食物列表页。

代码:

,当点击饮食记录里面的分组时就会跳转到响应的页record是之前的页面,Item Index的页面组件应记录在条目文件记录的里面。

ItemIndex.ets:

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 ItemIndex {
  @State amount:number=1//状态变量
  @State value:string=''//用户按键的内容记录在value内 amount最终结果
  @State showPanel:boolean=false//默认不展示

  onPanelShow(){
    this.amount=1//每次弹出,需要初始化为原始状态
    this.value=''//每次弹出,初始化为原始状态
    this.showPanel=true
  }

  build() {
    Column() {
      //1.头部导航
      this.Header()
      //2.列表
      ItemList({showPanel:this.onPanelShow.bind(this)})
      .layoutWeight(1)//除头部外 剩下都被列表占用 这样高度固定
     
    }
    .width('100%')
    .height('100%')
  }
  @Builder Header(){
    Row(){
        Image($r('app.media.ic_public_back'))
          .width(24)
          .onClick(() => router.back())
      Blank()
      Text('早餐').fontWeight(CommonConstants.FONT_WEIGHT_500)
    }
    .width(CommonConstants.THOUSANDTH_940)
    .height(32)
  }
}

ItemList.ets:

import { CommonConstants } from '../../common/constants/CommonConstants'
@Component
export default struct ItemList {
  build() {
    Tabs() {
      TabContent() {
        this.TabContentBuilder()
      }
      .tabBar('全部')
      TabContent() {
        this.TabContentBuilder()
      }
      .tabBar('主食')
      TabContent() {
        this.TabContentBuilder()
      }
      .tabBar('果蔬')
    }
    .width(CommonConstants.THOUSANDTH_940)
    .height('100%')
    .barMode(BarMode.Scrollable)

  }
  @Builder TabContentBuilder(){
    List({space:CommonConstants.SPACE_10}){
      ForEach([1,2,3,4,5],(item)=>{
        ListItem(){
          Row({space:CommonConstants.SPACE_6}){
            Image($r('app.media.toast'))
              .width(50)
            Column({space:CommonConstants.SPACE_4}){
              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)
        }
      })
    }
    .width('100%')
    .height('100%')
  }

}
效果:

编写时问题:

代码有很多重复的,想要抽出来,用Builder函数调用,但是抽出来 TabContent() 是不行的。

把TabContent()的内容抽出来可以,把里面的List封装到Builder中。

二、底部Panel

主要功能:

当点击列表的每一项时,会弹出食物详细信息,用户可以选择详细的数量。用户可以输入相应数量,然后保存添加。该部分主要实现日期,卡片样式。

代码:

ItemIndex.ets

import router from '@ohos.router'
import { CommonConstants } from '../common/constants/CommonConstants'
import ItemList from '../view/item/ItemList'
@Entry
@Component
struct ItemIndex {
  @State message: string = 'Hello World'
  @State showPanel: boolean = false

  onPanelShow(){
    this.showPanel=true
  }


  build() {
    Column(){
      //导航
      this.Header()
      //列表
      ItemList({showPanel:this.onPanelShow.bind(this)})
      //底部面板
      Panel(this.showPanel) {//点击加号是弹出
         // 3.1.顶部日期
         ItemPanelHeader()
         // 3.2.记录项卡片
         if(this.item){
          ItemCard({amount: this.amount, item: $item})
         }
       
        // 3.4.按钮
        this.PanelButton()
        Button('关闭')
          .onClick(()=>this.showPanel=false)
      }
      .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(24)
        .onClick(() => router.back())
      Blank()
      Text('早餐').fontSize(18).fontWeight(CommonConstants.FONT_WEIGHT_600)
    }
    .width(CommonConstants.THOUSANDTH_940)
    .height(32)
  }
}

 ItemList.ets


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%')
    .barMode(BarMode.Scrollable)

  }
  @Builder TabContentBuilder(){
    List({space:CommonConstants.SPACE_10}){
      ForEach([1,2,3,4,5],(item)=>{
        ListItem(){
          Row({space:CommonConstants.SPACE_6}){
            Image($r('app.media.toast'))
              .width(50)
            Column({space:CommonConstants.SPACE_4}){
              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%')
  }

}

ItemPanelHeader.ets

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)
    }
  }
}

ItemCard.ets 

import { CommonConstants } from '../../common/constants/CommonConstants'
@Component
export default struct ItemCard {
  @Prop amount: number//状态变量 Prop不能初始化

  build() {
    Column({space:CommonConstants.SPACE_8}){
      //1.图片
      Image($r('app.media.toast')).width(150)
      //2.名称
      Row(){
        Text('全麦吐司') .fontWeight(CommonConstants.FONT_WEIGHT_700)//为了添加颜色 将其放入Row容器中
      }
      .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)

      }
      Divider()//下划线
        .width(CommonConstants.THOUSANDTH_940)
        .opacity(0.6)//透明度
      //4.数量
      Row(){
        Column({space:CommonConstants.SPACE_4}){
          Text(this.amount.toFixed(1))//1位小数
            .fontSize(50).fontColor($r('app.color.primary_color'))
            .fontWeight(CommonConstants.FONT_WEIGHT_600)
          Divider().color($r('app.color.primary_color'))
        }
        .width(150)
        Text('片')
          .fontColor($r('app.color.primary_color'))
          .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)
    }
  }
}
效果:

编写时问题:

1.触发Panel的事件,当点击加号是弹出面板,在ItemList中ListItem加点击事件不对

原因ItemList无法控制父组件ItemIndex的弹出,showPanel用函数控制,函数声明出来,有父组件调用我们时去覆盖我们的声明。

2.不弹面板

面板不占高度的,浮在表面的, 但是他要求所在的容器高度和所包含的元素是固定的,ItemList是动态的,不固定,被挤到最下面去了 ItemList({showPanel:this.onPanelShow.bind(this)}) .layoutWeight(1)。

相关知识:

Panel

可滑动面板,提供一种轻量的内容展示窗口,方便在不同尺寸中

Panel(show: boolean)

参数名

参数类型

必填

参数描述

show

boolean

控制Panel显示或隐藏。

说明:

如果设置为false时,则不占位隐藏。Visible.None或者show之间有一个生效时,都会生效不占位隐藏。

属性

除支持通用属性外,还支持以下属性:

名称

参数类型

描述

type

PanelType

设置可滑动面板的类型。

默认值:PanelType.Foldable

mode

PanelMode

设置可滑动面板的初始状态。

Minibar类型默认值:PanelMode.Mini;其余类型默认值:PanelMode.Half

dragBar

boolean

设置是否存在dragbar,true表示存在,false表示不存在。

默认值:true

fullHeight

string | number

指定PanelMode.Full状态下的高度。

默认值:当前组件主轴大小减去8vp空白区

说明:

不支持设置百分比。

halfHeight

string | number

指定PanelMode.Half状态下的高度。

默认值:当前组件主轴大小的一半。

说明:

不支持设置百分比。

miniHeight

string | number

指定PanelMode.Mini状态下的高度。

默认值:48

单位:vp

说明:

不支持设置百分比。

show

boolean

当滑动面板弹出时调用。

backgroundMask9+

ResourceColor

指定Panel的背景蒙层。

PanelType枚举说明

名称

描述

Minibar

提供minibar和类全屏展示切换效果。

Foldable

内容永久展示类,提供大(类全屏)、中(类半屏)、小三种尺寸展示切换效果。

Temporary

内容临时展示区,提供大(类全屏)、中(类半屏)两种尺寸展示切换效果。

PanelMode枚举说明

名称

描述

Mini

类型为minibar和foldable时,为最小状态;类型为temporary,则不生效。

Half

类型为foldable和temporary时,为类半屏状态;类型为minibar,则不生效。

Full

类全屏状态。

事件

除支持通用事件外,还支持以下事件:

名称

功能描述

onChange(event: (width: number, height: number, mode: PanelMode) => void)

当可滑动面板发生状态变化时触发, 返回的height值为内容区高度值,当dragbar属性为true时,panel本身的高度值为dragbar高度加上内容区高度。

onHeightChange(callback: (value: number) => void)9+

当可滑动面板发生高度变化时触发,返回的height值为内容区高度值,默认返回值单位为px。当dragbar属性为true时,panel本身的高度值为dragbar高度加上内容区高度。因用户体验设计原因,panel最高只能滑到 fullHeight-8vp。

三、数字键盘

主要功能:

实现键盘的格式和输入以及按钮等功能,完成键盘键入,达到用户根据实际按钮实现变化。

代码:

ItemIndex.ets

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 ItemIndex {
  @State amount:number=1
  @State value:string=''//记录按键
  @State showPanel: boolean = false

  onPanelShow(){
    //在每次展开时要做一个初始化
    this.amount=1
    this.value=''
    this.showPanel=true
  }


  build() {
    Column(){
      //导航
      this.Header()
      //列表
      ItemList({showPanel:this.onPanelShow.bind(this)})
      .layoutWeight(1)
      //底部面板
      Panel(this.showPanel) {//点击加号是弹出
        // 3.1.顶部日期
        ItemPanelHeader()
        // 3.2.记录项卡片
        ItemCard({amount:this.amount})
         // 3.3.数字键盘
        NumberKeyboard({amount:$amount,value: $value})
        // // 3.4.按钮
        // this.PanelButton()

      }
      .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(24)
        .onClick(() => router.back())
      Blank()
      Text('早餐').fontSize(18).fontWeight(CommonConstants.FONT_WEIGHT_600)
    }
    .width(CommonConstants.THOUSANDTH_940)
    .height(32)
  }
}

NumderKeyboard.ets

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(){//每一个键都是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')//1:1:1
    .columnsGap(8)//列间距
    .rowsGap(8)//行间距
    .padding(8)
    .margin({top: 10})
  }
//点击数字
  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.将字符串转为数值
  //不能直接ParseFloat转,会报错,要单独定义一个函数
    let amount = this.parseFloat(val)
    // 4.保存
    if(amount >= 999.9){//值有上限
      this.amount = 999.0
      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)//去除,从0开始去,去到字符串最后一位减一,把小数点去除了
    }
    return parseFloat(str)
  }
}

键盘Button:默写成两个按钮都关闭

Row({space: CommonConstants.SPACE_6}){
  Button('取消')
    .width(120)
    .type(ButtonType.Normal)
    .borderRadius(6)
    .backgroundColor($r('app.color.light_gray'))
    .onClick(() => this.showPanel = false)
  Button('提交')
    .width(120)
    .type(ButtonType.Normal)
    .borderRadius(6)
    .backgroundColor($r('app.color.primary_color'))
    .onClick(() => this.showPanel = false)

}
.margin({top: 10})
效果:

编写时问题:

1.由于按键和数字可能对不起来:

用value记录,amount才是最终结果。

2.其碳水化合物之类的会随着片数增加二受影响

@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)
  }
}

相关知识:

Grid

网格容器,由“行”和“列”分割的单元格所组成,通过指定“项目”所在的单元格做出各种各样的布局。

Grid子组件的索引值计算规则:

  • 按子组件的顺序依次递增。
  • if/else语句中,只有条件成立分支内的子组件会参与索引值计算,条件不成立分支内的子组件不计算索引值。
  • ForEach/LazyForEach语句中,会计算展开所有子节点索引值。
  • if/else/ForEach/LazyForEach发生变化以后,会更新子节点索引值。
  • Grid子组件的visibility属性设置为Hidden或None时依然会计算索引值。
  • Grid子组件的visibility属性设置为None时不显示,但依然会占用子组件对应的网格。

接口

Grid(scroller?: Scroller)

参数名

参数类型

必填

参数描述

scroller

Scroller

可滚动组件的控制器。用于与可滚动组件进行绑定。

说明:

不允许和其他滚动类组件绑定同一个滚动控制对象。

属性

除支持通用属性外,还支持以下属性:

名称

参数类型

描述

columnsTemplate

string

设置当前网格布局列的数量,不设置时默认1列。

例如, '1fr 1fr 2fr' 是将父组件分3列,将父组件允许的宽分为4等份,第一列占1份,第二列占1份,第三列占2份。

说明:

设置为'0fr'时,该列的列宽为0,不显示GridItem。设置为其他非法值时,GridItem显示为固定1列。

rowsTemplate

string

设置当前网格布局行的数量,不设置时默认1行。

例如,'1fr 1fr 2fr'是将父组件分三行,将父组件允许的高分为4等份,第一行占1份,第二行占一份,第三行占2份。

说明:

设置为'0fr',则这一行的行宽为0,这一行GridItem不显示。设置为其他非法值,按固定1行处理。

columnsGap

Length

设置列与列的间距。

默认值:0

说明:

设置为小于0的值时,按默认值显示。

rowsGap

Length

设置行与行的间距。

默认值:0

说明:

设置为小于0的值时,按默认值显示。

scrollBar

BarState

设置滚动条状态。

默认值:BarState.Off

scrollBarColor

string | number | Color

设置滚动条的颜色。

scrollBarWidth

string | number

设置滚动条的宽度。宽度设置后,滚动条正常状态和按压状态宽度均为滚动条的宽度值。

默认值:4

单位:vp

cachedCount

number

设置预加载的GridItem的数量,只在LazyForEach中生效。具体使用可参考减少应用白块说明

默认值:1

说明:

设置缓存后会在Grid显示区域上下各缓存cachedCount*列数个GridItem。LazyForEach超出显示和缓存范围的GridItem会被释放。

设置为小于0的值时,按默认值显示。

editMode8+

boolean

设置Grid是否进入编辑模式,进入编辑模式可以拖拽Grid组件内部GridItem

默认值:false

layoutDirection8+

GridDirection

设置布局的主轴方向。

默认值:GridDirection.Row

maxCount8+

number

当layoutDirection是Row/RowReverse时,表示可显示的最大列数

当layoutDirection是Column/ColumnReverse时,表示可显示的最大行数。

默认值:Infinity

说明:

当maxCount小于minCount时,maxCount和minCount都按默认值处理。

设置为小于0的值时,按默认值显示。

minCount8+

number

当layoutDirection是Row/RowReverse时,表示可显示的最小列数。

当layoutDirection是Column/ColumnReverse时,表示可显示的最小行数。

默认值:1

说明:

设置为小于0的值时,按默认值显示。

cellLength8+

number

当layoutDirection是Row/RowReverse时,表示一行的高度。

当layoutDirection是Column/ColumnReverse时,表示一列的宽度。

默认值:第一个元素的大小

multiSelectable8+

boolean

是否开启鼠标框选。

默认值:false

- false:关闭框选。

- true:开启框选。

supportAnimation8+

boolean

是否支持动画。当前支持GridItem拖拽动画。

默认值:false

Grid组件根据rowsTemplate、columnsTemplate属性的设置情况,可分为以下三种布局模式:

  1. rowsTemplate、columnsTemplate同时设置:
    • Grid只展示固定行列数的元素,其余元素不展示,且Grid不可滚动。
    • 此模式下以下属性不生效:layoutDirection、maxCount、minCount、cellLength。
    • Grid的宽高没有设置时,默认适应父组件尺寸。
    • Gird网格列大小按照Gird自身内容区域大小减去所有行列Gap后按各个行列所占比重分配。
    • GridItem默认填满网格大小。
    • 此模式下GridItem同时设置了rowStart、columnStart,会用设置的rowStart、columnStart所在位置摆放GridItem。如果这个位置已经有GridItem则会发生重叠。
    • 如果GridItem设置了rowStart、columnStart其中一个,会从上一个GridItem布局位置开始遍历寻找满足rowStart或columnStart的空闲位置摆放,如果无满足条件的空闲位置,则不布局该GridItem。
    • 如果GridItem的rowStart、columnStart属性都没有设置,会从上一个GridItem布局位置开始遍历寻找空闲位置摆放,如果没有空闲位置,则不布局该GridItem。
    • 如果GridItem的rowEnd有设置,但是rowStart没有设置,当做rowStart已经设置,并且和rowEnd设置为相同值。如果GridItem的columnEnd有设置,但是columnStart没有设置,当做columnStart已经设置,并且和columnEnd设置为相同值。
  2. rowsTemplate、columnsTemplate仅设置其中的一个:
    • 元素按照设置的方向进行排布,超出Grid显示区域后,Grid可通过滚动的方式展示。
    • 如果设置了columnsTemplate,Gird滚动方向为垂直方向,主轴方向为垂直方向,交叉轴方向为水平方向。
    • 如果设置了rowsTemplate,Gird滚动方向为水平方向,主轴方向为水平方向,交叉轴方向为垂直方向。
    • 此模式下以下属性不生效:layoutDirection、maxCount、minCount、cellLength。
    • 网格交叉轴方向尺寸根据Gird自身内容区域交叉轴尺寸减去交叉轴方向所有Gap后按所占比重分配。
    • 网格主轴方向尺寸取当前网格交叉轴方向所有GridItem高度最大值。
    • 此模式下GridItem同时设置了rowStart、columnStart,会用设置的rowStart、columnStart所在位置摆放GridItem。如果这个位置已经有GridItem则会发生重叠。
    • 如果GridItem设置了rowStart、columnStart其中一个,会从上一个GridItem布局位置开始遍历寻找满足rowStart或columnStart的空闲位置摆放。
    • 如果GridItem的rowStart、columnStart属性都没有设置,会从上一个GridItem布局位置开始遍历寻找空闲位置摆放。
    • 如果GridItem的rowEnd有设置,但是rowStart没有设置,当做rowStart已经设置,并且和rowEnd设置为相同值。如果GridItem的columnEnd有设置,但是columnStart没有设置,当做columnStart已经设置,并且和columnEnd设置为相同值。
  3. rowsTemplate、columnsTemplate都不设置:
    • 元素在layoutDirection方向上排布,列数由Grid的宽度、首个元素的宽度、minCount、maxCount、columnsGap共同决定。
    • 行数由Grid高度、首个元素高度、cellLength、rowsGap共同决定。超出行列容纳范围的元素不显示,也不能通过滚动进行展示。
    • 此模式下仅生效以下属性:layoutDirection、maxCount、minCount、cellLength、editMode、columnsGap、rowsGap。
    • 当前layoutDirection设置为Row时,先从左到右排列,排满一行再排一下一列。剩余高度不足时不再布局,整体内容顶部居中。
    • 当前layoutDirection设置为Column时,先从上到下排列,排满一列再排一下一列,剩余宽度度不足时不再。整体内容顶部居中。
    • 此模式下GridItem的rowStart、columnStart不生效。

总结:

一、食物列表

首先,定义了一个具有头部导航和列表的组件,列表可以通过点击头部的返回图标或某种方式触发显示一个面板。面板显示时,会重置数量和输入内容的状态。

其次,使用 Tabs 组件创建标签页布局,TabContent 用于展示所有项目列表。使用 ForEach 遍历分组信息,并为每个分类创建一个 TabContent 和 tabBar。对于每个项目,使用 ListItem、Row、Image、Column 和 Text 组件创建列表项布局。为每个列表项添加 onClick 事件处理器,当点击时调用 showPanel 方法,并传递被点击的项目项。

二、底部Panel

在前面的基础上,ItemIndex 导入了路由模块、通用常量和自定义组件。定义了一个名为ItemIndex的结构体,它是一个组件,具有两个状态变量message和showPanel。onPanelShow方法用于设置showPanel为true,表示面板应该显示。build方法构建了组件的UI布局,包括导航、列表和面板。面板使用了Panel组件,并且可以自定义面板的样式和行为。Header 定义了组件的头部布局,包括返回按钮和标题文本。ItemList 组件定义了ItemList组件,它接收一个showPanel回调函数作为属性。build方法构建了一个带标签页的列表界面,每个标签页都调用TabContentBuilder方法来生成内容。TabContentBuilder方法使用ForEach遍历一个数组,并为每个项生成一个列表项,列表项中包含图片、文本和添加按钮。点击列表项时,会调用showPanel回调函数。ItemPanelHeader 定义了ItemPanelHeader组件,用于显示日期和可能的下拉箭头图标。ItemCard 组件定义了ItemCard组件,它接收一个amount属性。build方法构建了一个卡片布局,显示了图片、名称、营养素信息和数量。NutrientInfo是一个辅助构建器,用于显示营养素的标签和值。

三、数字键盘

在前面的基础上,Grid 组件创建网格布局,用于展示数字键盘的按钮。用 ForEach 遍历 numbers 数组,并为每个数字创建 GridItem 和 Text 组件作为按钮。clickDelete 方法处理删除按钮的点击事件,从当前值中移除最后一个字符,并更新数值。parseFloat 方法用于将字符串转换为浮点数,去除字符串末尾的小数点。在 clickNumber 中,检查输入的字符串是否符合预期的数值格式,确保不会输入超过两个小数点的数字。

  • 24
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值