【鸿蒙HarmonyOS.4】健身APP项目 第四天(借鉴b站某厂程序员)

目录

实现添加任务弹窗中的点击事件

成就页面Top部分

完成成就页面

完成个人页面

 遇到的问题


现阶段已经把所有的页面和功能都已经创建出来。


  • 实现添加任务弹窗中的点击事件

  • 点击添加按钮后弹出小键盘输入要完成的任务目标

  •  根据要完成的任务目标计算预计消耗卡路里,并可继续点击修改按钮进行修改(现在没有涉及到数据交互,则再次点击确定按钮后直接关闭该弹窗)。

  • 该部分现阶段TaskAddDialog.ets代码
//添加任务弹窗

import DateUtil from '../utils/DateUtil'

//封装,设置专属GridItem样式
@Extend(GridItem) function btnStyle() {
  .backgroundColor(Color.White)
  .opacity(0.7)
  .height(50)
  .borderRadius(15)
}

@Preview
@CustomDialog
export default struct TaskAddDialog {

  //从全局内取出日期
  @StorageProp('date') date: number = DateUtil.beginTimeOfDay(new Date())

  //弹窗弹出时小键盘就会存在
  @State show: boolean = true

  //创建一个字符串类型数组填入小键盘
  numberArr: string[] = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '.']

  controller: CustomDialogController

  //定义clickNumber内的变量
  @State value: string = ''
  @State num: number = 0

  //定义一个卡路里量
  @State calorie: number = 500

  //创建一个自定义组件
  @Builder
  saveBtn(text: string, color: ResourceStr, onClick: () => void) {
    Button() {
      Text(text)
        .fontSize(20)
        .fontWeight(800)
        .opacity(0.9)
    }
    .width(80)
    .height(50)
    .type(ButtonType.Normal)
    .backgroundColor(color)
    .borderRadius(5)
    .padding({ left: 3, right: 3 })
    .onClick(onClick)
  }


  //数字键的点击事件
  clickNumber(num: string) {
    let val = this.value + num

    let firstIndex = val.indexOf('.')  //第一个点的下标
    let lastIndex = val.indexOf('.')   //最后一个点的下标
    if(firstIndex !== lastIndex || (lastIndex != -1 && lastIndex < val.length - 2)) {
      return
    }
    //将val字符串类型转换成数字
    let amount = this.parseFloat(val)

    if(amount >= 999.9) {
      this.num = 999.0
      this.value = '999'
    }else {
      this.num = amount
      this.value = val
    }
  }

  //删除键的点击事件
  clickDel() {
    if(this.value.length <= 0) {
      this.value = ''
      this.num = 0
      return
    }
    this.value = this.value.substring(0, this.value.length - 1)
    this.num = this.parseFloat(this.value)
  }

  //字符转数字转换方法
  parseFloat(str: string) {  // 鸿蒙提供的可以将字符串类型转换成数字
    if(!str) {      //判断是否为空
      return 0
    }
    if(str.endsWith('.')){        //最后为点时加一个0
      str = str.substring(0, str.length - 1)
    }
    return parseFloat(str)
  }

  build() {
    Column() {
      Row() {
        //左上角日期
        Text(DateUtil.formatDate(this.date)) //从全局内获取日期
          .fontSize(15)
          .fontWeight(600)
        Blank(10)
        //右上角按钮
        Button() {
          Text('x')
            .fontSize(15)
            .fontColor(Color.White)
            .fontWeight(800)
        }
        .width(20)
        .height(20)
        .backgroundColor(Color.Red)
        .padding({ bottom: 5 })
        .onClick(() => {
          this.controller.close()
        })
      }
      .width('95%')
      .justifyContent(FlexAlign.End)

      //运动任务图标
      Column({ space: 10 }) {
        Image($r('app.media.swim'))
          .width(90)
          .height(90)
        Text('游泳')
          .fontSize(20)
          .fontWeight(700)
          .backgroundColor($r('app.color.light_gray'))

        //目标任务量输入
        Row() {
          TextInput({text: this.num.toFixed(1)})
            .width('35%')
            .fontSize(35)
            .fontColor($r('app.color.task_color'))
            .caretColor(Color.Transparent)
            .textAlign(TextAlign.Center)
            .copyOption(CopyOptions.None)
          Text('/小时')
            .fontSize(35)
            .opacity(0.7)
            .fontWeight(800)
        }

        //Panel组件展示小键盘
        Panel(this.show) {
          Column() {
            //格栅容器Grid
            Grid() {
              //遍历小键盘数组
              ForEach(this.numberArr, (item) => {
                GridItem() {
                  Text(item)
                    .fontSize(20)
                    .fontWeight(500)
                }
                .btnStyle()     //专属GridItem样式
                .onClick(() => {
                  this.clickNumber(item)    //调用数字键点击事件
                })
              })
              GridItem() {
                Text('删除')
                  .fontSize(20)
                  .fontWeight(500)
              }
              .btnStyle()
              .onClick(() => {
                this.clickDel()
              })

              GridItem() {
                this.saveBtn('确定', $r('app.color.btn_color'), () => this.show = false) //改变show的状态,可以使Panel组件消失掉
              }
              .columnStart(1)  //开始在第一行
              .columnEnd(3)    //结束在第三行
              .btnStyle()
            }
            .columnsTemplate('1fr 1fr 1fr')  //三列,每一列占据一样的宽度
            .columnsGap(5)
            .rowsGap(8)
            .width('95%')
            .padding({ top: 15 })
          }
        }
        .mode(PanelMode.Half) //展示占据一半
        .halfHeight(1050)
        .type(PanelType.Temporary)
        .dragBar(false)
        .width('100%')

        //消耗多少卡路里
        Row() {
          Text('预计消耗' + this.calorie * this.num + '卡路里')
            .fontSize(20)
            .fontWeight(70)
            .opacity(0.7)
        }
        //创建一个在点击确定后可以在任务图标下可以修改的按钮
        Row({space: 20}) {
          this.saveBtn('修改', $r('app.color.light_gray'), () => this.show = this.show = true)
          this.saveBtn('确定', $r('app.color.btn_color'), () => this.controller.close())    //现在没有涉及到数据交互,则直接关闭即可
        }
      }
    }
    .width('95%')
    .height('95%')
    .alignItems(HorizontalAlign.Center)
  }
}
  • 该部分现阶段AddTaskPage.ets代码
