黑马健康-首页

注:本人在创作过程中的疑难杂点,全以TODO Z-形式在代码中标注。设计所有页面的TODO会在结尾的注意事项中展示

一,Index-首页整体UI设计

1.1Tabs组件介绍

TabBar就是固定导航栏的部分

TabContent是上面需要展示的内容

1.2代码框架及其实现结果



@Entry
@Component
struct Index {
  build() {
    Tabs(){
      //1.饮食记录
      TabContent(){
        Text('饮食记录页面')
      }
      .tabBar($r('app.string.tab_record'))
      //2.发现页面
      TabContent(){
        Text('发现页面')
      }
      .tabBar($r('app.string.tab_discover'))
      //3.我的主页
      TabContent(){
        Text('我的主页')
      }
      .tabBar($r('app.string.tab_user'))
    }
    .width('100%')
    .height('100%')
  }
}

1.3完善代码--改变bar位置及其样式和点击高亮功能

及其实现结果

Builder函数:自定义TabBar的样式 

index参数:在TabBarBuilder中的参数index标记,实现点击不同的导航选项使其变色


import { CommonConstants } from '../common/constants/CommonConstants'

@Entry
@Component
struct Index {
  @State currentIndex:number=0
  //TODO Z2 设置bar样式是图片加文字
  //TODO Z3.index—控制样式
  @Builder TabBarBuilder(title:ResourceStr,image:ResourceStr,index:number){
    Column({space:CommonConstants.SPACE_8}){
      Image(image)
        .width(22)
        //判切换的角标跟样式角标一样的,蓝色,否则灰色
          //TODO Z5.fillcolor的图片对象必须是svg格式
        .fillColor(this.selectColor(index))
      Text(title)
        .fontSize(14)
        .fontColor(this.selectColor(index))
    }
  }
  /*高亮颜色设置函数*/
  selectColor(index:number){
    return this.currentIndex===index?$r('app.color.primary_color'):$r('app.color.gray')
  }
  build() {
    //TODO Z1.bar(标题)的位置设置在下面
    Tabs({barPosition:BarPosition.End}){
      //1.饮食记录
      TabContent(){
        Text('饮食记录页面')
      }
      .tabBar(this.TabBarBuilder($r('app.string.tab_record'),$r('app.media.ic_calendar'),0))
      //2.发现页面
      TabContent(){
        Text('发现页面')
      }
      .tabBar(this.TabBarBuilder($r('app.string.tab_discover'),$r('app.media.discover'),1))
      //3.我的主页
      TabContent(){
        Text('我的主页')
      }
      .tabBar(this.TabBarBuilder($r('app.string.tab_user'),$r('app.media.ic_user_portrait'),2))
    }
    .width('100%')
    .height('100%')
    //TODO z4.记录当前切换的角标
    .onChange(index=>
      this.currentIndex=index
    )
  }
}

2.RecordIndex-页面内容UI

二,SearchHeader-顶部搜索栏

1.饮食记录UI

2.页面代码

 //TODO Z1 seacher组件带图标;input不带图标

//TODO Z2.Badge容器型组件-显示数字角标

import { CommonConstants } from '../../common/constants/CommonConstants'
@Component
export default struct SeacherHeader {
  build() {
    Row({space:CommonConstants.SPACE_6}){
      //带搜索图标的输入框
      //TODO Z1 seacher组件带图标;input不带图标
      Search({placeholder:'搜索饮食或运动信息'})
        .textFont({size:18})
        .layoutWeight(1)
      //TODO Z2.Badge组件-显示数字角标
      Badge({count:1,position:BadgePosition.RightTop,style:{fontSize:12}}){
        Image($r('app.media.ic_public_email'))
          .width(20)
      }

    }
    .width(CommonConstants.THOUSANDTH_940)
  }
}

三,StatsCard统计卡片

1.StatsCard-统计卡片UI:日期信息(自定义日期选择弹窗)+统计信息(滑动切换)

