目录
一、导言
1、项目介绍:
(1)项目目标:本项目综合运用本学期所学内容及个人自学知识,使用Harmony0s 4.0及以上版本开发的一款具有实用性和创新性的移动应用软件。
(2)项目介绍:本项目是一款关于记录饮食/运动的健康应用;
在登录页面,用户点击同意协议弹窗后可跳转到饮食记录页;
记录页给出推荐的摄入热量,并记录每天饮食摄入热量和运动消耗热量,在一日三餐列表中和运动列表中可添加相应的食物内容和运动内容,跳转到食物/运动列表页面;
在食物/运动列表页中选择食物类型和数量可计算出摄入热量,通过关系型数据库存储信息,并展示到统计页面中。
(3)实现功能:
主体功能页面:
1.欢迎页面
2.饮食统计页
3.食物列表页
具体实现:
- 欢迎页面
ⅰ、UI设计
ⅱ、页面业务设计 - 饮食记录
ⅰ、首页Tabs
ⅱ、顶部搜索栏
ⅲ、统计卡片
ⅳ、记录列表 - 食物/运动列表
ⅰ、食物列表页
ⅱ、底部Panel
ⅲ、数字键盘 - 记录项
ⅰ、饮食记录
ⅱ、数据模型 - 数据持久化
ⅰ、通用DB工具
ⅱ、饮食记录业务开发
ⅲ、数据持久化和页面交互
(4)运行效果:
二、欢迎页面
1、UI设计
欢迎页面结构:
//整体为列式布局,内嵌两个行布局,分别存放slogan图片和三行文字
Column(){
Row(){Image(slogan)}
Image
Row(){Text(text1)Text(text2)Text(text3)}
Text(介绍)Text(版权号)
}.width('100%')
.height('100%')
.backgroundColor()
ps1:slogan图片在Row容器中,不设置权重时会出现整体内容靠上的界面;
ps2:slogan图片不在Row容器中,但设置权重时会图片会撑满整个界面;
上述原因分析:layoutweight属性的意义就是将父控件的剩余空间按照设置的权重比例再分配,不在行容器内,图片自己会撑满剩余空间。在行内会按照比例扩大。
文字公共样式属性可用@Extend抽取,定义专用函数,并传入相关参数。
@Extend装饰器,仅可定义在全局,可以设置组件特有属性,这里是Text组件
例如:
@Extend(Text) function opacityWhiteText(opacity: number, fontSize: number = 10) {
.fontSize(fontSize)
.opacity(opacity)
.fontColor(Color.White)
}
运行效果:
代码如下:
// 抽取公共样式
@Extend(Text) function opacityWhiteText(opacity: number, fontSize: number = 10) {
.fontSize(fontSize)
.opacity(opacity) // 设置文本的透明度
.fontColor(Color.White)
}
@Entry
@Component
struct WelcomePage {
build() {
Column({ space: 10 }) {
// 1. 中央Slogan
Row() {
Image($r('app.media.home_slogan')).width(260)
}
.layoutWeight(1) // 设置该行在列中的权重
// 2. logo
Image($r('app.media.home_logo')).width(150)
// 3. 文字描述
Row() {
Text('黑马健康支持').opacityWhiteText(0.8, 12)
Text('IPv6')
.opacityWhiteText(0.8)
.border({ style: BorderStyle.Solid, width: 1, color: Color.White, radius: 15 })
.padding({ left: 5, right: 5 })
Text('网络').opacityWhiteText(0.8, 12)
}
Text('‘减更多‘指黑马健康App希望通过软件工具的形式,帮助更多用户实现身材管理')
.opacityWhiteText(0.6)
Text('浙ICP备0000000号-36D')
.opacityWhiteText(0.4)
.margin({ bottom: 35 })
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.welcome_page_background'))
}
}
2、页面业务设计
流程图如下
用户协议同意与否的状态需要持久化保存,由于是轻量级数据,所以这里采用用户首选项保存
关键技术:
用户首选项
在弹窗确定按钮中添加点击事件confirm来调用用户首选项。
并在EntryAbility.ts中的onCreate生命周期中加载首选项。
相关代码:
//EntryAbility.ts加载首选项
export default class EntryAbility extends UIAbility {
async onCreate(want, launchParam) {
// 1.加载用户首选项
PreferenceUtil.loadPreference(this.context)
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
}
//common\utils\PreferenceUtil.ts
import preferences from '@ohos.data.preferences';
import { CommonConstants } from '../constants/CommonConstants';
import Logger from './Logger';
class PreferenceUtil{
private pref: preferences.Preferences
async loadPreference(context){
try { // 加载preferences
this.pref = await preferences.getPreferences(context, CommonConstants.H_STORE)
Logger.debug(`加载Preferences[${CommonConstants.H_STORE}]成功`)
} catch (e) {
Logger.debug(`加载Preferences[${CommonConstants.H_STORE}]失败`, JSON.stringify(e))
}
}
async putPreferenceValue(key: string, value: preferences.ValueType){
if (!this.pref) {
Logger.debug(`Preferences[${CommonConstants.H_STORE}]尚未初始化!`)
return
}
try {
// 写入数据
await this.pref.put(key, value)
// 刷盘
await this.pref.flush()
Logger.debug(`保存Preferences[${key} = ${value}]成功`)
} catch (e) {
Logger.debug(`保存Preferences[${key} = ${value}]失败`, JSON.stringify(e))
}
}
async getPreferenceValue(key: string, defaultValue: preferences.ValueType){
if (!this.pref) {
Logger.debug(`Preferences[${CommonConstants.H_STORE}]尚未初始化!`)
return
}
try {
// 读数据
let value = await this.pref.get(key, defaultValue)
Logger.debug(`读取Preferences[${key} = ${value}]成功`)
return value
} catch (e) {
Logger.debug(`读取Preferences[${key}]失败`, JSON.stringify(e))
}
}
}
const preferenceUtil = new PreferenceUtil()
export default preferenceUtil as PreferenceUtil
//welcomePage页面中
async aboutToAppear(){
// 1.加载首选项
let isAgree = await PreferenceUtil.getPreferenceValue(PREF_KEY, false)
// 2.判断是否同意
if(isAgree){
// 2.1.同意,跳转首页
this.jumpToIndex()
}else{
// 2.2.不同意,弹窗
this.controller.open()
}
}
jumpToIndex(){
setTimeout(() => {
router.replaceUrl({//替换当前页面
url: 'pages/Index'
})
}, 1000)
}
自定义弹窗
1.使用CustomDialog装饰器声明弹窗组件
2.在页面中声明弹窗控制器,并利用其控制弹窗
2.1.弹窗控制器
2.2.使用自定义弹窗
2.3.利用弹窗控制器控制窗口打开,关闭使用close()
相关代码:
import { CommonConstants } from '../../common/constants/CommonConstants'
@CustomDialog
export default struct UserPrivacyDialog {
controller: CustomDialogController
confirm: () => void
cancel: () => void
build() {
Column({space: CommonConstants.SPACE_10}){
// 1.标题
Text($r('app.string.user_privacy_title'))
.fontSize(20)
.fontWeight(CommonConstants.FONT_WEIGHT_700)
// 2.内容
Text($r('app.string.user_privacy_content'))
// 3.按钮
Button($r('app.string.agree_label'))
.width(150)
.backgroundColor($r('app.color.primary_color'))
.onClick(() => {
this.confirm()
this.controller.close()
})
Button($r('app.string.refuse_label'))
.width(150)
.backgroundColor($r('app.color.lightest_primary_color'))
.fontColor($r('app.color.light_gray'))
.onClick(() => {
this.cancel()
this.controller.close()
})
}
.width('100%')
.padding(10)
}
}
运行效果:
代码如下:
import common from '@ohos.app.ability.common'
import router from '@ohos.router'
import PreferenceUtil from '../common/utils/PreferenceUtil'
import UserPrivacyDialog from '../view/welcome/UserPrivacyDialog'
@Extend(Text) function opacityWhiteText(opacity: number, fontSize: number = 10) {
.fontSize(fontSize)
.opacity(opacity)
.fontColor(Color.White)
}
const PREF_KEY = 'userPrivacyKey'
@Entry
@Component
struct WelcomePage {
context = getContext(this) as common.UIAbilityContext
controller: CustomDialogController = new CustomDialogController({
builder: UserPrivacyDialog({
confirm: () => this.onConfirm(),
cancel: () => this.exitApp()
})
})
async aboutToAppear(){
// 1.加载首选项
let isAgree = await PreferenceUtil.getPreferenceValue(PREF_KEY, false)
// 2.判断是否同意
if(isAgree){
// 2.1.同意,跳转首页
this.jumpToIndex()
}else{
// 2.2.不同意,弹窗
this.controller.open()
}
}
jumpToIndex(){
setTimeout(() => {
router.replaceUrl({
url: 'pages/Index'
})
}, 1000)
}
onConfirm(){
// 1.保存首选项
PreferenceUtil.putPreferenceValue(PREF_KEY, true)
// 2.跳转到首页
this.jumpToIndex()
}
exitApp(){
// 退出APP
this.context.terminateSelf()
}
build() {
Column({ space: 10 }) {
// 1.中央Slogan
Row() {
Image($r('app.media.home_slogan')).width(260)
}
.layoutWeight(1)
// 2.logo
Image($r('app.media.home_logo')).width(150)
// 3.文字描述
Row() {
Text('黑马健康支持').opacityWhiteText(0.8, 12)
Text('IPv6')
.opacityWhiteText(0.8)
.border({ style: BorderStyle.Solid, width: 1, color: Color.White, radius: 15 })
.padding({ left: 5, right: 5 })
Text('网络').opacityWhiteText(0.8, 12)
}
Text(`'减更多'指黑马健康App希望通过软件工具的形式,帮助更多用户实现身材管理`)
.opacityWhiteText(0.6)
Text('浙ICP备0000000号-36D')
.opacityWhiteText(0.4)
.margin({ bottom: 35 })
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.welcome_page_background'))
}
}
三、遇到问题
创建完成用户首选项后不能在预览页面中展示效果,需部署到模拟器运行,在运行时首页展示的并不是欢迎页面而是Index页面中的Hello World。
解决方法:在EntryAbility.ts还需修改windowStage.loadContent,中首先加载页面为welcomePage
四、总结
以上就是本章的主要内容,包括了欢迎页面UI构建,业务逻辑处理,新学习了弹窗组件的使用,以及加载用户首选项时,在EntryAbility.ts中一些细节的修改和处理。