//添加任务按钮跳转页面

import router from '@ohos.router'
import TaskAddDialog from '../dialog/TaskAddDialog'

@Entry
@Component
struct AddTaskPage {

  controller: CustomDialogController = new CustomDialogController({
    builder: TaskAddDialog()      //在任务添加列表中点击加号添加后app直接崩溃自动退出,在TaskAddDialog未加()
  })

  //任务数组
  arr: any[] = [
    {
      name: '跳绳',
      icon: $r('app.media.skip'),
      consume: 600,
      pre: '小时'
    },
    {
      name: '游泳',
      icon: $r('app.media.swim'),
      consume: 400,
      pre: '小时'
    },
    {
      name: '卧推',
      icon: $r('app.media.push'),
      consume: 50,
      pre: '个'
    },
    {
      name: '慢跑',
      icon: $r('app.media.jog'),
      consume: 600,
      pre: '小时'
    },

  ]

  build() {
    Column() {
      //返回按钮
      Row() {
        Image($r('app.media.back'))
          .width(25)
      }
      .margin({ top: 10, left: 10, bottom: 10})
      .onClick(() => { //返回上一页
        router.back()
      })

      //遍历数组展示
      List({ space: 10}) {
        //遍历
        ForEach(this.arr, (item) => {
          ListItem() {
            Row() {
              Image(item.icon)
                .width(60)
                .height(60)
                .margin({ right: 15})
              Column() {
                Text(item.name)
                  .fontSize(15)
                  .fontWeight(500)
                Text(item.consume + '卡路里/' + item.pre)
                  .fontSize(10)
                  .fontWeight(600)
                  .opacity(0.7)
              }
              .alignItems(HorizontalAlign.Start) //交叉轴方向左对齐
              Blank()
              Button() {
                Image($r('app.media.add_norm_filled'))
                  .width(20)
              }
              .backgroundColor(Color.Transparent)  //透明背景
              .onClick(() => {
                this.controller.open()
              })
            }
            .width('100%')
            .justifyContent(FlexAlign.SpaceBetween) //主轴方向对齐方式
          }
          .width('95%')
          .backgroundColor(Color.White)
          .padding(5)
        })
      }
      .width('100%')
      .alignListItem(ListItemAlign.Center)
    }
    .width('100%')
    .height('100%')
    .backgroundColor($r('app.color.light_gray'))
    .alignItems(HorizontalAlign.Start)
  }
}

  • 成就页面Top部分

  • 在view文件夹中创建achievement文件夹新建AchievementTop.ets成就页面上半部分文件

  • 该部分现阶段AchievementTop.ets代码
