黑马健康应用(食物列表页的完成)

食物列表页是在之前的实物记录页面的基础上实现的一个路由页面,就是在食物记录页面点击某个食物便可跳转到该食物的列表页。

首先食物列表页分为三个部分包括:一.头部导航 、二..食物列表 、三.底部面板,整个页面则是一个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)
  }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值