技术:日期选择窗~DatePicker;

            穿梭切换~Swiper;

 整体是一个列式的布局

build() {
    Column(){
      // 1.日期信息
      Row(){
        Text(...)
        Image(...)
      }
        //点击启动弹窗
        .onClick(() => this.controller.open())

      // 2.统计信息
      Swiper(){
        // 2.1.热量统计
        CalorieStats(...)
        // 2.2.营养素统计
        NutrientStats(...)
      }
      ...
    }
   ...
  }

 1.1.DatePickDialog--(自定义日期选择)弹窗 UI 和代码

 //TODO Z1. DatePickDialog组件中的月份是0到11,在开发的过程中需要将月份加一

//TODO Z2. AppStorage.SetOrCreate全局存储;

存储数据时不要把日期存进去,否则在做日期监控的过程中会出现问题,应存储它所对应的毫秒值,用getTime去将日期时间转换为毫秒值。

在读取AppStorage的值时可以使用@StorageLink(双向绑定)或者@StorageProp(单向绑定)。

 

import { CommonConstants } from '../../common/constants/CommonConstants'
@CustomDialog
export default struct DatePickDialog {
  controller: CustomDialogController
  selectedDate: Date = new Date()
  build() {
    Column({space: CommonConstants.SPACE_12}){
      // 1.日期选择器
      DatePicker({
        start: new Date('2020-01-01'),
        end: new Date(),
        selected: this.selectedDate
      })
        .onChange((value: DatePickerResult) => {
          this.selectedDate.setFullYear(value.year, value.month, value.day)
        })
      // 2.按钮
      Row({space:CommonConstants.SPACE_12}){
        Button('取消')
          .width(120)
          .backgroundColor($r('app.color.light_gray'))
          .onClick(() => this.controller.close())
        Button('确定')
          .width(120)
          .backgroundColor($r('app.color.primary_color'))
          .onClick(() => {
            // 1.保存日期到全局存储
            AppStorage.SetOrCreate('selectedDate', this.selectedDate.getTime())
            // 2.关闭窗口
            this.controller.close()
          })
      }
    }
    .padding(CommonConstants.SPACE_12)
  }
}

1.1.1DateUtil(class)
 

//TODO Z3. DateUtil组件--今天的开始日期;因为我们日期选择弹窗显示的日期只有年月日的零时零分零秒。

selectedDate中用getTime()来转换为毫秒值会精确到时分秒×

 DateUtil组件--是今天的零时零分零秒,为了在以后的日期判断时不出现错误(例如中间不到24小时)

class DateUtil{
 
  formatDate(num: number): string{
    let date = new Date(num)
    let year = date.getFullYear()
    let month = date.getMonth()+1
    let day = date.getDate()
    let m = month < 10 ? '0' + month : month
    let d = day < 10 ? '0' + day : day
    return `${year}/${m}/${d}`
  }
 
  beginTimeOfDay(date: Date){
    let d = new Date(date.getFullYear(), date.getMonth(), date.getDate())
    return d.getTime()
  }
}
 
let dateUtil = new DateUtil()
 
export default dateUtil as DateUtil

1.2统计信息(滑动切换)UI和代码

Swiper:  (1)CalorieStats-热量统计

              (2)NutrientStats-营养素统计

//TODO Z1.Swiper-穿梭切换卡片的部分,可以为子组件提供滑动轮播显示的能力

// 2.统计信息
      Swiper(){
        // 2.1.热量统计
        CalorieStats(...)
        // 2.2.营养素统计
        NutrientStats(...)
      }

1.2.1CalorieStats--热量统计UI和代码

import { CommonConstants } from '../../common/constants/CommonConstants'
@Component
export default struct CaloriesStates {
  intake: number=192
  expend: number=150
  //一般是根据身高体重计算,这里直接初始化
  recommend: number = CommonConstants.RECOMMEND_CALORIE
//TODO Z3.还可以吃=recommend-摄入+消耗
  remainCalorie() {
    return this.recommend - this.intake + this.expend;
  }

