概要
开发一个软件,首先第一次进入此系统时要出现一个欢迎页面,并且弹窗显示是否同意进入系统,同意后即可进入首页。首页分为三部分,记录页面、发现页面和我的主页。本系统主要涉及首页的记录页面部分。
整体流程
1.预期效果
预期①欢迎页面
预期②弹窗
预期③首页
预期④记录页面顶部搜索栏
2.结构框架
3.具体代码
(1)预期①
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 {
/**
* 预期(1)的具体实现代码,即欢迎页面
*/
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'))
}
}
(2)预期②
import { CommonConstants } from '../../common/constants/CommonConstants'
@CustomDialog
export default struct UserPrivacyDialog {
/**
*用户同意协议弹出,预期(2)具体实现代码
*/
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)
}
}
(3)预期③
//声明一个状态变量
@State currentIndex: number = 0
//定义TabBar
@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))
}
}
//选择颜色
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.isPageShow})//记录页面
}
.tabBar(this.TabBarBuilder($r('app.string.tab_record'), $r('app.media.ic_calendar'), 0))
TabContent() {
Text('发现页面')
}
.tabBar(this.TabBarBuilder($r('app.string.tab_discover'), $r('app.media.discover'), 1))
TabContent() {
Text('我的主页')
}
.tabBar(this.TabBarBuilder($r('app.string.tab_user'), $r('app.media.ic_user_portrait'), 2))
}
.width('100%')
.height('100%')
.onChange(index => this.currentIndex = index)//当前切换的脚标
}
(4)预期④
import { CommonConstants } from '../../common/constants/CommonConstants'
@Component
export default struct SearchHeader {
build() {
Row({space: CommonConstants.SPACE_6}){
Search({placeholder: '搜索饮食或运动信息'})
.textFont({size: 18})
.layoutWeight(1)
Badge({count: 1, position: BadgePosition.RightTop, style: {fontSize: 12}}){
Image($r('app.media.ic_public_email'))
.width(24)
}
}
.width(CommonConstants.THOUSANDTH_940)
}
}
实现步骤
1.欢迎页面需要判断用户是否同意用户隐私协议,同意与否的状态需要将其持久化保存起来,方便下一次打开应用时做出判断,这个持久化我们利用用户首选项保存起来,因此在进入页面的那一刻就需要加载用户首选项,通过这个首选项来判断是否同意,同意则进入首页,否则跳出隐私询问弹窗,再次询问是否同意进入,如果同意则将此状态持久化保存到首选项里,然后跳转首页。此后再打开应用时,无需再询问,就能直接进入首页。
2.记录页面分为三部分,顶部搜索栏,可滑动的·统计卡片,饮食或运动记录列表,因为每一部分的代码内容都很多,所以定义单独的组件在新的页面完成操作。
build() {
Column(){
// 1.头部搜索栏
SearchHeader()
// 2.统计卡片
StatsCard()
// 3.记录列表
RecordList()
.layoutWeight(1)
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.index_page_background'))
}
新的API
1.退出操作
//声明一个context
context = getContext(this) as common.UIAbilityContext
exitApp(){
// 退出APP
this.context.terminateSelf()//调用
}
2.显示未读
在图片的右上角(可选)显示未读信息的数量
Badge({count: 1, position: BadgePosition.RightTop, style: {fontSize: 12}}){
Image($r('app.media.ic_public_email'))
.width(24)
}
技术总结
1.文本
欢迎页面的文本涉及了很多样式,其中圈起来的文字就是套了一个边框
Text('IPv6')
.border({ style: BorderStyle.Solid, width: 1, color: Color.White, radius: 15 })//给文本加边框,边框的样式,宽度,颜色,弧度
.padding({ left: 5, right: 5 })//内边距,避免边框内部太紧凑,设置一个左右的值
2.布局权重
欢迎页面的logo占据很大部分,涉及到布局权重
// 1.中央Slogan
Row() {
Image($r('app.media.home_slogan')).width(260)
}
.layoutWeight(1)//占剩下的所有空间
3.抽取
多次用到的样式抽取出来,优化代码
@Extend(Text) function opacityWhiteText(opacity: number, fontSize: number = 10) {
//因为每次引用的透明度和字体大小都不一样,所有不要写死,引用时传入相应的参数即可
.fontSize(fontSize)
.opacity(opacity)
.fontColor(Color.White)
}//透明的白色字体
4.用户首选项
export default class EntryAbility extends UIAbility {
async onCreate(want, launchParam) {
// 1.加载用户首选项
PreferenceUtil.loadPreference(this.context)
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
}
//页面加载时跳出弹窗
async aboutToAppear(){
// 1.加载首选项
let isAgree = await PreferenceUtil.getPreferenceValue(PREF_KEY, false)
// 2.判断是否同意
if(isAgree){
// 2.1.同意,跳转首页
this.jumpToIndex()
}else{
// 2.2.不同意,弹窗
this.controller.open()
}
}
onConfirm(){
// 1.保存首选项
PreferenceUtil.putPreferenceValue(PREF_KEY, true)
// 2.跳转到首页
this.jumpToIndex()
}
exitApp(){
// 退出APP
this.context.terminateSelf()
}
5.自定义弹窗
自定义弹窗:定义在单独的组件文件里,页面里用到的组件可以放到一个名为view的组件里,代表这个页面的视图组件,dialog下面有一个印章的图标点击,系统会自动补全代码框架。(如何预览组件,加preview。)
控制器CustomDialogController只需声明,不需初始化,使用此组件时才需要初始化,因为每一个弹窗都有自己的控制器。
在欢迎页面声明controller,controller的类型是CustomDialogController,在new的过程中需要传一个对象,bulider属性的值就是刚刚定义的对话框UserPrivacyDialog。
controller: CustomDialogController = new CustomDialogController({
builder: UserPrivacyDialog({
confirm: () => this.onConfirm(),
cancel: () => this.exitApp()
})
})
与自定义组件相似,只不过装饰器不同
自定义组件 自定义弹窗 装饰器 @Component @CustomDialog 必须要有一个成员变量controller,其类型必须是CustomDialogController 结构体 struct NAME{ }
6.Tabs组件
Tabs组件可以实现页面内视图内容的快速切换,包含TabBar和TabContent两个部分。
TabBar的位置可以由属性名决定的。.vertical代表是否水平布局。在Builder里面定义tabBar的样式