鸿蒙项目—黑马健康应用(3)

目录

一、食物列表

1、食物列表页面结构:

食物列表逻辑结构

运行效果:

代码如下:

2、底部Panel

Panel结构

​编辑

关键技术:

Panel组件

运行效果:

代码如下:

3、数字键盘

关键技术:Grid组件

运行效果:

代码如下:

二、遇到问题

三、总结


一、食物列表

1、食物列表页面结构:

食物列表逻辑结构

标题结构

图片是一个返回箭头,点击时回退上个页面。Text定义选择记录项目是早中晚餐。

标题实现代码

@Builder Header() {
    Row() {
      Image($r('app.media.ic_public_back'))
        .width(24)
        .onClick(() => router.back())
      Blank()
      Text(this.type.name).fontSize(18).fontWeight(CommonConstants.FONT_WEIGHT_600)
    }
    .width(CommonConstants.THOUSANDTH_940)
    .height(32)
  }

运行效果:

代码如下:

//ItemIndex.ets
import { CommonConstants } from '../common/constants/CommonConstants'
import ItemList from '../view/item/ItemList'
import router from '@ohos.router'

@Extend(Button) function panelButtonStyle(){
  .width(120)
  .type(ButtonType.Normal)
  .borderRadius(6)
}

@Entry
@Component
struct ItemIndex {


  build() {
    Column() {
      // 1.头部导航
      this.Header()
      // 2.列表
      ItemList()
        .layoutWeight(1)
      // 3.底部面板

    }
    .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 {


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

2、底部Panel

Panel结构

关键技术:

Panel组件

底部弹出面板,列式布局,在食物列表页设置点击事件,当点击时弹出来

  • onPanelShow 方法用于控制面板的显示状态,通过修改 showPanel 状态来实现面板的展开和隐藏。
  • .mode(PanelMode.Full) 指定了面板的模式为全屏模式
    自定义面板行为:
  • .dragBar(false) 禁用了面板的拖动条,.backgroundMask 和 .backgroundColor 分别设置了面板的遮罩颜色和背景颜色。
    布局权重
  • .layoutWeight(1) 确保 ItemList 组件填满可用的垂直空间。

运行效果:

代码如下:


import { CommonConstants } from '../common/constants/CommonConstants'
import ItemList from '../view/item/ItemList'
import router from '@ohos.router'
import ItemPanelHeader from '../view/item/ItemPanelHeader'
import ItemCard from '../view/item/ItemCard'


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

  onPanelShow() {
    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.记录项卡片
        ItemCard({amount: this.amount})

        // 3.3.数字键盘

        // 3.4.按钮

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


//ItemCard.ets
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('全麦吐司').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)
      }
      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)
    }
  }
}

3、数字键盘

关键技术:Grid组件

  • columnsTemplate('1fr 1fr 1fr') 定义了网格的列宽,使用 fr 单位来表示相对宽度,使得每列宽度自适应。
  • columnsGap(8) 和 rowsGap(8) 设置了列与列之间、行与行之间的间距。
  • GridItem() 用于定义网格中的单个项,可以包含文本、按钮或其他组件。
  • .onClick(() => this.clickNumber(num)) 绑定了点击事件,当用户点击某个数字或操作按钮时触发。
  • ForEach(this.numbers, num => {...}) 用于遍历数字数组,并为每个数字创建一个网格项。
  • 使用 @Link 装饰器将 NumberKeyboard 组件的状态与父组件的状态链接起来,实现双向数据绑定。
  • 在 clickNumber 方法中,实现了对用户输入的验证逻辑,确保输入的数字格式正确,并且不超过最大值。

运行效果:

代码如下:

        // 3.3.数字键盘
        NumberKeyboard({amount:$amount,value:$value})
        // 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})
      }
      


//NumberKeyboard.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(){
          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: 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.将字符串转为数值
    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)
    }
    return parseFloat(str)
  }
}

二、遇到问题

        问题1:弹出面板未正确弹出

        解决方法:面板并不占用高度,浮在表面,,要求它所在的容器组件高度固定,需在列表中添加权重(1)

        问题2:如何实现数字输入与页面元素改变的联动

        解决方法:①定义状态变量来存储当前的输入值。在 NumberKeyboard 组件中,使用 @Link 装饰器将 amount 和 value 与父组件的状态进行双向绑定。②在 NumberKeyboard 组件中,为每个按键和删除操作提供事件处理函数,如 clickNumber 和 clickDelete。③当用户点击数字或删除按钮时,更新 value 字符串,并重新计算 amount 数值。使用 parseFloat 函数来确保数值格式正确。④确保 NumberKeyboard 组件的 amount 和 value 属性与父组件中的状态是绑定的,这样任何更改都会同步到父组件。

三、总结

        通过本次开发,我们深入理解了食物列表页面的设计和实现,掌握了关键技术的应用,如列表组件、Panel组件和Grid组件。Panel组件提供了一个可展开和收起的底部面板,用于展示额外的操作或信息。在食物列表页面中,Panel组件可用于编辑、删除食物项或进行其他高级操作。Grid组件实现了数字键盘的网格布局,允许用户输入数量。通过Grid组件,我们创建了一个直观且响应式的用户界面,便于用户进行数字输入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值