黑马健康4(食物列表页)

界面实例:

代码

食物列表页:

ItemIndex.ets

import router from '@ohos.router'
import { CommonConstants } from '../common/constants/CommonConstants'
import { RecordTypeEnum, RecordTypes } from '../model/RecordTypeModel'
import RecordService from '../RecordService/RecordService'
import ItemCard from '../view/item/ItemCard'
import ItemList from '../view/item/ItemList'
import ItemPanelHeader from '../view/item/ItemPanelHeader'
import NumberKeyboard from '../view/item/NumberKeyboard'
import RecordItem from '../viewmodel/RecordItem'
import RecordType from '../viewmodel/RecordType'

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

@Entry
@Component
struct ItemIndex {
  @State amount: number = 1
  @State value: string = ''
  @State showPanel: boolean = false
  @State item: RecordItem = null
  @State type: RecordType = RecordTypes[0]
  @State isFood: boolean = true

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

  onPageShow(){
    // 1.获取跳转时的参数
    let params: any = router.getParams()
    // 2.获取点击的饮食记录类型
    this.type = params.type
    this.isFood = this.type.id !== RecordTypeEnum.WORKOUT
  }

  build() {
    Column() {
      // 1.头部导航
      this.Header()
      // 2.列表
      ItemList({ showPanel: this.onPanelShow.bind(this), isFood: this.isFood })
        .layoutWeight(1)
      // 3.底部面板
      Panel(this.showPanel) {
        // 3.1.顶部日期
        ItemPanelHeader()
        // 3.2.记录项卡片
        if(this.item){
          ItemCard({amount: this.amount, item: $item})
        }
        // 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 PanelButton(){
    Row({space: CommonConstants.SPACE_6}){
      Button('取消')
        .panelButtonStyle()
        .backgroundColor($r('app.color.light_gray'))
        .onClick(() => this.showPanel = false)
      Button('提交')
        .panelButtonStyle()
        .backgroundColor($r('app.color.primary_color'))
        .onClick(() => {
          // 1.持久化保存
          RecordService.insert(this.type.id, this.item.id, this.amount)
            .then(() => {
              // 2.关闭弹窗
              this.showPanel = false
            })
        })
    }
    .margin({top: 10})
  }

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

食物列表页包括头部导航,列表,底部面板三部分,底部面板又包括顶部日期,记录项卡片,数字键盘,按钮四个部分,底部面板的出现是一个弹窗,点击规定的地方才会出现应该出现的弹窗。

列表项:

ItemList.ets

import { CommonConstants } from '../../common/constants/CommonConstants'
import RecordItem from '../../viewmodel/RecordItem'
import ItemModel from '../../model/ItemModel'
import ItemCategory from '../../viewmodel/ItemCategory'
import GroupInfo from '../../viewmodel/GroupInfo'
@Component
export default struct ItemList {

  showPanel:(item:RecordItem)=>void
@Prop isFood:boolean

  build() {
    Tabs()//是个容器
    {
      TabContent() {
        this.TabContentBuilder(ItemModel.list(this.isFood))//从里面查
      }
      .tabBar('全部')

      ForEach(
        ItemModel.listItemGroupByCategory(this.isFood),
        (group: GroupInfo<ItemCategory, RecordItem>) => {
          TabContent() {
            this.TabContentBuilder(group.items)//group里面已经有数据了不用再查了
          }
          .tabBar(group.type.name)
        })



    }
    .width(CommonConstants.THOUSANDTH_940)
    .height('100%')
    .barMode(BarMode.Scrollable)//Scrollable Tabbar的枚举布局 占多少位置就是多少位置 超出位置可以进行滚动
  }
  //封装
  @Builder TabContentBuilder(items:RecordItem[]){
    List({space:CommonConstants.SPACE_10})
    {
      ForEach(items,(item:RecordItem)=>{
        ListItem(){
          Row({space:CommonConstants.SPACE_6}){
            Image(item.image).width(50)
            Column({space:CommonConstants.SPACE_4})
            {
              Text(item.name).fontWeight(CommonConstants.FONT_WEIGHT_500)
              Text(`${item.calorie}千卡/${item.unit}`).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(item))//itemCard的父组件 item先传给它 再去传给ItemCard
      })
    }
    .width('100%')
    .height('100%')
  }
}

panel面板:

ItemPanelHeader.ets

import { CommonConstants } from '../../common/constants/CommonConstants'
@Component
 export default struct ItemPanelHeader {
  build() {
    Row() {
      Text('1月17日 早餐').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'
import RecordItem from '../../viewmodel/RecordItem'
@Component
export default struct ItemCard {
  //状态变量,不能初始化 渲染组件 显示
  @Prop amount:number
  @Link item:RecordItem
  build() {
    Column({space: CommonConstants.SPACE_8}){
      // 1.图片
      Image(this.item.image).width(150)
      // 2.名称
      Row(){
        Text(this.item.name).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({label: '热量(千卡)', value: this.item.calorie})
        if(this.item.id < 10000){
          this.NutrientInfo({label: '碳水(千卡)', value: this.item.carbon})
          this.NutrientInfo({label: '蛋白质(千卡)', value: this.item.protein})
          this.NutrientInfo({label: '脂肪(千卡)', value: this.item.fat})
        }
      }
      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(this.item.unit)
          .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)
    }
  }
}

数字键盘:

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')//3列 1:1:1 行的话自己根据空间进行排列了
    .columnsGap(8)
    .rowsGap(8)
    .padding(8)
    .margin({top:16})
  }
  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.Panel:可滑动面板,提供一种轻量的内容展示窗口,方便在不同尺寸中

2.在列表项中使用Divider添加分隔线,增强视觉分隔效果。在NumberKeyboard中,使用不同的方法处理数字输入和删除操作,提供用户输入反馈。

3.在ItemList组件中,根据分类对项目进行分组,并使用Tabs组件展示不同分类的标签页。

4.使用ListGrid组件来渲染列表和网格布局。使用ForEach方法遍历数据集合,为每项数据创建对应的UI元素。

5.使用@Component@Prop注解定义组件和属性,实现组件化开发。

多段部署

要实现的效果:

代码:

BreakpointConstants.ets:

import BreakpointType from '../bean/BreanpointType';
export default class BreakpointConstants {
  /**
   * 小屏幕设备的 Breakpoints 标记.
   */
  static readonly BREAKPOINT_SM: string = 'sm';
 
  /**
   * 中等屏幕设备的 Breakpoints 标记.
   */
  static readonly BREAKPOINT_MD: string = 'md';
 
  /**
   * 大屏幕设备的 Breakpoints 标记.
   */
  static readonly BREAKPOINT_LG: string = 'lg';
 
  /**
   * 当前设备的 breakpoints 存储key
   */
  static readonly CURRENT_BREAKPOINT: string = 'currentBreakpoint';
 
  /**
   * 小屏幕设备宽度范围.
   */
  static readonly RANGE_SM: string = '(320vp<=width<600vp)';
 
  /**
   * 中屏幕设备宽度范围.
   */
  static readonly RANGE_MD: string = '(600vp<=width<840vp)';
 
  /**
   * 大屏幕设备宽度范围.
   */
  static readonly RANGE_LG: string = '(840vp<=width)';
//用的比较多,定义为常量
  static readonly BAR_POSITION: BreakpointType<BarPosition> = new BreakpointType({
    sm: BarPosition.End,
    md: BarPosition.Start,
    lg: BarPosition.Start,
  })
}

BreakpointSystem.ets:

import mediaQuery from '@ohos.mediaquery'
import BreakpointConstants from '../constants/BreakpointConstants'
 
export default class BreakpointSystem{
//定义监听器
  //ets才能导入
  private smListener: mediaQuery.MediaQueryListener = mediaQuery.matchMediaSync(BreakpointConstants.RANGE_SM)
  private mdListener: mediaQuery.MediaQueryListener = mediaQuery.matchMediaSync(BreakpointConstants.RANGE_MD)
  private lgListener: mediaQuery.MediaQueryListener = mediaQuery.matchMediaSync(BreakpointConstants.RANGE_LG)
//监听器的回调函数
  smListenerCallback(result: mediaQuery.MediaQueryResult){
    if(result.matches){
      //匹配到了就存储
      this.updateCurrentBreakpoint(BreakpointConstants.BREAKPOINT_SM)
    }
  }
 
  mdListenerCallback(result: mediaQuery.MediaQueryResult){
    if(result.matches){
      this.updateCurrentBreakpoint(BreakpointConstants.BREAKPOINT_MD)
    }
  }
 
  lgListenerCallback(result: mediaQuery.MediaQueryResult){
    if(result.matches){
      this.updateCurrentBreakpoint(BreakpointConstants.BREAKPOINT_LG)
    }
  }
//存储到全局变量的函数
  updateCurrentBreakpoint(breakpoint: string){
    AppStorage.SetOrCreate(BreakpointConstants.CURRENT_BREAKPOINT, breakpoint)
  }
//注册回调函数
  register(){
    this.smListener.on('change', this.smListenerCallback.bind(this))
    this.mdListener.on('change', this.mdListenerCallback.bind(this))
    this.lgListener.on('change', this.lgListenerCallback.bind(this))
  }
//在系统退出时,取消回调
  unregister(){
    this.smListener.off('change', this.smListenerCallback.bind(this))
    this.mdListener.off('change', this.mdListenerCallback.bind(this))
    this.lgListener.off('change', this.lgListenerCallback.bind(this))
  }
}

Index:


  private breakpointSystem:BreakpointSystem=new BreakpointSystem()
  //storageProp必须初始化
  @StorageProp('currentBreakpoint') currentBreakpoint: string=BreakpointConstants.BREAKPOINT_SM

aboutToAppear(){
    //多段部署
    this.breakpointSystem.register()//完成回调函数注册
  }
 
  aboutToDisappear(){
    this.breakpointSystem.unregister()
  }


declare interface BreakpointTypeOptions<T>{
  //泛型
  sm?:T,
  md?:T,
  lg?:T
}

export default class BreakpointType<T>{
  options: BreakpointTypeOptions<T>//对象
 
//构造函数
  constructor(options: BreakpointTypeOptions<T>) {
    
    this.options = options
  }
 
  getValue(breakpoint: string): T{
    return this.options[breakpoint]
  }
}

Tabs:

.vertical(new BreakpointType({
      sm:false,
      md:true,
      lg:true
    }).getValue(this.currentBreakpoint))//布局模式

swiper:

.displayCount(new BreakpointType({
        sm: 1,
        md: 1,
        lg: 2
      }).getValue(this.currentBreakpoint))

运行截图:

多段部署总结:

实现了用户多种设备的选择,实现了响应式布局,我们可以将这个程序应用到折叠手机,平板上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值