  build() {
    Row({ space: CommonConstants.SPACE_6 }) {
      //1.饮食摄入
      this.StatsBuilder('饮食摄入',this.intake)
      //2.还可以吃多少
      //TODO z2. stack重叠容器+progress进度条
      Stack(){
        // 2.1.进度条
        Progress({
          value: this.intake,
          total: this.recommend,
          type: ProgressType.Ring
        })
          .width(120)
          .style({strokeWidth: CommonConstants.DEFAULT_10})
          .color($r('app.color.primary_color'))
        //2.2统计数据
        this.StatsBuilder('还可以吃多少',this.remainCalorie(),`推荐${this.recommend}`)

      }
      //3.运动消耗
      this.StatsBuilder('运动消耗',this.expend)
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceEvenly)
    .padding({ top: 30, bottom: 35 })

  }

  @Builder StatsBuilder(label:string, value :number ,tips?:string){
    Column({space:6}) {
      Text(label)
        .fontColor($r('app.color.gray'))
        .fontWeight(CommonConstants.FONT_WEIGHT_600)
      //TODO Z1.text里是字符串,number转成整形字符串

      Text(value.toFixed(0))
        .fontSize(20)
        .fontWeight(CommonConstants.FONT_WEIGHT_700)
      if (tips) {
        Text(tips)
          .fontSize(12)
          .fontColor($r('app.color.gray'))
      }
    }
  }

}

1.2.2NutrientStats--营养素统计UI和代码

import { CommonConstants } from '../../common/constants/CommonConstants'
@Component
export default struct CaloriesStates {
  carbon: number=23
  protein: number=9
  fat: number=7

  recommendCarbon: number = CommonConstants.RECOMMEND_CARBON
  recommendProtein: number = CommonConstants.RECOMMEND_PROTEIN
  recommendFat: number = CommonConstants.RECOMMEND_FAT

  build() {
    Row({ space: CommonConstants.SPACE_6 }) {
      this.StatsBuilder(
        '碳水化合物',
        this.carbon,
        this.recommendCarbon,
        $r('app.color.carbon_color')
      )
      this.StatsBuilder(
        '蛋白质',
        this.protein,
        this.recommendProtein,
        $r('app.color.protein_color')
      )
      this.StatsBuilder(
        '脂肪',
        this.fat,
        this.recommendFat,
        $r('app.color.fat_color')
      )
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceEvenly)
    .padding({ top: 30, bottom: 35 })

  }

  @Builder StatsBuilder(label:string, value :number , recommend: number, color: ResourceStr){
    Column({space:6}) {
      Stack(){
        // 2.1.进度条
        Progress({
          value: value,
          total: recommend,
          type: ProgressType.Ring
        })
          .width(95)
          .style({strokeWidth: CommonConstants.DEFAULT_8})
          .color(color)
        Column({space:6}){
          Text('摄入推荐')
            .fontSize(12)
            .fontColor($r('app.color.gray'))
          Text(`${value.toFixed(0)}/${recommend.toFixed(0)}`)
            .fontSize(20)
            .fontWeight(CommonConstants.FONT_WEIGHT_700)
        }

      }
      Text(`${label}(克)`)
        .fontSize(12)
        .fontColor($r('app.color.gray'))


    }
  }

}

 #注意事项

 1.index-主页

2.ReardIndex-页面内容

2.1.SearcherHeader-头部导航

2.2StatsCard-卡片

2.2.1DatePickDialog-自定义日期弹窗
2.2.2swiper组件-切换器
2.2.2.1CaloriesStats-第一页

2.2.2.2NeuTrientStats-第二页

#结果

 4.RecordList--记录列表UI和代码