//成就页面Top部分

import DateDialog from '../../dialog/DateDialog'
import DateUtil from '../../utils/DateUtil'

//健身记录、运动消耗、距离目标样式差不多,则封装此样式
@Extend(Text)
function textStyle(color: ResourceStr | Color, fw: number | FontWeight) {
  .fontSize(20)
  .fontWeight(fw)
  .fontColor(color)
}

@Preview
@Component
export default struct AchievementTop {

  //运动消耗掉多少卡路里
  @State value: number = 1930;
  //总共需要消耗多少卡路里
  @State task: number = 3500;

  //全局获取日期
  @StorageProp('date') date: number = DateUtil.beginTimeOfDay(new Date())

  //日期选择弹窗
  controller: CustomDialogController = new CustomDialogController({
    builder: DateDialog({date: new Date(this.date)})
  })

  build() {
    Column() {
      Column({ space: 5}) {
        Text(DateUtil.formatDate(this.date))    //日期显示,调用formatDate传入日期
          .fontSize(15)
          .fontColor($r('app.color.dark_gray'))
          .onClick(() => {              //点击事件,用户在此页面也可以更换日期,从全局获取日期
            this.controller.open()
          })
        Text('健身记录')
          .textStyle(Color.White, 800)    //直接调用封装的样式
      }
      .width('95%')
      .margin({top: 30, bottom: 10})
      .alignItems(HorizontalAlign.Start)    //在交叉轴上的对齐方式,左对齐

      //横向展示健身记录内容
      Row() {
        Column({space: 5}) {
          Text('运动消耗')
            .textStyle(Color.White, 500)        //直接调用封装的样式
          Text(this.value + '/' + this.task + '千卡')
            .textStyle($r('app.color.theme_color'), 800)
            .margin({right: 20, bottom: 30})
          Text('距离目标')
            .textStyle(Color.White, 500)
          Text((this.task - this.value) + '千卡')  //剩余多少卡路里需要消耗
            .textStyle($r('app.color.progress_sel'), 800)
        }
        .alignItems(HorizontalAlign.Start)
        .margin({left: 5})
        Progress({            //进度条组件
          value: this.value,
          total: this.task,
          type: ProgressType.Ring     //样式,圆形
        })
          .color($r('app.color.progress_sel'))
          .backgroundColor($r('app.color.progress_normal'))
          .width('50%')
          .height('95%')
          .style({strokeWidth: 25})
      }
      .backgroundColor($r('app.color.dark_gray'))
      .width('95%')
      .height(190)
      .borderRadius(15)
    }
    .width('100%')
    .height('100%')
  }
}
  • 在MainIndex.ets首页页面的TabContent()下调用AchievementTop()成就页部分
//成就页
      TabContent() {
        AchievementTop()
      }

  • 完成成就页面

  • 在viewmodel文件夹中创建AchievementInfo.ets成就页徽章设置1和AchievementMapInfo.ets成就页徽章设置2
  • 该部分现阶段AchievementInfo.ets代码和AchievementMapInfo.ets代码
//成就页徽章设置1

//徽章属性
export default class AchievementInfo {
  days: number;
  icOn: ResourceStr;
  icOff: ResourceStr;

  constructor(days: number, icOn: ResourceStr, icOff: ResourceStr) {
    this.days = days;
    this.icOn = icOn;
    this.icOff = icOff;
  }
}
//成就页徽章设置2

//徽章照片(打开,关闭)
export default class AchievementMapInfo {
  off_3: ResourceStr = $r('app.media.ic_badge_3_off');  //off_3代表没有完成第三天的徽章
  on_3: ResourceStr = $r('app.media.ic_badge_3_on');    //on_3代表完成
  off_7: ResourceStr = $r('app.media.ic_badge_7_off');
  on_7: ResourceStr = $r('app.media.ic_badge_7_on');
  off_30: ResourceStr = $r('app.media.ic_badge_30_off');
  on_30: ResourceStr = $r('app.media.ic_badge_30_on');
  off_50: ResourceStr = $r('app.media.ic_badge_50_off');
  on_50: ResourceStr = $r('app.media.ic_badge_50_on');
  off_73: ResourceStr = $r('app.media.ic_badge_73_off');
  on_73: ResourceStr = $r('app.media.ic_badge_73_on');
  off_99: ResourceStr = $r('app.media.ic_badge_99_off');
  on_99: ResourceStr = $r('app.media.ic_badge_99_on');
}
  • 在view文件夹中创建achievement文件夹新建AchievementContent.ets成就页面下半部分文件

  • 该部分现阶段AchievementContent.ets代码
