HarmonyOS快速上手

近一段时间HarmonyOS热度高涨,也跟风看一下文档,做一下HarmonyOS快速上手的内容记录。

目录结构

在不使用云开发的情况下,基本目录结构如下:

  • AppScope - 存放应用全局所需要的资源文件
  • entry - 应用主模块,存放代码、资源等
    • main - 总文件夹
      • ets - 存放ets代码
        • entryability - 存放ability文件,用于应用逻辑和生命周期管理
        • pages - 存放UI界面相关代码文件
      • resources - 存放多媒体和布局文件
      • module.json5 - 模块配置文件
    • ohosTest - 单元测试目录
    • build-profile.json5 - 模块级配置信息,包括编译、构建配置项
    • hvigorfile.ts - 模块级构建脚本
    • oh-package.json5 - 模块级依赖配置文件
  • oh_modules - 工程依赖
  • build-profile.json5 - 工程级配置信息,包括签名、产品配置等
  • hvigorfile.ts - 工程级编译构建任务脚本
  • oh-package.json5 - 工程级依赖配置文件

需要描述UI的界面使用.ets文件

其他的普通文件如ability, 云服务等使用.ts文件。

组件与装饰器

组件分类

  • 基础组件
  • 容器组件
  • 媒体组件
  • 绘制组件
  • 画布组件

比如Text, Image, Button等就是基础组件,我们在构建页面的时候,最外层要使用布局组件来进行包裹,常用的布局组件有Row, Column, List, Grid

组件都是通过属性和方法来构建其样式和绑定事件的,布局组件和包含子组件的组件,使用{}包裹起内部组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Component // 构建组件的装饰器,先不关注,只看内容
struct Index {
build() {
// 布局组件
Row({
space: 16, // 属性
}) {
// 基础组件不包含子组件,不需要使用{}
Text('文本内容')
.fontSize(28)
.fontWeight(FontWeight.Bold)
Button('登录')
.width('90%')
.height(40)
.onClick(() => {
// 绑定事件
console.log('click handler')
})
}
.padding({
top: 40,
left: 20,
right: 20,
})
.width('100%')
.height('100%')
}
}

Row, Column就是用来构建Flex布局,比较简单,不过多赘述了,简单看一下List, Grid组件吧。

List一般与子组件ListItem配合使用,实现垂直/水平方向线性排列

List是很常用的滚动类容器组件,一般和子组件ListItem一起使用,List列表中的每一个列表项对应一个ListItem组件。

1
2
3
4
List(value?: {space?: number | string, initialIndex?: number, scroller?: Scroller})
space - 列表项间距
initialIndex - 初次加载起始位置显示的item
scroller控制器 - 控制List滚动

滚动事件监听:

  • onScroll - 滑动时触发,返回值scrollOffset为滑动偏移量,scrollState为当前滑动状态
  • onScrollIndex - 滑动时触发,返回值为滑动起始位置索引和滑动结束位置索引
  • onReachStart - 到达起始位置时触发
  • onReachEnd - 到达末尾时触发
  • onScrollStop - 滑动停止时触发

Grid组件为网格容器,是一种网格列表,由“行”和“列”分割的单元格所组成,通过指定“项目”所在的单元格做出各种各样的布局。Grid组件一般和子组件GridItem一起使用,Grid列表中的每一个条目对应一个GridItem组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Grid() {
ForEach(数组, (item: string) => {
GridItem() {
Text(item)
.fontSize(16)
.fontColor(Color.White)
.backgroundColor(0x007DFF)
.width('100%')
.height('100%')
.textAlign(TextAlign.Center)
}
}, item => item)
}
.columnsTemplate('1fr 1fr 1fr 1fr') // 列
.rowsTemplate('1fr 1fr 1fr 1fr') // 行
.columnsGap(10) // 列间距
.rowsGap(10) // 行间距
.height(300) // grid高度