// import router from '@ohos.router'
import { CommonConstants } from '../../common/constants/CommonConstants'
// import { CommonConstants } from '../../common/constants/CommonConstants'
// import RecordService from '../../service/RecordService'
// import GroupInfo from '../../viewmodel/GroupInfo'
// import RecordType from '../../viewmodel/RecordType'
// import RecordVO from '../../viewmodel/RecordVO'
//

//TODO Z1. 不属于通用样式,是TEXT文本单独样式,用extend(text)
@Extend(Text) function grayText(){
  .fontSize(14)
  .fontColor($r('app.color.light_gray'))
}

@Component
export default struct RecordList {

  // @Consume @Watch('handleRecordsChange') records: RecordVO[]
  // @State groups: GroupInfo<RecordType, RecordVO>[] = []
  //
  // handleRecordsChange(){
  //   this.groups = RecordService.calculateGroupInfo(this.records)
  // }


  build() {
    List({space: CommonConstants.SPACE_10}){
      ForEach([1,2,3,4,5], (item) => {
        ListItem(){
          Column({space: CommonConstants.SPACE_8}){
            // 1.分组的标题
            Row({space: CommonConstants.SPACE_4}){
              Image($r('app.media.ic_breakfast')).width(24)
              Text('早餐').fontSize(18).fontWeight(CommonConstants.FONT_WEIGHT_700)
              Text(`建议423~592.max}千卡`).grayText()
              Blank()
              Text('190').fontSize(14).fontColor($r('app.color.primary_color'))
              Text('千卡').grayText()
              Image($r('app.media.ic_public_add_norm_filled'))
                .width(20)
                .fillColor($r('app.color.primary_color'))
            }
            .width('100%')
            // .onClick(() => {
            //   router.pushUrl({
            //     url: 'pages/ItemIndex',
            //     params: {type: group.type}
            //   })
            // })

            // 2.组内记录列表
            List(){
              ForEach([1,2], (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('1片').grayText()
                    }
                    Blank()
                    Text(`91千卡`).grayText()
                  }
                  .width('100%')
                  .padding(CommonConstants.SPACE_6)
                }
                //TODO Z3. 侧滑删除食物
                .swipeAction({end: this.deleteButton.bind(this)})
              })
            }
            .width('100%')
          }
          .width('100%')
          .backgroundColor(Color.White)
          .borderRadius(CommonConstants.DEFAULT_18)
          .padding(CommonConstants.SPACE_12)
        }
      })
    }
    .width(CommonConstants.THOUSANDTH_940)
    .height('100%')
    .margin({top: 10})
  }
//TODO Z2.删除定义样式
  @Builder deleteButton(){
    Image($r('app.media.ic_public_delete_filled'))
      .width(20)
      .fillColor(Color.Red)
      .margin(5)
  }
}

四,ItemIndex--食物列表

1.ItemIndex-食物列表主页 UI 和代码:

        1.1头部导航--Header(@Builder函数)

        1.2列表--ItemList

        1.3底部面板--Panel

                1.3.1顶部日期--ItemPanelHeader

                1.3.2记录项卡片--ItemCard

                1.3.3数字键盘--NumberKeyboard

                1.3.4按钮--PanelButton(@Builder函数)

 

列式布局的表单,最上方是一个导航条、其下面的每个卡片包含文本、图片、按钮等。

点击导航条上不同的导航项时会显示不同的页面--Tabs组件,下面的卡片内容用List来渲染

1.1Header--头部导航-UI和代码(@Builder函数)

1.2.ItemList-食物列表页-UI及其代码

// import { CommonConstants } from '../../common/constants/CommonConstants'
import { CommonConstants } from '../../common/constants/CommonConstants'
// import ItemModel from '../../model/ItemModel'
// import GroupInfo from '../../viewmodel/GroupInfo'
// import ItemCategory from '../../viewmodel/ItemCategory'
// import RecordItem from '../../viewmodel/RecordItem'

