本篇主要分析 常用组件与布局 这个案例,这个 Demo 主要是Ark UI的使用,没有太多新的概念,就不再手动复现了。摘一些知识点记录一下。
1 登录页面
1.1 定时器
// src/main/ets/pages/LoginPage.ets
login(): void {
if (this.account === '' || this.password === '') {
prompt.showToast({
message: $r('app.string.input_empty_tips')
})
} else {
this.isShowProgress = true;
if (this.timeOutId === -1) {
this.timeOutId = setTimeout(() => {
this.isShowProgress = false;
this.timeOutId = -1;
router.replaceUrl({ url: 'pages/MainPage' });
}, CommonConstants.LOGIN_DELAY_TIME);
}
}
}
aboutToDisappear() {
clearTimeout(this.timeOutId);
this.timeOutId = -1;
}
这是登录页面实现的功能逻辑,这里首次出现了 setTimeout
这个动作,然后把函数返回值保存到本地变量 timeOutId
,在 aboutToDisappear
的时候调用 clearTimeout
删除定时器。
1.2 @Extend装饰器
@Extend 装饰器用来简化重复的样式代码,效果还是比较显著的,比如Demo里用来装饰登录页面输入框和分割线的函数:
@Extend(TextInput)
function inputStyle() {
.placeholderColor($r('app.color.placeholder_color'))
.height($r('app.float.login_input_height'))
.fontSize($r('app.float.big_text_size'))
.backgroundColor($r('app.color.background'))
.width(CommonConstants.FULL_PARENT)
.padding({ left: CommonConstants.INPUT_PADDING_LEFT })
.margin({ top: $r('app.float.input_margin_top') })
}
@Extend(Line)
function lineStyle() {
.width(CommonConstants.FULL_PARENT)
.height($r('app.float.line_height'))
.backgroundColor($r('app.color.line_color'))
}
以及他们的使用,达到了简化代码的目的:
TextInput({ placeholder: $r('app.string.account') })
.maxLength(CommonConstants.INPUT_ACCOUNT_LENGTH)
.type(InputType.Number)
.inputStyle()
.onChange((value: string) => {
this.account = value;
})
Line().lineStyle()
TextInput({ placeholder: $r('app.string.password') })
.maxLength(CommonConstants.INPUT_PASSWORD_LENGTH)
.type(InputType.Password)
.inputStyle()
.onChange((value: string) => {
this.password = value;
})
Line().lineStyle()
1.3 LoadingProgress组件
LoadingProgress 组件就纯粹是语言糖了。
逆天远程仿真设备,一下午没刷出一个可用的,没看到这个组件的实际效果。
2 主页
2.1 Tab
主页也是有页面跳转的,但不同于登录跳转到主页用的 router
,这里通过 tabs 实现主页面和设置页面的两个不同页面的转换。
@Entry
@Component
struct MainPage {
@State currentIndex: number = CommonConstants.HOME_TAB_INDEX;
private tabsController: TabsController = new TabsController();
@Builder TabBuilder(title: string, index: number, selectedImg: Resource, normalImg: Resource) {
Column() {
Image(this.currentIndex === index ? selectedImg : normalImg)
.width($r('app.float.mainPage_baseTab_size'))
.height($r('app.float.mainPage_baseTab_size'))
Text(title)
.margin({ top: $r('app.float.mainPage_baseTab_top') })
.fontSize($r('app.float.main_tab_fontSize'))
.fontColor(this.currentIndex === index ? $r('app.color.mainPage_selected') : $r('app.color.mainPage_normal'))
}
.justifyContent(FlexAlign.Center)
.height($r('app.float.mainPage_barHeight'))
.width(CommonConstants.FULL_PARENT)
.onClick(() => {
this.currentIndex = index;
this.tabsController.changeIndex(this.currentIndex);
})
}
build() {
Tabs({
barPosition: BarPosition.End,
controller: this.tabsController
}) {
TabContent() {
Home()
}
.padding({ left: $r('app.float.mainPage_padding'), right: $r('app.float.mainPage_padding') })
.backgroundColor($r('app.color.mainPage_backgroundColor'))
.tabBar(this.TabBuilder(CommonConstants.HOME_TITLE, CommonConstants.HOME_TAB_INDEX,
$r('app.media.home_selected'), $r('app.media.home_normal')))
TabContent() {
Setting()
}
.padding({ left: $r('app.float.mainPage_padding'), right: $r('app.float.mainPage_padding') })
.backgroundColor($r('app.color.mainPage_backgroundColor'))
.tabBar(this.TabBuilder(CommonConstants.MINE_TITLE, CommonConstants.MINE_TAB_INDEX,
$r('app.media.mine_selected'), $r('app.media.mine_normal')))
}
.width(CommonConstants.FULL_PARENT)
.backgroundColor(Color.White)
.barHeight($r('app.float.mainPage_barHeight'))
.barMode(BarMode.Fixed)
.onChange((index: number) => {
this.currentIndex = index;
})
}
}
有若干知识点值得分析:
-
实际页面是通过
TabContent
来呈现的,每个页面又有自己对应的tabBar
; -
tabBar
内部调用@Builder
函数来创建了一个列元素;列元素又用选择渲染来对激活状态下的标签进行特殊呈现; -
tabBar
内部对是否激活的判断,实际上是通过父组件的一个@State
变量来维护的,同时每个tabBar
的onClick
方法又会激活不同的页面,并改变父组件的currentIndex
;
2.2 Swiper & Grid
主页第一个 TabContent
是 Home
组件,这个组件最吸引眼球的地方大概是 Swiper 。
轮播组件是很多需求场景里必不可少的东西,极适合在首页展示主题相关的内容。在这里 Swiper
实际上就是个语言糖,遥想当年拿HTML三剑客哼哧哼哧实现的土鳖轮播效果现在居然都已经框架内嵌了……
用法还是比较简单的:
Swiper(this.swiperController) {
ForEach(mainViewModel.getSwiperImages(), (img: Resource) => {
Image(img).borderRadius($r('app.float.home_swiper_borderRadius'))
}, (img: Resource) => JSON.stringify(img.id))
}
.autoPlay(true)
Grid 用于创建网格,此次Demo里的网格属于比较老实的款式,文档当中给出 GridItem.rowStart/rowEnd/columnStart/columnEnd
的示例,展示了网格的灵活性。
Grid() {
ForEach(mainViewModel.getFirstGridData(), (item: ItemData) => {
GridItem() {
Column() {
Image(item.img)
.width($r('app.float.home_homeCell_size'))
.height($r('app.float.home_homeCell_size'))
Text(item.title)
.fontSize($r('app.float.little_text_size'))
.margin({ top: $r('app.float.home_homeCell_margin') })
}
}
}, (item: ItemData) => JSON.stringify(item))
}
.columnsTemplate('1fr 1fr 1fr 1fr')
.rowsTemplate('1fr 1fr')
.columnsGap($r('app.float.home_grid_columnsGap'))
.rowsGap($r('app.float.home_grid_rowGap'))