一、项目目标
综合运用本学期所学内容及个人自学知识,使用HarmonyOS 4.0及以上版本开发一款具有实用性和创新 性的移动应用软件。
二、项目介绍
黑马健康是一款功能全面的健康管理应用,它通过提供个性化的饮食记录、健康评估等功能,帮助用户轻松管理健康,改善饮食和生活习惯。无论是需要减肥塑形,还是关注日常营养摄入,黑马健康都能为用户提供定制化的服务,让健康管理变得简单而有效。
欢迎界面
1.
2.
3.
4.
5.
业务逻辑界面
(自定义弹窗)
1.
2.
3.
4.
5.
6.
7.
欢迎界面
1.
2.
3.EntryAbility界面配置用户首选项
4.调用用户首选项
5.点击不同意退出应用,同意进入首页
6.退出应用,再次进入,依旧是进入首页,不会出现弹窗,欢迎页面交换逻辑实现完成
Index界面
1.
2.
3.
遇到的问题及解决方案
WelcomePages界面
1.
exitApp()方法的实现
context = getContext(this) as common.UIAbilityContext exitApp(){ // 退出APP this.context.terminateSelf()//终结 }
2.用户首选项
3.
router.两种方法跳转到首页,使用replaceUrl,不必考虑压栈问题,这个界面出现一次就够了
加一个缓冲1000ms延迟
4.EntryAbility界面
默认跳转界面index修改为WelcomePages
5.index界面点击下面时样式没有变化
解决方法:记录角标index为number类型
@State currentIndex: number = 0
给Tabs加.onchange点击事件记录下当前角标
.onChange(index => this.currentIndex = index)
然后在@Builder渲染颜色
@Builder TabBarBuilder(title: ResourceStr, image: ResourceStr, index: number) { Column({space: CommonConstants.SPACE_8}) { Image(image) .width(22) .fillColor(this.selectColor(index)) Text(title) .fontSize(14) .fontColor(this.selectColor(index)) } }
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 //退出App时用上下文,类型为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'))
}
}//进入页面那一刻,加载首选项,判断是否同意,如果同意把同意状态保存到首选项,然后跳转回首页
//如果不同意退出App
//欢迎页面:首选项和自定义弹窗
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 BreakpointType from '../common/bean/BreanpointType'
import BreakpointConstants from '../common/constants/BreakpointConstants'
import { CommonConstants } from '../common/constants/CommonConstants'
import BreakpointSystem from '../common/utils/BreakpointSystem'
import RecordIndex from '../view/record/RecordIndex'
@Entry
@Component
struct Index {
@State currentIndex: number = 0//记录当前切换到哪一页面
private breakpointSystem: BreakpointSystem = new BreakpointSystem()
@StorageProp('currentBreakpoint') currentBreakpoint:string = BreakpointConstants.BREAKPOINT_SM
@State isPagesShow: boolean = false
onPageShow(){//页面开始显示
this.isPagesShow = true
}
onPageHide(){//页面被隐藏
this.isPagesShow = false
}
@Builder TabBarBuilder(title:ResourceStr,image:ResourceStr,index:number){
Column({space:CommonConstants.SPACE_8}){
Image(image)
.width(22)
.fillColor(this.selectColor(index))//判断当前的角标跟我的角标是否是同一个
Text(title)
.fontSize(12)
.fontColor(this.selectColor(index))
}
}
aboutToAppear(){
this.breakpointSystem.register()//完成回调函数的注册
}
aboutToDisappear(){
this.breakpointSystem.unregister()//取消注册
}
selectColor(index : number){//选择一个颜色
return this.currentIndex === index ? $r('app.color.primary_color') : $r('app.color.gray')
}
build() {
Tabs({barPosition: BreakpointConstants.BAR_POSITION.getValue(this.currentBreakpoint)}) {//位置
TabContent(){
RecordIndex({isPageShow: this.isPagesShow})//封装到RecordIndex组件里
}
.tabBar(this.TabBarBuilder($r('app.string.tab_record'),$r('app.media.ic_calendar'),0))
TabContent(){
Text('我的主页')
}
.tabBar(this.TabBarBuilder($r('app.string.tab_user'),$r('app.media.ic_user_portrait'),1))
TabContent(){
Text('发现页面')
}
.tabBar(this.TabBarBuilder($r('app.string.tab_discover'),$r('app.media.discover'),2))
}
.width('100%')
.height('100%')
.onChange(index => this.currentIndex = index)
.vertical(new BreakpointType({
sm: false,
md: true,
lg: true,
}).getValue(this.currentBreakpoint))
}
}
总结
1.欢迎页面
分析布局:从上到下的列式布局,中央Slogan,logo,三行文字描述,背景色为绿色,宽高比为100%
2.欢迎页面业务
定义一个单独的组件文件里,页面组件放入名为view的目录里,细分为welcome目录,把欢迎页面的组件放入里面,在welcome里定义弹窗UserPrivacyDialog。
弹窗内容:1.标题(加黑加粗)
2.内容
3.按钮(由调用者来实现时间处理逻辑)
本来不可以预览,所以要加入@Preview变为可预览。
首先加载首选项,询问是否同意,如果同意则跳进首页,
如果不同意则继续询问,同意之后则保存首选项,跳入首页;
如果还是不同意则退出app。
自定义弹窗:1.使用@CustomDialog装饰器声明弹窗组件。
2.在页面中声明弹窗控制器,并利用其控制弹窗。
3.首页Tabs
Tabs组件:实现页面内视图内容快速切换,包含TabBar和TabContent两个部分。
Tabs({barPosition: BarPosition.End}){
TabContent(){
Text(‘内容1’)
}
.tabBar(this.TabBuilder(‘标题1’, $r(‘app.media.home’), 0))
TabContent(){
Text(‘内容2’)
}
.tabBar(this.TabBuilder(‘标题2’, $r(‘app.media.user’), 1))
// …
}
.width(‘100%’)
.height(‘100%’)
.vertical(false)
.onChange(index => this.currentIndex = index)
}