目录
现阶段已经把所有的页面和功能都已经创建出来。
-
实现添加任务弹窗中的点击事件
- 点击添加按钮后弹出小键盘输入要完成的任务目标
- 根据要完成的任务目标计算预计消耗卡路里,并可继续点击修改按钮进行修改(现在没有涉及到数据交互,则再次点击确定按钮后直接关闭该弹窗)。
- 该部分现阶段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后未添加()导致点击添加按钮后系统崩溃退出系统。