鸿蒙期末个人项目“黑马健康”——项目阶段四:食物列表页

目录

项目介绍:

页面布局:

分析:

                食物列表页UI设计:

                食物列表-底部Panel:

                食物列表-数字键盘:

步骤:

页面实现效果:

出现的问题与解决:

问题1:

问题2:

问题3:

问题4:

阶段项目代码:

        食物列表页代码:

  食物列表-底部Panel:

  食物列表源码-键盘:


项目介绍:

 黑马健康软件是一款基于全民健康的软件,主要有三个页面组成,分别是欢迎页面,统计记录页面,食物列表页面。

页面布局:

分析:

                食物列表页UI设计:
  •                 列式结构,第一行为导航条,左侧为返回按钮,右侧是提示的标签文本;剩下的部分就是一个Tabs组件
                食物列表-底部Panel:
  •                 列式布局,第一行是Text文本和Image组件,第二行Image组件,第三行是一个Text组件,第四行是食物的详细信息,用Text组件构成,第五行是食物的数量(左侧是列式布局,首行是一个Text文本组件,第二行是一个Divder,右侧是一个Text文本);第六行是键盘,Grid组件;最后一行是两个按钮;
                食物列表-数字键盘:
  •                 Grid布局加上最下面的一行按钮,Grid布局用来加载数字键盘。

                

步骤:

  •         在pages文件夹下新建ItemIndex.ets文件,用来渲染食物列表页面;
  •         在view文件夹下面新建item文件夹,在其中新ItemList.ets文件,用来渲染列表展示的列表项;新建ItemCard.ets文件,用来渲染Panel的表项;新建ItemPanelHeader.ets用来渲染Panel的首部;
  •         在item文件夹下新建NumberKeyBoard.ets文件,用来渲染数字键盘

页面实现效果:

食物列表的效果展示

键盘的效果展示

上面营养素的信息也随着数量的变化而变化

出现的问题与解决:

问题1:

当添加多个tabBar时,就会出现下图的这种情况,长度长的文字会被呈现省略号的效果,查阅TabsAPI文档,发现有一个BarMode属性,其中默认的是Fixed模式

Fixed

所有TabBar平均分配barWidth宽度(纵向时平均分配barHeight高度)。

还有一个模式是Scrollable模式,

Scrollable

每一个TabBar均使用实际布局宽度,超过总长度(横向Tabs的barWidth,纵向Tabs的barHeight)后可滑动。

当选用Scrollable模式,文字内容就会显示全:

问题2:

出现问题:当点击列表的一行时,不显示弹窗

改进:因为面板虽然不占用高度,浮在表面上,但是要求容器所在的高度以及内部元素的高度是固定的,所以需要固定上部分组件ItemList()的高度,用.layoutWeight(1)

问题3:

Link修饰的变量在传递参数的时候不能用this,应该使用$amount

问题4:

这里显示的@Link装饰的value变量必须被初始化,经过检查已经初始化,并且在这个问题函数内部也没有赋值

问题出现的原因:直接忘记传值