构建的网格布局如果使用了固定的行数和列数,那么构建出的网格是不可滚动的。然而有时候因为内容较多,我们通过滚动的方式来显示更多的内容,就需要一个可以滚动的网格布局。我们只需要设置rowsTemplate和columnsTemplate中的一个即可(要指定固定的高度或者宽度)。

装饰器

常用的装饰器有如下几个:

  • @Component - 自定义组件,必须实现build方法
  • @Entry - 组件作为页面的默认入口组件
  • @State - 装饰组件内部的状态数据,变化后自动触发更新
  • @Prop - 装饰的变量必须使用父组件的@State进行初始化,组件内部修改后不会通知父组件,是单向数据绑定
  • @Link - 装饰的变量可以和父组件的@State变量建立双向绑定,并且需要在父组件中进行初始化
  • @Builder - 装饰的方法用于定义组件的声明式UI描述。
  • @Provider/@Consume

组件状态

在组件范围传递的状态管理常见的场景如下:

场景 装饰器
组件内的状态管理 @State
从父组件单向同步状态 @Prop
与父组件双向同步状态 @Link
跨组件层级双向同步状态 @Provide, @Consume

如果需要观察嵌套类对象属性变化,需要使用@Observed@ObjectLink装饰器,因为上述表格中的装饰器只能观察到对象的第一层属性变化。

当状态改变,需要对状态变化进行监听做一些相应的操作时,可以使用@Watch装饰器来修饰状态

父子组件通信

前边说到的@Prop装饰器可以接收父组件传递的@State装饰的数据,但是有些时候没这么复杂,我们只是想在子组件接收父组件传递的数据进行渲染,可以直接定义属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 子组件
@Component
struct TodoItem {
private content: string;
build() {
Row() {
Text(this.content)
}
}
}

// 父组件
TodoItem({
content: 'abc'
})

生命周期

自定义组件

对于@Component定义的自定义组件生命周期为:

  • 组件创建
  • aboutToAppear() - 创建组件之后,执行build之前 - 可以做一些数据初始化的工作
  • aboutToDisappear() - 释放资源,清除副作用
  • 组件销毁
1
2
3
4
5
6
@Component
struct MyComponent {
aboutToAppear() {}

aboutToDisappear() {}
}

入口组件

对于使用@Entry修饰的页面入口组件还有额外的三个生命周期函数:

  • 组件创建
  • aboutToAppear()
  • onPageShow() - 页面显示
  • onBackPress() - 返回
  • onPageHide() - 页面消失
  • aboutToDisappear()
  • 组件销毁
1
2
3
4
5
6
7
8
9
@Entry
@Component
struct MyComponent {
onPageShow() {}

onPageHide() {}

onBackPress() {}
}

条件/循环渲染

使用if / else if / else进行条件渲染:

1
2
3
4
5
6
7
8
9
build() {
Column() {
if (boolean) {
Text('true')
} else {
Text('false')
}
}
}

三元运算符也可以使用

使用ForEach迭代数组,为每个数组项创建组件

1
2
3
4
5
6
7
8
9
10
11
@Builder RankList() {
Column() {
List() {
ForEach(数组数据, (item, index) => {
ListItem() {
// ...
}
}, (item, index) => item.id)
}
}
}

ForEach接收三个参数:

  • 数组数据
  • 组件生成函数
  • 唯一键生成函数

路由

假设pages目录下有以下两个文件:

  • Index.ets
  • Second.ets

最常用的我们可以使用pushUrl进行路由的跳转,会将新的路由页面加入到栈中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Index.ets
import router from '@ohos.router'

