前言
今天是1024,祝各位程序员们,钱多事少离家近,不秃也强bug黄。在上一篇文章中,我们了解了deveco studio的主推开发语言arkts
,并写了一个简单的例子,本文我们将学习另外一个例子来加深我们对于鸿蒙应用开发的理解。
正文
本文的例子同样来源于harmonyos学堂,根据源码内容我们来反推开发过程,看开发过程中能学到那些知识点。
一、创建工程
首先我们在deveco studio中创建一个名为mycenter的功能,如下图所示:
点击finish
创建项目,项目创建好之后,通过预览就能看到hello world
,下面来说明一下这次开发的个人中心app包含的内容,首先是一个登录页面,登录进去之后可以通过底部导航切换页面内容,分别是首页和个人的内容,下面我们首先来写登录页面。
二、登录
在创建工程时会自带一个页面,就是我们所看到的index.ets,那么现在我们需要创建一个登录页面,鼠标右键pages
→new
→page
。
会出现一个弹窗,我们输入页面的名称为login。
点击finish完成页面的创建,创建完成之后你可以在resource/main/base/profile/main_pages.json
中看到我们增加的登录页面的配置。
① 更换启动页面
当前有两个页面,index和login,因为我们一打开app就进入login页面,所以我们将两个内容的位置换一下,代码如下所示:
{
"src": [
"pages/login",
"pages/index"
]
}
这样当app运行的时候第一个页面就是login了,是不是很简单,下面我们要做的就是写这个登录页面的ui和功能,先来看看这个页面的ui效果
根据这个ui,我们可以得出,页面整体效果为纵向布局,中间两处文字为蓝色文字和底部三个登录方式为横向,为了方便使用,首先我们增加文字的颜色和图标,首先修改resources/base/element/color.json
中的代码如下所示:
{
"color": [
{
"name": "start_window_background",
"value": "#ffffff"
},
{
"name": "white",
"value": "#ffffff"
},
{
"name": "background",
"value": "#f1f3f5"
},
{
"name": "title_text_color",
"value": "#182431"
},
{
"name": "login_more_text_color",
"value": "#99182431"
},
{
"name": "placeholder_color",
"value": "#99182431"
},
{
"name": "line_color",
"value": "#33182431"
},
{
"name": "login_button_color",
"value": "#007dff"
},
{
"name": "login_blue_text_color",
"value": "#007dff"
},
{
"name": "other_login_text_color",
"value": "#838d97"
},
{
"name": "loading_color",
"value": "#182431"
},
{
"name": "mainpage_selected",
"value": "#1698ce"
},
{
"name": "mainpage_normal",
"value": "#6b6b6b"
},
{
"name": "mainpage_backgroundcolor",
"value": "#f1f3f5"
},
{
"name": "home_grid_fontcolor",
"value": "#99182431"
},
{
"name": "setting_button_backgroundcolor",
"value": "#e5e8ea"
},
{
"name": "setting_button_fontcolor",
"value": "#fa2a2d"
}
]
}
颜色弄好了,然后将源码中resources/base/element/meida
下的图标复制到你的项目中即可。
下面我们可以开始写登录页面了,首先我们修改build()
函数中的内容,定义一个纵向布局,然后设置颜色,内容大小和内容填充,代码如下所示:
@entry
@component
struct login {
build() {
// 页面纵向布局
column() {
.backgroundcolor($r('app.color.background'))
.height('100%')
.width('100%')
.padding({
left: 12,
right: 12,
bottom: 24
})
}
}
此时保存一下预览效果是一片空白,下面我们在column()
中增加一个图标和两段文字,代码如下所示:
//logo
image($r('app.media.logo'))
.width(78)
.height(78)
.margin({ top: 100, bottom: 16 })
text('登录')
.fontsize(24)
.fontweight(fontweight.medium)
.fontcolor($r('app.color.title_text_color'))
text('登录帐号以使用更多服务')
.fontsize(16)
.fontcolor($r('app.color.login_more_text_color'))
.margin({ top: 16, bottom: 30 })
然后再预览一下效果如图所示:
下面我们来写账号和密码的输入框,添加如下所示代码:
//账号输入框
textinput({ placeholder: '账号' })
.maxlength(11)
.type(inputtype.number)
.placeholdercolor($r('app.color.placeholder_color'))
.height(45)
.fontsize(18)
.backgroundcolor($r('app.color.background'))
.width('100%')
.padding({ left: 0 })
.margin({ top: 12 })
.onchange((value: string) => {
//获取输入的内容
})
//下划线
line().width('100%')
.height(2)
.backgroundcolor($r('app.color.line_color'))
//密码输入框
textinput({ placeholder: '密码' })
.maxlength(8)
.type(inputtype.password)
.placeholdercolor($r('app.color.placeholder_color'))
.height(45)
.fontsize(18)
.backgroundcolor($r('app.color.background'))
.width('100%')
.padding({ left: 0 })
.margin({ top: 12 })
.onchange((value: string) => {
//获取输入的内容
})
//下划线
line().width('100%')
.height(2)
.backgroundcolor($r('app.color.line_color'))
我们首先预览一下:
接下来我们说一下可以说明的点,首先就是textinput的输入类型,如果为password,则会自带一个按钮,点击可以查看密码的明文内容,这一点我还是很喜欢的,不用自己写了,同时我们看到还有一个onchange()
,里面会实时同步你输入的内容,因此如果我们想要获取输入框的内容,就需要定义变量来接收,可以在login中定义变量,代码如下所示:
@state account: string = '';
@state password: string = '';
这是账号和密码的输入值,依次为内容赋值,将账号输入框下的内容赋值给account
, 密码输入框下的内容赋值给password
,代码如下所示:
//账号输入框
textinput({ placeholder: '账号' })
...
.onchange((value: string) => {
//获取输入的内容
this.account = value;
})
...
//密码输入框
textinput({ placeholder: '密码' })
...
.onchange((value: string) => {
//获取输入的内容
this.password = value;
})
...
② 拓展修饰符
下面我们介绍一下拓展修饰符,首先我们看一下刚才所写的代码,如下图所示:
可以看到我标注的部分代码一致,但是确实是输入框和线所需要的设置,那么我们可以将重复的内容样式通过拓展修饰符封装一个样式函数,供同类样式使用,下面我们在login{}
外面增加一个inputstyle()
函数,代码如下所示:
/**
* 输入框的通用样式
*/
@extend(textinput) function inputstyle() {
.placeholdercolor($r('app.color.placeholder_color'))
.height(45)
.fontsize(18)
.backgroundcolor($r('app.color.background'))
.width('100%')
.padding({ left: 0 })
.margin({ top: 12 })
}
这里我们用到@extend
修饰符,它里面是所修饰的类型,这里输入的是textinput
组件,里面的代码就是刚才的标注的代码,挪过来而已,然后我们再写一个linestyle()
函数,代码如下所示:
@extend(line) function linestyle() {
.width('100%')
.height(2)
.backgroundcolor($r('app.color.line_color'))
}
注意这两个函数添加的位置,如下图所示:
如果你写在组件内部会报错的,然后我们再更新一下刚才的输入框和线的代码,如下图所示:
相比上面的原始写法就简洁很多了,减少重复的代码,因为输入框下方的两个蓝色文字也是一样的样式,所以再增加一个扩展样式,代码如下所示:
@extend(text) function bluetextstyle() {
.fontcolor($r('app.color.login_blue_text_color'))
.fontsize(14)
.fontweight(fontweight.medium)
}
在下划线后面增加代码,如下所示:
row() {
text('短信验证码登录').bluetextstyle()
text('忘记密码').bluetextstyle()
}
.justifycontent(flexalign.spacebetween)
.width('100%')
.margin({ top: 8 })
通过一个横向布局,装载两个text,通过justifycontent(flexalign.spacebetween)
设置布局中的内容,左右都靠边,看一下预览效果:
下面我们写登录按钮和注册的ui,在上述代码后面继续增加代码,如下所示:
//登录按钮
button('登录', { type: buttontype.capsule })
.width('90%')
.height(40)
.fontsize(16)
.fontweight(fontweight.medium)
.backgroundcolor($r('app.color.login_button_color'))
.margin({ top: 80, bottom: 12 })
.onclick(() => {
// 登录
})
text('注册账号')
.fontcolor($r('app.color.login_blue_text_color'))
.fontsize(16)
.fontweight(fontweight.medium)
这里的代码其实就没有什么好解释的,一目了然,重点是点击登录按钮之后在onclick()
函数中执行的代码。
③ 页面跳转
下面我们在login{}
里面增加一个登录的函数,代码如下所示:
/**
* 登录
*/
login(): void {
if (this.account === '' || this.password === '') {
//显示toast
promptaction.showtoast({ message: '账号或密码为空' })
return
}
router.replaceurl({ url: 'pages/index' });
}
这里的代码用到了promptaction
和router
,两个都是鸿蒙内部的插件,你输入后如果发现有红色下划波浪线,那么就需要导包,鼠标放在波浪线上,使用快捷键,alt + enter会出现一个弹窗,
选择第一项就会将所需要的插件导入到当前的组件中,导入后就不会报错了,导入内容如下图所示:
然后在登录按钮的点击事件中调用登录函数,如下图所示:
重新预览一下,点击登录按钮试试看,如下图所示:
随便输入账号和密码再点击登录,就会跳转到index页面,你可以试试看呀,不过我们当前登录页面的内容其实还没有写完,因为有一些细节还需要处理,首先要做的就是页面完整,在注册账号的text后面再添加如下代码:
//空白填充组件,具有自动填充容器空余部分的能力。仅当父组件为row/column时生效。
blank()
text('其他登录方式')
.fontcolor($r('app.color.other_login_text_color'))
.fontsize(12)
.fontweight(fontweight.medium)
.margin({ top: 48, bottom: 12 })
row({ space: 44 }) {
this.imagebutton($r('app.media.login_method1'))
this.imagebutton($r('app.media.login_method2'))
this.imagebutton($r('app.media.login_method3'))
}
这里因为3个按钮样式一致,只是图片资源不同,因此我们可以通过@builder
修饰器构建一个按钮,在login{}
中添加如下代码:
/**
* 其他登录方式按钮
* @param src
*/
@builder
imagebutton(src: resource) {
button({ type: buttontype.circle, stateeffect: true }) {
image(src)
}
.height(48)
.width(48)
.backgroundcolor($r('app.color.background'))
}
预览效果如下图所示:
④ 等待进度条
通常来说登录不会是一下子就成功的,涉及到登录之后的一些数据处理,网络延时,通常我们会在点击登录之后显示一个等待进度,告诉用户正在登录中,稍安勿躁,因为如果你登陆的时候没有什么变化,然后登录有很久,用户会以为你的app卡死了,直接就给你卸载。
那我们同样可以定义几个变量,在login{}
中添加如下代码:
//是否显示加载条
@state isshowprogress: boolean = false;
//超时标识
private timeoutid: number = -1;
我们通过这个isshowprogress
变量来设置是否显示加载进度,这个timeoutid
用来做超时处理,比如登录多久可以进入主页面,简单模拟一下,首先要做的就是修改这个isshowprogress
变量为true
,修改login()
函数,代码如下所示:
login(): void {
if (this.account === '' || this.password === '') {
//显示toast
promptaction.showtoast({ message: '账号或密码为空' })
return
}
//内容不为空则显示加载进度条
this.isshowprogress = true;
if (this.timeoutid === -1) {
//设置超时处理,两秒后执行页面跳转到主页
this.timeoutid = settimeout(() => {
this.isshowprogress = false;
this.timeoutid = -1;
router.replaceurl({ url: 'pages/index' });
}, 2000);
}
}
这里我们将isshowprogress
赋值为true
,那么就会触发ui刷新,因此我们需要在build()函数中,合适的位置判断这个isshowprogress
值,注意这个加载进度不是一个弹窗,它会占用页面的空间,所以还记得我们之前所写的那个blank()
组件吗?我们在它的上面添加如下代码:
//是否显示等待进度条
if (this.isshowprogress) {
loadingprogress()
.color($r('app.color.loading_color'))
.width(30)
.height(30)
.margin({ top: 20 })
}
添加位置如下图所示:
这里通过判断isshowprogress
来显示等待进度条,那么就还有取消显示的地方,我们再回到login()
函数,观察下面这段代码:
if (this.timeoutid === -1) {
//设置超时处理,两秒后执行页面跳转到主页
this.timeoutid = settimeout(() => {
this.isshowprogress = false;
this.timeoutid = -1;
router.replaceurl({ url: 'pages/index' });
}, 2000);
}
首先判断这个参数是否为-1,是的话则增加一个2秒超时处理,在超时结束时我们将isshowprogress
设置为false,那么刷新ui的时候就会去掉之前所显示的加载进度条,同时将timeoutid
再设置为-1,最后跳转页面。我们还可以增加一个生命周期的处理,在login{}
中增加如下代码:
/**
* 组件的生命周期,组件销毁时执行
*/
abouttodisappear() {
cleartimeout(this.timeoutid);
this.timeoutid = -1;
}
因为当你跳转页面时,当前的组件就会销毁,就会触发这个生命周期函数,所以我们在这里进行销毁超时处理,下面我们来看一下运行的效果。
三、导航栏
登录后我们进入index页面,也就是主页面,我们先看看主页面的内容
通过这两张图,我们可以看到,主页面有两部分,选项卡和选项卡内容,通过底部选项卡点击进行切换,那么在写这个页面的时候应该怎么入手呢?首先我们应该先写选项卡,也就是底部导航这一部分内容。
下面我们修改一下index.ets
中的代码,如下所示:
@entry
@component
struct index {
@state currentindex: number = 0
private tabscontroller: tabscontroller = new tabscontroller()
@builder tabbuilder(title: string, index: number, selectedimg: resource, normalimg: resource) {
column() {
image(this.currentindex === index ? selectedimg : normalimg)
.width(24)
.height(24)
text(title)
.margin({ top: 4 })
.fontsize(10)
.fontcolor(this.currentindex === index ? $r('app.color.mainpage_selected') : $r('app.color.mainpage_normal'))
}
.justifycontent(flexalign.center)
.height(26)
.width('100%')
.onclick(() => {
this.currentindex = index
this.tabscontroller.changeindex(this.currentindex)
})
}
build() {
tabs({
barposition: barposition.end,
controller: this.tabscontroller
}) {
tabcontent() {
// 首页内容
}
.padding({ left: 12, right: 12 })
.backgroundcolor($r('app.color.mainpage_backgroundcolor'))
.tabbar(this.tabbuilder('首页', 0, $r('app.media.home_selected'), $r('app.media.home_normal')))
tabcontent() {
// 我的内容
}
.padding({ left: 12, right: 12 })
.backgroundcolor($r('app.color.mainpage_backgroundcolor'))
.tabbar(this.tabbuilder('我的', 1, $r('app.media.mine_selected'), $r('app.media.mine_normal')))
}
.width('100%')
.backgroundcolor(color.white)
.barheight(56)
.barmode(barmode.fixed)
.onchange((index: number) => {
this.currentindex = index
})
}
}
下面我们来分析一下这段代码,首先我们定义了currentindex
变量,用于记录当前选项卡的下标,然后定义了一个tabscontroller
,用于进行选项卡的控制,接下来使用@builder
装饰器来构建tab的内容,使用纵向布局将图标和文字居中摆放,根据currentindex
和当前index
的判断来进行tab的选中、未选中状态。currentindex
默认为0,则是默认选中第一个tab,也就是首页tab,在tab的点击事件中,我们更新currentindex
的值,然后再使用this.tabscontroller.changeindex(this.currentindex)
进行切换tab选项。
然后来看build()
函数中的代码,这里我们使用了tabs()
组件,通过页签进行内容视图切换的容器组件,每个页签对应一个内容视图。我们看里面传的参数,这里重点是第一个参数,这个的barposition不是下标的意思,而是设置tabs的页签位置。默认值:barposition.start
,这里的默认值实际上还要结合tabs
组件的vertical
属性来结合使用。
vertical
设置为false是为横向tabs,设置为true时为纵向tabs。默认值:false,我们没有在代码中设置这个属性,所以默认就是纵向的,那么我们再结合这个barposition
的值来看:
start
,vertical属性方法设置为true时,页签位于容器左侧;vertical属性方法设置为false时,页签位于容器顶部。end
,vertical属性方法设置为true时,页签位于容器右侧;vertical属性方法设置为false时,页签位于容器底部。
那么现在就是tabs就是在屏幕底部,tabs可以在屏幕上下左右进行摆放。
在tabs()
中放置了两个tabcontent()
,tabcontent,仅在tabs中使用,对应一个切换页签的内容视图,这个内容视图我们后面来写,这个组件有一个tabbar()
属性,用于装载tab内容,这里就用到我们之前所构建的tabbuilder()
函数。
最后我们再了解一下tabs()
组件的其它两个属性:
barmode
有两个属性,1. scrollable:每一个tabbar均使用实际布局宽度,超过总长度(横向tabs的barwidth,纵向tabs的barheight)后可滑动。2. fixed:所有tabbar平均分配barwidth宽度(纵向时平均分配barheight高度)。onchange
,tab页签切换后触发的事件。index
:当前显示的index索引,索引从0开始计算。触发该事件的条件:1、tabcontent支持滑动时,组件触发滑动时触发。2、通过控制器api接口调用。3、通过状态变量构造的属性值进行修改。4、通过页签处点击触发。
通过这些说明,相信你已经知道tabs()
的用法了,下面我们保存预览一下index,默认是home,点击mine,如下图所示:
四、首页
在写这个首页的内容之前,我们先看一下整个页面的布局,如图
首页内容呈纵向摆放,同时需要考虑屏幕大小,因此我们可以加一个滑动控件,再看里面的内容,首先是一个标题,标题下面是轮播图,然后是两个网格列表。这样页面内容就介绍完了,那么我么应该怎么来写这个页面的内容呢?
① 轮播图
首先我们完成标题和轮播图,在ets
下创建一个viewmodel
包,该包下创建一个indexviewmodel.ets
文件,代码如下所示:
export class indexviewmodel {
/**
* 获取轮播图数据
*/
getswiperimages(): array<resource> {
let swiperimages: resource[] = [
$r('app.media.fig1'),
$r('app.media.fig2'),
$r('app.media.fig3'),
$r('app.media.fig4')
]
return swiperimages
}
}
export default new indexviewmodel()
通过这个getswiperimages()
来获取轮播图数据,下面我们可以构建主页面的组件了,在在ets
下创建一个view
包,包下新建一个home.ets文件,里面代码如下所示:
import mainviewmodel from '../viewmodel/indexviewmodel';
/**
* 首页
*/
@component
export default struct home {
private swipercontroller: swipercontroller = new swipercontroller();
build() {
scroll() {
column({ space: 12 }) {
//首页
column() {
text('首页')
.fontweight(fontweight.medium)
.fontsize(24)
.margin({ top: 12 })
.padding({ left: 12 })
}
.width('100%')
.alignitems(horizontalalign.start)
//轮播图
swiper(this.swipercontroller) {
foreach(mainviewmodel.getswiperimages(), (img: resource) => {
image(img).borderradius(16)
}, (img: resource) => json.stringify(img.id))
}
.margin({ top: 24 })
.autoplay(true)
}
}
.height('100%')
}
}
这里的代码就是一个按照我们上面所说的思路来设计的,滚动条里面有标题和轮播图,并设置轮播图自动轮播,在滚动组件中内容未填满页面高度的情况下,内容就会居中显示,我们将home放在index中,如下图所示:
然后我们预览index,看看预览效果图:
② 网格列表
下面我们再来写网格列表,首先要做的就是制造一些数据,先创建一个数据bean,在ets
下创建一个bean
包,该包下创建一个itemdata.ets
文件,代码如下所示:
export default class itemdata {
title: resource|string;
img: resource;
others?: resource|string;
constructor(title: resource|string, img: resource, others?: resource|string) {
this.title = title;
this.img = img;
this.others = others;
}
}
这个bean中只有三个数据,标题、图片,其他。下面我们在indexviewmodel
中制造一些假数据,写两个函数,代码如下所示:
/**
* 获取第一个网格数据
*/
getfirstgriddata(): array<itemdata> {
let firstgriddata: itemdata[] = [
new itemdata('我的最爱', $r('app.media.love')),
new itemdata('历史记录', $r('app.media.record')),
new itemdata('消息', $r('app.media.message')),
new itemdata('购物车', $r('app.media.shopping')),
new itemdata('我的目标', $r('app.media.target')),
new itemdata('圈子', $r('app.media.circle')),
new itemdata('收藏', $r('app.media.favorite')),
new itemdata('回收站', $r('app.media.recycle'))
]
return firstgriddata
}
/**
* 获取第二个网格数据
*/
getsecondgriddata(): array<itemdata> {
let secondgriddata: itemdata[] = [
new itemdata('排行榜', $r('app.media.top'), '当前热品尽在掌握'),
new itemdata('新品首发', $r('app.media.new'), '最新潮牌,马上发布'),
new itemdata('大牌闪购', $r('app.media.brand'), '更多大牌敬请期待'),
new itemdata('发现好物', $r('app.media.found'), '更多内容等您探索')
]
return secondgriddata
}
这里我们需要导入itemdata,还记得是怎么导入的吗?因为创建others?: resource|string;
的时候,使用了一个?
,表示可以为空,下面我们在home中增加这两个网格的ui展示,代码如下所示:
import mainviewmodel from '../viewmodel/indexviewmodel';
import itemdata from '../bean/itemdata';
/**
* 首页
*/
@component
export default struct home {
private swipercontroller: swipercontroller = new swipercontroller();
build() {
scroll() {
column({ space: 12 }) {
//首页
...
//轮播图
...
//第一个网格布局
grid() {
foreach(mainviewmodel.getfirstgriddata(), (item: itemdata) => {
griditem() {
column() {
image(item.img)
.width(24)
.height(24)
text(item.title)
.fontsize(12)
.margin({ top: 4 })
}
}
}, (item: itemdata) => json.stringify(item))
}
.columnstemplate('1fr 1fr 1fr 1fr')
.rowstemplate('1fr 1fr')
.columnsgap(8)
.rowsgap(12)
.padding({ top: 12, bottom: 12 })
.height(124)
.backgroundcolor(color.white)
.borderradius(24)
text('列表')
.fontsize(16)
.fontweight(fontweight.medium)
.width('100%')
.margin({ top: 12 })
//第二个网格布局
grid() {
foreach(mainviewmodel.getsecondgriddata(), (seconditem: itemdata) => {
griditem() {
column() {
text(seconditem.title)
.fontsize(16)
.fontweight(fontweight.medium)
text(seconditem.others)
.margin({ top: 4 })
.fontsize(12)
.fontcolor($r('app.color.home_grid_fontcolor'))
}
.alignitems(horizontalalign.start)
}
.padding({ top: 8, left: 8 })
.borderradius(12)
.align(alignment.topstart)
.backgroundimage(seconditem.img)
.backgroundimagesize(imagesize.cover)
.width('100%')
.height('100%')
}, (seconditem: itemdata) => json.stringify(seconditem))
}
.width('100%')
.height(260)
.columnstemplate('1fr 1fr')
.rowstemplate('1fr 1fr')
.columnsgap(8)
.rowsgap(12)
.margin({ bottom: 55 })
}
}
.height('100%')
}
}
这里注意一下我将之前写过的一些代码省略了,所以这里你就不要复制粘贴了,其实网格列表和普通列表在数据渲染的方式上一样,只不过网格列表有一些其他的属性,我们需要了解。
columnstemplate
:string类型,设置当前网格布局列的数量,不设置时默认1列。例如, ‘1fr 1fr 1fr 1fr’ 是将父组件分4列,将父组件允许的宽分为4等份,第一列占1份,第二列占1份,第三列占1份,第四列占1份。设置为’0fr’时,该列的列宽为0,不显示griditem。设置为其他非法值时,griditem显示为固定1列。rowstemplate
:string类型,设置当前网格布局行的数量,不设置时默认1行。例如,‘1fr 1fr’是将父组件分两行,将父组件允许的高分为2等份,第一行占1份,第二行占1份,设置为’0fr’,则这一行的行宽为0,这一行griditem不显示。设置为其他非法值,按固定1行处理。columnsgap
:length类型,设置列与列的间距。默认值:0,设置为小于0的值时,按默认值显示。rowsgap
:length类型,设置行与行的间距。默认值:0,设置为小于0的值时,按默认值显示。
其余的属性就没有什么好说的,下面我们再预览一下index,如下图所示:
此时你点击我的,可以看到什么也没有,下面我们来写我的。
五、我的
首先我们看一下我的页面的图
内容同样是呈纵向摆放的,上面是个人信息,中间这里是一个功能列表,最下面是退出按钮,下面我们首先提供列表的数据,在indexviewmodel
中写一个函数,代码如下所示:
/**
* 获取设置列表数据
*/
getsettinglistdata(): array<itemdata> {
let settinglistdata: itemdata[] = [
new itemdata('推送通知', $r('app.media.news'), '开关'),
new itemdata('数据管理', $r('app.media.data'), null),
new itemdata('菜单设置', $r('app.media.menu'), null),
new itemdata('关于', $r('app.media.about'), null),
new itemdata('清除缓存', $r('app.media.storage'), null),
new itemdata('隐私协议', $r('app.media.privacy'), null)
]
return settinglistdata
}
然后我们在view包下先建一个mine.ets
,代码如下所示:
import router from '@ohos.router';
import promptaction from '@ohos.promptaction';
import itemdata from '../bean/itemdata';
import mainviewmodel from '../viewmodel/indexviewmodel';
/**
* 我的
*/
@component
export default struct mine {
@builder settingcell(item: itemdata) {
row() {
row({ space: 12 }) {
image(item.img)
.width(22)
.height(22)
text(item.title)
.fontsize(16)
}
// 设置功能item最右侧的功能项
if (item.others === null) {
//可以进入下一级页面
image($r('app.media.right_grey'))
.width(12)
.height(24)
} else {
//开关
toggle({ type: toggletype.switch, ison: false })
}
}
.justifycontent(flexalign.spacebetween)
.width('100%')
.padding({
left: 8,
right: 22
})
}
build() {
scroll() {
column({ space: 12 }) {
column() {
text('我的')
.fontweight(fontweight.medium)
.fontsize(24)
.margin({ top: 12 })
.padding({ left: 12 })
}
.width('100%')
.alignitems(horizontalalign.start)
// 个人信息
row() {
image($r('app.media.account'))
.width(48)
.height(48)
column() {
text('李先生')
.fontsize(20)
text('lonelyxxx@qq.com')
.fontsize(12)
.margin({ top: 4 })
}
.alignitems(horizontalalign.start)
.margin({ left: 24 })
}
.margin({ top: 24 })
.alignitems(verticalalign.center)
.width('100%')
.height(96)
.backgroundcolor(color.white)
.padding({ left: 24 })
.borderradius(16)
// 功能列表
list() {
foreach(mainviewmodel.getsettinglistdata(), (item: itemdata) => {
listitem() {
//构建每一个item
this.settingcell(item)
}
.height(48)
}, (item: itemdata) => json.stringify(item))
}
.backgroundcolor(color.white)
.width('100%')
.height('42%')
// 为列表增加分隔线
.divider({
strokewidth: 1,
color: color.grey,
startmargin: 42,
endmargin: 42
})
.borderradius(16)
.padding({ top: 4, bottom: 4 })
blank()
button('退出登录', { type: buttontype.capsule })
.width('90%')
.height(40)
.fontsize(16)
.fontcolor($r('app.color.setting_button_fontcolor'))
.fontweight(fontweight.medium)
.backgroundcolor($r('app.color.setting_button_backgroundcolor'))
.margin({ bottom: 55 })
.onclick(() => {
promptaction.showtoast({ message: '退出登录' })
router.replaceurl({ url: 'pages/login' })
})
}
.height('100%')
}
}
}
这个代码乍一看很多,下面我们来分析一下,从上往下来,首先是标题和个人信息,这部分就是ui效果,没有什么好说的,然后最关键的功能列表,这里通过@builder
来装饰settingcell()
函数。通过item的other来判断是否需要显示不同的效果,代码如下所示:
if (item.others === null) {
//可以进入下一级页面
image($r('app.media.right_grey'))
.width(12)
.height(24)
} else {
//开关
toggle({ type: toggletype.switch, ison: false })
}
为null就是一个向右的图标,不为null就是一个开关,默认为false。中间的列表加载就没有什么好说二的,最后的退出登录按钮点击之后就会调用router.replaceurl({ url: 'pages/login' })
,返回到登录页面,这里使用的是replaceurl
,用应用内的某个页面替换当前页面,并销毁被替换的页面。
下面我们通过index预览一下看看效果:
① 带参数跳转
现在我们登录后的账号并没有其他作用,我们可以把账号替换为李先生,首先我们需要修改登录按钮点击事件的代码,如下所示:
router.replaceurl({
url: 'pages/index',
params: {
account: this.account
}
});
就是在跳转页面的时候添加一个params属性,然后放入键和值,然后我们在mine组件中增加一行代码:
//接收传递过来的参数
@state account: string = router.getparams()?.['account'];
这样就能拿到传递的参数值,然后设置到text中即可。
下面运行一下看看效果
本文就到这里了,鸿蒙提供的一些学习资料是很全面的,通过阅读加上实操过程中的测试可以很快上手应用开发。
六、源码
如果对你有所帮助的话,不妨 star 或 fork,山高水长,后会有期~
源码地址:mycenter
发表评论