阶段项目代码:

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 value: string = ''//记录按下的键的内容
  @State amount: number  = 1 //需要和其他组件互动
  @State showPanel:boolean = false
  onPanelShow(){
    this.showPanel = true
  }
  build() {
   Column(){
     // 1.头部导航栏
       this.Header()
     // 2.TODO 列表
       ItemList({showPanel:this.onPanelShow.bind(this)})
        .layoutWeight(1)
     // 3.TODO 底部面板
      Panel(this.showPanel) {
        // 3.1顶部日期
            ItemPanelHeader()
        // 3.2记录项信息卡片
           ItemCard({amount:this.amount})
        // 3.3数字键盘
           NumberKeyBoard({amount:$amount,value:$value})//link传递值时使用$,不用this
        // 3.4按钮
        Row({space:6}){
          Button('取消')
            .width(120)
            .backgroundColor($r('app.color.light_gray'))
            .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(25)
        .onClick(()=>{
          router.back()
        })
      Blank()
      Text('早餐').fontSize(18).fontWeight(CommonConstants.FONT_WEIGHT_700)
    }.width('94%')
    .height(36)
  }
}
        食物列表页代码:
import { CommonConstants } from '../../common/constants/CommonConstants'
/**
 * 列表展示Tabs
 */
@Extend(Text) function grayText(){
  .fontSize(14)
  .fontColor($r('app.color.light_gray'))
}
@Component
export default struct ItemList {

  build() {

    Tabs(){
      TabContent(){
          this.TabContentBuilder()
      }.tabBar('全部')
      TabContent(){
        this.TabContentBuilder()
      }.tabBar('主食')
      TabContent(){
        this.TabContentBuilder()
      }.tabBar('果蔬')
      TabContent(){
        this.TabContentBuilder()
      }.tabBar('肉蛋奶')
      TabContent(){
        this.TabContentBuilder()
      }.tabBar('坚果')
      TabContent(){
        this.TabContentBuilder()
      }.tabBar('其它')

    }
    .barMode(BarMode.Scrollable)
    .barWidth(700)
    .width('100%')
    .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('90千卡/片').grayText()
            }
            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%')
  }
}
import router from '@ohos.router'
import { CommonConstants } from '../common/constants/CommonConstants'
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(25)
        .onClick(()=>{
          router.back()
        })
      Blank()
      Text('早餐').fontSize(18).fontWeight(CommonConstants.FONT_WEIGHT_700)
    }.width('94%')
    .height(36)
  }
}
  食物列表-底部Panel:
import { CommonConstants } from '../../common/constants/CommonConstants'
/**
 * 列表展示Tabs
 * 注意这里控制父组件的状态改变的话,需要用一个函数就覆盖
 */
@Extend(Text) function grayText(){
  .fontSize(14)
  .fontColor($r('app.color.light_gray'))
}
@Component
export default struct ItemList {

  showPanel:() => void

  build() {

    Tabs(){
      TabContent(){
          this.TabContentBuilder()
      }.tabBar('全部')
      TabContent(){
        this.TabContentBuilder()
      }.tabBar('主食')
      TabContent(){
        this.TabContentBuilder()
      }.tabBar('果蔬')
      TabContent(){
        this.TabContentBuilder()
      }.tabBar('肉蛋奶')
      TabContent(){
        this.TabContentBuilder()
      }.tabBar('坚果')
      TabContent(){
        this.TabContentBuilder()
      }.tabBar('其它')

    }
    .barMode(BarMode.Scrollable)
    .barWidth(700)
    .width('100%')
    .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('90千卡/片').grayText()
            }
            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%')
  }
}
import { CommonConstants } from '../../common/constants/CommonConstants'
import DateUtil from '../../common/utils/DateUtil'
import DatePickDialog from '../record/DatePickDialog'
/**
 *
 * Panel的顶部样式
 */
@Component
 export default struct ItemPanelHeader {
  @StorageProp('selectedDate') selectedDate:number = DateUtil.beginTimeOfDay(new Date())

  controller:CustomDialogController = new CustomDialogController({
    builder:DatePickDialog({selectedDate:new Date(this.selectedDate)})
  })

  build() {
    Row(){
      Text(DateUtil.formatDate(this.selectedDate))
          .fontWeight(CommonConstants.FONT_WEIGHT_600)
          .fontSize(18)
          .margin({left: 80,right:13})
          .onClick(()=>{
            this.controller.open()
          })

      Text('早餐')
        Image($r('app.media.ic_public_spinner'))
          .width(20)
          .fillColor(Color.Black)

    }
    .width(CommonConstants.THOUSANDTH_940)
  }
}
import { CommonConstants } from '../../common/constants/CommonConstants'
/**
 * Panel信息部分
 */
@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:10,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('蛋白质(克)',10.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)
    }
  }
}
  食物列表源码-键盘:
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)
    .height(60)
    .borderRadius(8)
  }
  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('.')//从后往前查找小数点的下标
    if(firstIndex !== lastIndex || (lastIndex != -1&&lastIndex<val.length-2) ){
      //需要排除掉的情况:2.1小数点不止有一位,2.2小数点的位数超过2位
      return
    }
    // 3.将输入字符串转为数值
    let amount = this.parseFloat(val)
    // 4.保存
    if(amount>=999.9){
      this.amount = 999
      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)
  }
}

参考黑马课堂老师的讲解,欢迎大家的批评和指正。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值