黑马健康App

五.食物列表页

(一)列表页UI设计

1.功能分析

当用户点击饮食记录里的早、午、晚餐的分组时就会跳转到食物列表页,从中添加食物,点击相应食物时会弹出相应面板记录数据。


2.布局分析

3.代码实现

(1)头部导航
@Entry
@Component
struct ItemIndex {
  build() {
    Column() {
      // 1.头部导航
      this.Header()
    }
    .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)
  }
}

previewer:

(2)列表

每个tab项都类似所以可以将其中的list封装起来调用

/*ItemList*/
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%')
  }

  @Builder TabContentBuilder(items: RecordItem[]) {
    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({ space: CommonConstants.SPACE_4 }) {
              Text('全麦吐司').fontWeight(CommonConstants.FONT_WEIGHT_500)
              Text('91千卡/片').fontSize(14).fontColor($r('app.color.light_gray'))
            }.alignItems(HorizontalAlign.Start)

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

/*ItemIndex*/
import ItemList from '../view/item/ItemList'
@Entry
@Component
struct ItemIndex {
  build() {
    Column() {
      // 1.头部导航
      this.Header()
      // 2.头部导航
      ItemList()
    }
    .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)
  }
}

previewer:

(二)底部Panel

1.布局分析

自定义生成键盘需要用到Grid组件
底部面板需要用到Panel组件


2.代码实现

(1)面板简单实现

问题:点击时没有出现底部面板
原因及解决方法:面板不占高度,是浮在表面的,它要求它所在的容器的高度与容器里面元素的高度是固定的,但这里面list的高度不固定(动态的),导致底部面板被挤到对下面去了(无法展示出来)。用.layoutWeight(1)去固定list的高度即可解决。

/*ItemIndex*/
import { CommonConstants } from '../common/constants/CommonConstants'
import ItemList from '../view/item/ItemList'
/**
 * 条目
 */
@Entry
@Component
struct ItemIndex {
  @State showPanel: boolean = false

  build() {
    Column() {
      // 1.头部导航
      this.Header()
      // 2.列表
      ItemList({ showPanel: this.onPanelShow.bind(this)})
        .layoutWeight(1)
      // 3.底部面板
      Panel(this.showPanel) {
          Button('关闭').onClick(()=>this.showPanel=false)
              }
      .mode(PanelMode.Full)//默认完全展出面板
      .dragBar(false)//设置可不可以上下拖动顶部边框改变面板大小(这里不可以)
      .backgroundMask($r('app.color.light_gray'))//蒙板颜色
      .backgroundColor(Color.White)
    }
    .width('100%')
    .height('100%')
  }
}

/*ItemList*/
/**
 * 条目列表——食物列表
 */
@Component
export default struct ItemList {
  showPanel: (item: RecordItem) => void
  @Builder TabContentBuilder(items: RecordItem[]) {
    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({ space: CommonConstants.SPACE_4 }) {
              Text('全麦吐司').fontWeight(CommonConstants.FONT_WEIGHT_500)
              Text('91千卡/片').fontSize(14).fontColor($r('app.color.light_gray'))
            }.alignItems(HorizontalAlign.Start)

            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(item))
      })
    }
    .width('100%')
    .height('100%')
  }
}

previewer:

(2)顶部日期
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)
    }
  }
}

previewer:

(3)记录项卡片
/*ItemCard*/
import { CommonConstants } from '../../common/constants/CommonConstants'
import RecordItem from '../../viewmodel/RecordItem'
/**
 * 记录项卡片
 */
@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('全麦吐司').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)//下划线,opacity调整透明度
      // 3.营养素
      Row({space: CommonConstants.SPACE_8}){
        this.NutrientInfo( '热量(千卡)',  91.0)
        if(this.item.id < 10000){
          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))
            .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.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.toFixed(1)).fontSize(18).fontWeight(CommonConstants.FONT_WEIGHT_700)//这里toFixed(1)的作用是将value转成一位小数
    }
  }
}

/*ItemIndex*/
@Entry
@Component
struct ItemIndex {
  @State showPanel: boolean = false
  @State amount: number = 1