@Component
export default struct ItemList {
  // showPanel: (item: RecordItem) => void
  // @Prop isFood: boolean

  build() {
    Tabs() {
      TabContent() {
        this.TabContentBuilder()
      }
      .tabBar('全部')
      TabContent() {
        this.TabContentBuilder()
      }
      .tabBar('主食')
      TabContent() {
        this.TabContentBuilder()
      }
      .tabBar('肉蛋奶')

      // ForEach(
      //   ItemModel.listItemGroupByCategory(this.isFood),
      //   (group: GroupInfo<ItemCategory, RecordItem>) => {
      //     TabContent() {
      //       this.TabContentBuilder(group.items)
      //     }
      //     .tabBar(group.type.name)
      //   })
    }
    .width(CommonConstants.THOUSANDTH_940)
    .height('100%')
    //.barMode(BarMode.Scrollable)
  }

  @Builder TabContentBuilder() {
    List({space:CommonConstants.SPACE_6}) {
      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%')

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

1.3Panel--底部面板-UI和代码)(在ItemList添加点击事件

//TODO z1.设函数:达到子控制父,父调用时覆盖)

Panel组件:一个可滑动面板,提供一种轻量的内容展示窗口,方便在不同尺寸中切换

注意Panel它所在容器的高度以及内元素的高度必须是一个固定的

1.3.1 顶部日期--ItemPanelHeader-UI和代码

1.3.2 记录项卡片--ItemCard-UI和代

import { CommonConstants } from '../../common/constants/CommonConstants'
import RecordItem from '../../viewmodel/RecordItem'
@Component
export default struct ItemCard {
  //TODO Z3. 键盘不是本组件内,键盘修改,这里接受用PROP;Prop不需要初始化
  @Prop amount: number
 // @Link item: RecordItem

  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})
      //TODO Z1. 下划线
      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'))
      //TODO Z2.toFix(1)转成1位小数
      Text(value.toFixed(1)).fontSize(18).fontWeight(CommonConstants.FONT_WEIGHT_700)
    //(value * this.amount)
    }
  }
}
#注意事项
1.ItemList

 

2.ItemPanelHeader

3.ItemCard

 

4.ItemIndex

1.3.3 NumberKeyboard--数字键盘-UI和代码

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() {
    //TODO Z1. 网格容器
    Grid(){
      ForEach(this.numbers, num => {
        /*按钮.~9*/
        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%')
    //TODO Z3. 参考元素数据设置4*行高(60)+5*行距(8)=240+40=280
    .height(280)
    .backgroundColor($r('app.color.index_page_background'))
    //TODO Z2. 只设置列,动态布局;设置列和行,固定布局,多出来的不显示;
    .columnsTemplate('1fr 1fr 1fr')
    .columnsGap(8)//列间距
    .rowsGap(8)
    .padding(8)
    .margin({top: 10})
  }
  /*键盘转换到卡片,校验格式*/
  clickNumber(num: string){
    // 1.拼接用户输入的内容
    let val = this.value + num
    // 2.校验输入格式是否正确(俩小数点/小数点位数=2)
    let firstIndex = val.indexOf('.')//从前往后数第一个.位置
    let lastIndex = val.lastIndexOf('.')//从后往前数第一个.位置
    //if有两个小数点/一个小数点,小数位数超过两位
    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)
  }
  /*string转number AND 校验格式 */
  parseFloat(str: string){
    //TODO Z4. 删完了变成0
    if(!str){
      return 0
    }
    //若最后一位是".",
    if(str.endsWith('.')){
      //提取字符串中介于两个指定索引之间的字符。
      str = str.substring(0, str.length - 1)
    }
    //string转number
    return parseFloat(str)
  }
}

1.3.4PanelButton(@Builder函数)--按钮-UI和代码

 

#注意事项

1.ItemIndex

 

2.NumberKeyboard

  • 12
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值