@Entry
@Component
struct Index {
build() {
Column({
space: 16,
}) {
Button('Next')
.onClick(() => {
router.pushUrl({
url: 'pages/Second',
params: {
'msg': '来自第一个的数据'
}
})
})
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Second.ets
import router from '@ohos.router'

interface RouteParams {
msg: string
}

@Entry
@Component
struct Second {
@State params: RouteParams = router.getParams() as RouteParams
build() {
Row() {
Column() {
Text(this.params.msg)
.fontSize(50)
.fontWeight(FontWeight.Bold)
Button('Back')
.onClick(() => {
// 也可以通过参数返回到指定页面
router.back()
})
}
.width('100%')
}
.height('100%')
}
}

其他常用的路由跳转方法还有如replaceUrl()

在返回之前还可以先调用router.enableBackPageAlert()方法开启页面返回询问对话框功能。

路由跳转模式:

  • Standard - 多实例模式,也是默认情况下的跳转模式。目标页面会被添加到页面栈顶,无论栈中是否存在相同url的页面。
  • Single - 单实例模式。如果目标页面的url已经存在于页面栈中,则该url页面移动到栈顶。

UIAbility

UIAbility生命周期

  • UIAbility Start
  • Create - 实例创建时触发,系统会调用onCreate回调
  • WindowStageCreate - 实例创建完成之后,在进入Foreground之前。WindowStage为本地窗口管理器,用于管理窗口相关的内容,例如与界面相关的获焦/失焦、可见/不可见。可以在onWindowStageCreate回调中,设置UI页面加载、设置WindowStage的事件订阅。
  • Foreground - UIAbility切换至前台
  • Background - UIAbility切换至后台
  • WindowStageDestroy - 在UIAbility实例销毁之前,则会先进入onWindowStageDestroy回调,我们可以在该回调中释放UI页面资源。
  • Destroy - 在UIAbility销毁时触发。可以在onDestroy回调中进行系统资源的释放、数据的保存等操作。
  • UIAbility End

UIAbility生命周期包括四个状态

  • Create
  • Foreground
  • Background
  • Destroy

注意是UIAbility的状态

启动模式

UIAbility启动模式:

  • 单实例模式singleton - 默认情况下的启动模式。每次调用startAbility()方法时,如果应用进程中该类型的UIAbility实例已经存在,则复用系统中的UIAbility实例。系统中只存在唯一一个该UIAbility实例,即在最近任务列表中只存在一个该类型的UIAbility实例。
  • 多实例模式multiton - 每次调用startAbility()方法时,都会在应用进程中创建一个新的该类型UIAbility实例。即在最近任务列表中可以看到有多个该类型的UIAbility实例。
  • 指定实例模式specified - 针对一些特殊场景使用(例如文档应用中每次新建文档希望都能新建一个文档实例,重复打开一个已保存的文档希望打开的都是同一个文档实例)

在module.json5文件中的“launchType”字段配置

网络请求

需要在module.json5中申明网络访问权限。

使用方式:

  1. 导入http模块
1
import http from '@ohos.net.http';
  1. 创建httpRequest
1
let httpRequest = http.createHttp();

每一个HttpRequest对象对应一个HTTP请求。如需发起多个HTTP请求,须为每个HTTP请求创建对应HttpRequest对象。最多只能创建100个HttpRequest对象。

  1. 订阅头信息(可选)

用于订阅http响应头,会比request请求先返回,如果有需要可以订阅

1
2
3
httpReqquest.on('headersReceive', (header) => {
console.log(JSON.stringify(header))
})
  1. 发送http请求
1
2
3
4
5
6
7
8
9
10
11
12
let url = 'https://xxxxx';
let promise = httpRequest.request(
url,
{
method: http.RequestMethod.GET,
connectTimeout: 60000,
readTimeout: 60000,
header: {

}
}
)

post请求的参数需要添加到extraData中:

1
2
3
4
5
6
7
8
9
10
11
12
httpRequest.request(
url,
{
method: http.RequestMethod.POST,
// 数据
extraData: {
'param1': 'value1',
'param2': 'value2',
// ...
}
}
)
  1. 处理响应
1
2
3
4
5
6
7
promise.then(data => {
if (data.responseCode === http.ResponseCode.OK) {
console.log(data.result);
}
}).catch(err => {

})

每一个httpRequest对应一个HTTP请求任务,不可复用

作者

胡兆磊

发布于

2024-04-06

更新于

2024-04-07

许可协议