//成就页面下半内容

import AchievementInfo from '../../viewmodel/AchievementInfo'
import AchievementMapInfo from '../../viewmodel/AchievementMapInfo'
import AchievementTop from './AchievementTop'
@Component
export default struct AchievementContent {

  //导入后创建该变量
  achievementMapInfo: AchievementMapInfo = new AchievementMapInfo()

  @State successDays: number = 9   //先将用户定义成完成9天
  @State success: Array<AchievementInfo> = [
    {
      days: 3,
      icOn: this.achievementMapInfo.on_3,
      icOff: this.achievementMapInfo.off_3
    },
    {
      days: 7,
      icOn: this.achievementMapInfo.on_7,
      icOff: this.achievementMapInfo.off_7
    },
    {
      days: 30,
      icOn: this.achievementMapInfo.on_30,
      icOff: this.achievementMapInfo.off_30
    },
    {
      days: 50,
      icOn: this.achievementMapInfo.on_50,
      icOff: this.achievementMapInfo.off_50
    },
    {
      days: 73,
      icOn: this.achievementMapInfo.on_73,
      icOff: this.achievementMapInfo.off_73
    },
    {
      days: 99,
      icOn: this.achievementMapInfo.on_99,
      icOff: this.achievementMapInfo.off_99
    },
  ]

  build() {
    Column() {
      //导入成就页Top部分,则在MainIndex首页页面内的TabContent()后删除AchievementTop()
      Row() {AchievementTop()}
      .height('40%')
      Column() {
        Row() {
          Text('成就')
            .fontSize(20)
            .fontWeight(500)
            .fontColor(Color.White)
        }
        .width('95%')
        Column({space: 10}) {
          Flex({direction: FlexDirection.Row, wrap: FlexWrap.Wrap}) { //Flex弹性布局,呈现出行
            ForEach(this.success, (item) => {
              //图片和文字纵向排列
              Column() {
                Image(this.successDays >= item.days ? item.icOn : item.icOff)  //如果完成天数大于等于徽章天数,则展示点亮状态,否则反之
                  .width('100%')
                  .height(88)
                  .objectFit(ImageFit.Contain)
                Text($r('app.string.task_achievement_level', item.days))  //将徽章的天数传入进去
                  .lineHeight(16)
                  .fontSize(12)
                  .fontColor(Color.White)
              }
              .width('33%')
              .padding({top: 38, bottom: 10})
            })
          }
        }
      }
      .width('100%')
    }
    .width('100%')
    .height('100%')
    .backgroundColor(Color.Black)
  }
}
  • 在MainIndex.ets首页页面的TabContent()下将AchievementTop()成就页部分更改为AchievementContent()
TabContent() {
        AchievementContent()
      }

  • 完成个人页面

  • view文件夹中创建mine文件夹新建MineContent.ets个人页面

  • 该部分现阶段MineContent.ets代码
//个人页面

@Component
export default struct MineContent {

  //定义一个自定义组件
  @Builder
  item(msg: string) {
    Row() {
      Text(msg)
        .fontSize(20)
        .fontWeight(500)
      Image($r('app.media.ic_right_grey'))
        .objectFit(ImageFit.Contain)
        .height(12)
        .width(7)
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceBetween) //一个在最左边,一个在最右边
    .margin({top: 15, bottom: 15})
  }


  build() {
    Column() {
      Row() {
        Column() {
          Image($r('app.media.icon_user'))
            .height(66)
            .width(66)
            .objectFit(ImageFit.Contain)
            .margin({top: 71})
          Text('小王')
            .fontSize(20)
            .margin({bottom: 6})
        }
        .width('100%')
      }
      .width('100%')
      .height('25%')
      .backgroundColor($r('app.color.light_gray'))
      Column() {
        this.item('个人资料')
        Divider()             //下划线
        this.item('检测更新')
        Divider()
        this.item('关于')
        Divider()
      }
      .width('90%')
    }
    .width('100%')
    .height('100%')
  }
}

  •  遇到的问题

在任务添加列表中点击加号添加后app直接崩溃自动退出到模拟器桌面

在 AddTaskPage.ets中controller的TaskAddDialog后未添加()导致点击添加按钮后系统崩溃退出系统。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值