  build() {
    Column() {
      // 1.头部导航
      this.Header()
      // 2.列表
      ItemList({ showPanel: this.onPanelShow.bind(this)})
        .layoutWeight(1)
      // 3.底部面板
      Panel(this.showPanel) {
        // 3.1.顶部日期
        ItemPanelHeader()
        // 3.2.记录项卡片
        if(this.item){
          ItemCard({amount: this.amount})
        }
      }
      .mode(PanelMode.Full)//默认完全展出面板
      .dragBar(false)//设置可不可以上下拖动顶部边框改变面板大小(这里不可以)
      .backgroundMask($r('app.color.light_gray'))//蒙板颜色
      .backgroundColor(Color.White)
    }
    .width('100%')
    .height('100%')
  }
}

previewer:


 

(4)数字键盘

需要用到组件Grid,是一种网格容器。

简单实现
 
/*NumberKeyboard*/
import { CommonConstants } from '../../common/constants/CommonConstants'
/**
 * 数字键盘
 */
@Component
export default struct NumberKeyboard {
  numbers: string[] = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '.']

  @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()
            })
      GridItem(){
        Text('删除').fontSize(20).fontWeight(CommonConstants.FONT_WEIGHT_900)
      }
      .keyBoxStyle()
         }
    .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})//外边距
  }
}

/*ItemIndex*/
@Entry
@Component
struct ItemIndex {
  @State showPanel: boolean = false
  @State amount: number = 1

  build() {
    Column() {
      // 1.头部导航
      this.Header()
      // 2.列表
      ItemList({ showPanel: this.onPanelShow.bind(this)})
        .layoutWeight(1)
      // 3.底部面板
      Panel(this.showPanel) {
        // 3.1.顶部日期
        ItemPanelHeader()
        // 3.2.记录项卡片
        if(this.item){
          ItemCard({amount: this.amount})
        }
        // 3.3.数字键盘
        NumberKeyboard()
      }
      .mode(PanelMode.Full)//默认完全展出面板
      .dragBar(false)//设置可不可以上下拖动顶部边框改变面板大小(这里不可以)
      .backgroundMask($r('app.color.light_gray'))//蒙板颜色
      .backgroundColor(Color.White)
    }
    .width('100%')
    .height('100%')
  }
}

previewer:

传值:
/*ItemIndex*/
@Entry
@Component
struct ItemIndex {
  @State amount: number = 1//键盘输出内容
  @State value: string = ''//用户键盘输入内容
  @State showPanel: boolean = false

  onPanelShow(item: RecordItem) {
    this.amount = 1
    this.value = ''
    this.item = item
    this.showPanel = true
  }

  build() {
    Column() {
      // 1.头部导航
      this.Header()
      // 2.列表
      ItemList({ showPanel: this.onPanelShow.bind(this)})
        .layoutWeight(1)
      // 3.底部面板
      Panel(this.showPanel) {
        // 3.1.顶部日期
        ItemPanelHeader()
        // 3.2.记录项卡片
        if(this.item){
          ItemCard({amount: this.amount})
        }
        // 3.3.数字键盘
        NumberKeyboard({amount: $amount, value: $value})
      }
      .mode(PanelMode.Full)//默认完全展出面板
      .dragBar(false)//设置可不可以上下拖动顶部边框改变面板大小(这里不可以)
      .backgroundMask($r('app.color.light_gray'))//蒙板颜色
      .backgroundColor(Color.White)
    }
    .width('100%')
    .height('100%')
  }
}


/*NumberKeyboard*/
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')//网格布局(三列宽度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('.')//返回最后一次出现'.'的角标
    //如果出现多于一次小数点 或者 在有小数点的情况下等于或超过了2位小数 就算非法输入
    if(firstIndex !== lastIndex || (lastIndex != -1 && lastIndex < val.length - 2)){
      // 非法输入
      return
    }
    // 3.将字符串转为数值
    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//避免出现NaN
    }
    //如果字符串最后一位是小数点就将它去除
    if(str.endsWith('.')){
      str = str.substring(0, str.length - 1)
    }
    return parseFloat(str)
  }
}

/*ItemCard*/
  //营养素信息
  @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)//这里toFixed(1)的作用是将value转成一位小数
    }
  }

previewer:

(4)按钮(取消,提交)

提交按钮暂不做处理

/*ItemIndex*/
// 3.4.按钮
    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})

previewer:


 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值