认识和存储数据

三种常见的:
string:字符串
number:数字
boolean:布尔

存储变量

let price: number = 10;

存储常量

const PI:number = 3.14;

数组

语法规则:

1
2
let 数组名:类型[] = [数据1,数据2,数据3];
let 数组名:Array<类型> = [数据1,数据2,数据3];

函数

语法规则:

1
2
function 函数名(形参):返回值类型{}
函数名();

箭头函数

语法规则:

1
2
3
4
let arr = (形参)=>{

}
arr();

对象

使用接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Student {
name: string;
id: number;
sing: (song: string) => void;
dance: () => void;
}

let student: Student = {
name: "yuhong",
id: 12345,
sing: (song: string) => {
console.log("我要唱", song);
},
dance: () => {
console.log("我会跳舞");
}
}

使用类:

1
2
3
4
5
6
7
8
9
10
11
class Person {
public userName: string;
public password: number;

constructor(userName: string, password: number) {
this.userName = userName;
this.password = password;
}
}

let person1: Person = new Person('yuhong', 12345);

联合类型

联合类型是一种灵活的数据类型,它修饰的变量可以存储不同的数据类型。
语法规则:

1
2
3
4
let 变量: 类型1 | 类型2 | 类型3 = 值;

let judge: string | number = 100;
judge = "200";

甚至还可以将变量值约定在一定的范围内进行选择:

1
let gender: "man" | "woman" | "secret" = "man";

枚举类型

枚举类型是一种特殊的数据类型,约定变量只能在一组数据范围内进行选择。

1
2
3
4
5
6
7
8
9
10
11
12
enum 枚举名{
常量1 = 值;
常量2 = 值;
常量3 = 值;
}

enum Type{
Click = true;
Slide = true;
}

let aka: Type = Type.Click;

界面开发

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
@Entry
@Component
struct Index {
@State message: string = 'Hello World1';
//界面中展示的内容都在bulid中进行编写
build() {
RelativeContainer() {
//下方的链式调用即是对文本进行各个参数进行修改
Text(this.message)
.id('HelloWorld')
.fontSize($r('app.float.page_text_font_size'))
.fontWeight(FontWeight.Bold)
.fontColor(Color.Red)
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
.onClick(() => {
this.message = 'Welcome';
})
}
.height('100%')
.width('100%')
}
}

界面开发-组件

ArkUI(方舟开发框架)是一套构建鸿蒙应用界面的框架。
构建页面的最小单位是“组件”。

组件分类:

  1. 基础组件:呈现界面的基础元素,如文字、图片、按钮等。
  2. 容器组件:控制布局组件,如Row行、Column列等。

组件属性方法

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
@Entry
@Component
struct Index {
@State message: string = 'Hello World1';

build() {
RelativeContainer() {
Column() {
// 如果不设宽度的话就会默认居中显示
Text("小说简介")
.width("100%")
.height(40)
.fontSize(20)
.fontWeight(FontWeight.Bold)
Row() {
Text("都市")
.backgroundColor(Color.Orange)
.height(30)
.width(50)
Text("生活")
.backgroundColor(Color.Pink)
.height(30)
.width(50)
Text("情感")
.backgroundColor(Color.Yellow)
.height(30)
.width(50)
Text("男频")
.backgroundColor(Color.Gray)
.height(30)
.width(50)
}
.width("100%")


Text("学鸿蒙,就来黑马程序员~")
.width("100%")
.fontSize(20)
.fontWeight(600)
.height(30)
Row() {
Text("置顶 ")
.fontColor(Color.Red)
Text("新华社 ")
.fontColor(Color.Gray)
Text("4680评论")
.fontColor(Color.Gray)

}
.width("100%")
}

}
.height('100%')
.width('100%')
}
}

示意图:

文字溢出省略号、行高

  1. 文字溢出省略(设置文本超长时的显示方式)
    语法:
    1
    2
    3
    .textOverflow({
    overflow: TextOverflow.XXX
    })
    注意:需要配合.maxLines(行数)使用。
  2. 行高
    语法:.lineHeight(数字)
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
29
@Entry
@Component
struct Index {
@State message: string = 'Hello World1';

build() {
RelativeContainer() {
Column() {
Text("HarmonyOS开发初体验")
.width("100%")
.fontSize(20)
.fontWeight(FontWeight.Bold)

Text("ArkUI 是一套构建分布式应用界面的声明式 UI 开发框架。它使用简洁的 UI 信息语法、丰富的 UI 组件、以及实时界面预览工具,帮助你提升 HarmonyOS 应用界面开发效率。你只需使用一套 ArkTS API,就能在多个 HarmonyOS 设备上提供生动而流畅的用户界面体验。")
.width("100%")
//用于约束溢出限制的行数时变为省略号
.textOverflow({
overflow: TextOverflow.Ellipsis
})
//超出两行即为溢出
.maxLines(2)
//设置文本的行高
.lineHeight(25)
}
}
.height('100%')
.width('100%')
}
}

示意图:

Image图片组件

Image图片组件,用于展示页面中的图片。
语法:Image(图片数据源)
数据源支持网络图片资源和本地图片资源。
其中本地图片资源必须存放在项目文件夹中的resource->base->media文件夹当中。

并且路径可以简写为:

1
Image($r("app.media.文件名"))

对Image组件进行宽或高进行设定,另一边会对应进行缩放。

输入框和按钮

输入框:TextInput()
配置参数placeholder即可实现占位符。
.type属性方法控制输入框是否为密文(Password)。

在column的括号中添加属性space即可以控制纵向上元素的间隔。

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
@Entry
@Component
struct Index {
@State message: string = 'Hello World1';

build() {
RelativeContainer() {
Column({ space: 10 }) {
TextInput({
placeholder: "请输入账号"
})

TextInput({
placeholder: "请输入密码"
})
.type(InputType.Password)

Button("登录")
.width(200)
}

}
.height('100%')
.width('100%')
}
}

示意图:

设计资源-svg图标

svg图标的优点是,任意缩小放大不失真,可以修改颜色。
使用属性方法.fillColor,可以修改图标的颜色。

1
2
3
Image($r("app.media.ic_gallery_not_this_person"))
.width(50)
.fillColor(Color.Red)

布局元素的组成

内边距padding

内边距:内部元素与边框的距离。

1
2
3
4
5
6
7
8
9
Text("内边距Padding")
// .padding(20) 在四个方向上都是20的内边距
.padding({
top: 10,
bottom: 30,
left: 20,
right: 10
})
.backgroundColor(Color.Orange)

示意图:

外边距margin

外边距:边框与其他相邻元素之间的距离。

1
2
3
4
5
6
7
8
9
Text("外边距Margin")
// .margin(20) 在四个方向上都是20的外边距
.margin({
top: 100,
bottom: 30,
left: 20,
right: 10
})
.backgroundColor(Color.Blue)

示意图:

边框border

给组件添加边框,进行美化。
有三种样式:

  1. BorderStyle.Solid:实线
  2. BorderStyle.Dashed:虚线
  3. BorderStyle.Dotted:点线

语法:其中宽度、颜色、边框样式四个方向都可以分别设置。

1
2
3
4
5
6
Text("待完善")
.border({
width: 2,
color: Color.Blue,
style: BorderStyle.Solid
})

圆角组件

.borderRadius(参数)
可以只传一个数字对四个角同时进行设置,也可以分别对四个角进行设置。

  1. topLeft:左上角
  2. topRight:右上角
  3. bottomLeft:左下角
  4. bottomRight:右下角
1
2
3
4
5
6
7
8
9
Image($r("app.media.psc"))
.width(200)
.borderRadius(10)
.borderRadius({
topLeft:10
topRight:20
bottomLeft:0
bottomRight:30
})

特殊的圆角组件

正圆

要求高度和宽度保持一致,borderRadius取一半的值。

1
2
3
4
5
6
Text("我是正圆")
.backgroundColor(Color.Orange)
.width(100)
.height(100)
.textAlign(TextAlign.Center)
.borderRadius(50)

示意图:

胶囊按钮(左右半圆)

要求宽度长于高度,圆角是高度的一半。

1
2
3
4
5
6
Text("我是胶囊")
.backgroundColor(Color.Green)
.width(150)
.height(100)
.textAlign(TextAlign.Center)
.borderRadius(50)

示意图:

背景属性

ImageRepeat属性用于控制背景图是否进行平铺,
参数有NoRepeat(默认,不平铺),X:水平平铺,Y:垂直平铺,XY:水平垂直方向均平铺。

1
2
3
4
Text("背景")
.backgroundColor(Color.内置的颜色枚举)
.backgroundColor("十六位色值")
.backgroundImage(图片源,ImageRepeat)
背景图位置 - backgroundImagePosition

作用:调整背景图在组件内的显示位置,默认显示位置为组件左上角。
属性:.backgroundImagePosition(坐标对象或枚举)
参数:

  1. 位置坐标:{x:位置坐标,y:位置坐标}
  2. 枚举 Alignment(组件的左上角、中间、右下角等位置)
1
2
3
4
5
6
7
Text("背景图位置")
.backgroundColor(Color.Pink)
.width(100)
.height(80)
.backgroundImage($r("app.media.startIcon"))
.backgroundImagePosition({ x: 20, y: 20 })
.backgroundImagePosition(Alignment.Center)
背景图尺寸 - backgroundImageSize

作用:背景图缩放
属性:.backgroundImageSize(宽高对象或枚举)
参数:

  1. 背景图宽高:{width:尺寸,height:尺寸}
  2. 枚举 ImageSize:
    1. Container:等比例缩放背景图,当宽或高与组件尺寸相同停止缩放。
    2. Cover:等比例缩放背景图至图片完全覆盖组件范围。
    3. Auto:默认、原图尺寸

线性布局

线性布局(LinearLayout)通过线性容器Column和Row创建。

  1. Column容器:子元素垂直方向排列。
  2. Row容器:子元素水平方向排列。

排布主方向上的对齐方式:
.justifyContent(枚举FlexAlign)

交叉轴(Column和Row的垂直方向)的对齐方式

属性:alignItems()
参数:枚举类型

  1. 交叉轴在水平方向:HorizontalAlign
  2. 交叉轴在垂直方向:VerticalAlign
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Column() {
Text()
.width(300)
.height(100)
.backgroundColor(Color.Pink)
Text()
.width(300)
.height(100)
.backgroundColor(Color.Pink)
Text()
.width(300)
.height(100)
.backgroundColor(Color.Pink)
}
.width("100%")
.height("100%")
.justifyContent(FlexAlign.SpaceEvenly)
.alignItems(HorizontalAlign.End)

自适应伸缩

设置layoutWeight属性的子元素和兄弟元素,会按照权重进行分配主轴的空间。
语法:.layoutWeight(数字)

其他元素如果设置了定宽,则layoutWeight(1)的意思就是在除去定宽剩余的宽度下占据1份,如果总共只有一份,则全部占满。
如果都是通过份数去分配的,则按份数进行占比。

示意如下:

1
2
3
4
5
6
7
8
9
10
11
Row() {
Text("元素1")
.backgroundColor(Color.Red)
.layoutWeight(1)
Text("元素2")
.backgroundColor(Color.Yellow)
.width(100)
Text("元素3")
.backgroundColor(Color.Pink)
.width(200)
}

1
2
3
4
5
6
7
8
9
10
11
Row() {
Text("元素1")
.backgroundColor(Color.Red)
.layoutWeight(1)
Text("元素2")
.backgroundColor(Color.Yellow)
.layoutWeight(2)
Text("元素3")
.backgroundColor(Color.Pink)
.layoutWeight(3)
}

Flex弹性布局

弹性容器组件:Flex()
默认主轴方向水平向右,交叉轴方向垂直向下。
且Flex子组件的宽高之和大于Flex盒子,则会压缩子元素的宽高,不会溢出显示。

1
2
3
4
5
6
Flex(参数对象){
子组件1
子组件2
子组件3
子组件4
}
  1. 主轴方向:direction
  2. 主轴对齐方式:justifyContent
  3. 交叉轴对齐方式:alignItems
  4. 布局换行:wrap
1
2
3
4
5
6
Flex({
direction: FlexDirection.Column,
justifyContent: FlexAlign.SpaceBetween,
alignItems: ItemAlign.Center,
wrap: FlexWrap.Wrap
})

绝对定位 - position

作用:控制组件位置,可以实现层叠效果。
特点:

  1. 参照父组件左上角进行偏移。
  2. 绝对定位后的组件不再占有自身原有的位置。

语法:.position(位置对象)
参数:{x:水平偏移量,y:垂直偏移量}

层级 - zIndex

作用:调整组件的层级。
语法:.zIndex(层级数)
默认组件的层级都为0,数越大,层级越靠上。

层叠布局

层叠布局具有较强的组件层叠能力。场景:卡片层叠效果等。
特点:层叠操作更简洁,编码效率高。(绝对定位的优势是更灵活)
语法:stack(){}
alignContent用于对内部子元素的定位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
build() {
RelativeContainer() {
Stack({
alignContent: Alignment.Bottom
}) {
Text()
.width(300)
.height(500)
.backgroundColor(Color.Red)
Text()
.width(200)
.height(300)
.backgroundColor(Color.Green)
Text()
.width(100)
.height(100)
.backgroundColor(Color.Orange)
}
.width("100%")
.height("100%")
}
}

示意图:

程序开发部分

模板字符串

作用:拼接字符串和变量

1
2
3
let name: string = `小明`;
let age: number = 18;
console.log("简介信息", `姓名是${name},今年${age}岁了。`);

类型转换

  1. 字符串转数字
    1. Number():字符串直接转数字,转换失败返回NaN(字符串中包含非数字)
    2. parseInt():去掉小数部分转数字,转换失败返回NaN
    3. parseFloat():保留小数部分转数字,转换失败返回NaN
  2. 数字转字符串
    1. toString():数字直接转字符串
    2. toFixed():四舍五入转字符串,可设置保留几位小数

交互-点击事件

说明:组件被点击时触发的事件

作用:监听用户的点击行为,进行对应的操作

语法:onClick((参数)=>{})

1
2
3
4
5
6
Button('点我,显示弹框')
.onClick(()=>{
AlertDialog.show({
message: "你好,这是一个弹框"
})
})

状态管理

之前构建的页面多为静态页面

但如果希望构建一个动态的、有交互的界面,就需要引入“状态”的概念

普通变量:只能在初始化时渲染,后续将不会再进行刷新。

状态变量:需要装饰器装饰,改变会引起UI的渲染刷新(必须设置类型和初始值)。

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
29
30
31
32
33
34
35
// 注意点:
// 1.普通变量,只能在初始化时被渲染,后续变化了,也不会引起更新
// 2.状态变量,被装饰器修饰,值的改变会自动引起界面的更新
// 组件外的普通变量
let myName: string = "小红"

@Entry
@Component
struct Index {
@State message: string = 'Hello World1';
// 组件内的普通变量
myAge: number = 18
// 组件内的状态变量
@State myMsg: string = "状态变量"

build() {
Column() {
Text(myName).onClick(() => {
// 组件外的普通变量不需要加this
myName = "你好小红"
console.log("myName", myName);
})
Text(this.myAge.toString()).onClick(() => {
// 组件内的普通变量需要加this
this.myAge = 200
console.log("myAge", this.myAge)
})
Text(this.myMsg).onClick(() => {
// 组件内的状态变量需要加this
this.myMsg = "修改后的状态变量"
console.log("myMsg", this.myMsg)
})
}.width("100%")
}
}

条件渲染

使用if、else、else if,可基于不同状态渲染对应不同的UI内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
@State counter: number = 1

build(){
Column(){
if(this.counter==1){
Text("1")
}else if(this.counter==2){
Text("2")
}else{
Text("any")
}
}
}

Badge角标组件

语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
build() {
Column() {
Badge({
count: 1, // 角标显示的数字,为0时角标不显示
position: BadgePosition.RightTop, // 角标显示的位置
style: {
badgeSize: 16, // 角标的大小
fontSize: 14, // 角标中字体的大小
badgeColor: Color.Yellow // 角标的背景颜色
}
}) {
// 将需要添加角标的组件放在后面的大括号中
Image($r("app.media.bg_01"))
.width(100)
}
}
}

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
build() {
Column() {
Grid() {
// 生成12个格子
ForEach([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], () => {
GridItem() {
Column() {

}
.width("100%")
.height("100%")
.backgroundColor(Color.Orange)
.border({ width: 1 })
}
})
}
.columnsTemplate("1fr 1fr 1fr 1fr") // 格子布局分为4列,且每一列占一份
.rowsTemplate("1fr 2fr 1fr") // 格子布局分为3行,第2行占两份
.rowsGap(5) // 格子之间的行间距
.columnsGap(5) // 格子之间的列间距
.width("100%")
.height(500)
.backgroundColor(Color.Pink)
}
}

遮罩显隐控制以及动画效果

动画:animation(),可以在组件发生状态变化时进行过渡

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import { createCollaborationCameraMenuItems } from '@hms.collaboration.camera'

interface ImageInfo {
url: string
count: number
}

@Entry
@Component
struct Index {
@State images: Array<ImageInfo> = [
{ url: "app.media.bg_00", count: 1 },
{ url: "app.media.bg_01", count: 2 },
{ url: "app.media.bg_02", count: 3 },
{ url: "app.media.bg_03", count: 4 },
{ url: "app.media.bg_04", count: 5 },
{ url: "app.media.bg_05", count: 6 }
]
// 控制遮罩的显隐
@State maskOpacity: number = 0
@State maskZIndex: number = -1
// 控制图片的缩放
@State maskImgX: number = 0 // 水平缩放比
@State maskImgY: number = 0 // 垂直缩放比

build() {
Stack() {
// 初始化布局结构
Column() {
Grid() {
ForEach(this.images, (item: ImageInfo, index: number) => {
GridItem() {
Badge({
count: item.count,
position: BadgePosition.RightTop,
style: {
fontSize: 14,
badgeSize: 20,
badgeColor: "#fa2a2d"
}
}) {
Image($r(item.url))
.width(80)
}
}
})
}
.columnsTemplate("1fr 1fr 1fr")
.rowsTemplate("1fr 1fr 1fr")
.width("100%")
.height(400)
.margin({ top: 100 })

Button("立即抽卡")
.width(200)
.backgroundColor("#ed5b8c")
.margin({ top: 50 })
.onClick(() => {
// 点击时修改遮罩参数,让遮罩显示
this.maskOpacity = 1
this.maskZIndex = 99
this.maskImgX = 1
this.maskImgY = 1
})
}
.width("100%")
.height("100%")
.backgroundColor(Color.Pink)

// 抽卡遮罩层(弹层)
Column({ space: 30 }) {
Text("获得生肖卡")
.fontColor("#f5ebcf")
.fontSize(25)
.fontWeight(FontWeight.Bold)
Image($r("app.media.img_00"))
.width(150)
.scale({ x: this.maskImgX, y: this.maskImgY })
.animation({
duration: 200
})
Button("开心收下")
.width(200)
.height(50)
.backgroundColor(Color.Transparent)
.border({ width: 2, color: "#fff9e0" })
.onClick(() => {
this.maskOpacity = 0
this.maskZIndex = -1
this.maskImgX = 0
this.maskImgY = 0
})
}
.justifyContent(FlexAlign.Center)
.width("100%")
.height("100%")
// 颜色十六进制色值,如果是八位,前两位就是透明度
.backgroundColor("#cc000000")
// 设置透明度
.opacity(this.maskOpacity)
// 设置层级(层级如果盖在按钮的上方就算透明度为0也点击不到按钮)
.zIndex(this.maskZIndex)
// 设置动画,当有元素的状态发生改变,可以添加animation做动画
.animation({
duration: 200
})
}
}
}

Swiper轮播组件

Swiper是一个容器组件,当设置了多个子组件之后,可以对这些子组件进行轮播显示(文字、图片)。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@Entry
@Component
struct Index {
build() {
Column() {
// 轮播组件的基本使用
// 1.Swiper包内容
// 2.Swiper设尺寸
Swiper() {
Text('1')
.backgroundColor(Color.Orange)
Text('2')
.backgroundColor(Color.Yellow)
Text('3')
.backgroundColor(Color.Brown)
}
.width('100%')
.height(100)

Swiper() {
Image($r('app.media.ic_swiper_xmyp01'))
Image($r('app.media.ic_swiper_xmyp02'))
Image($r('app.media.ic_swiper_xmyp03'))
Image($r('app.media.ic_swiper_xmyp04'))
}
.width('100%')
.height(150)
.loop(true) //开启循环
.autoPlay(true) // 是否自动播放
.interval(4000) // 自动播放的间隔
.vertical(true) // 是否为纵向滑动
.aspectRatio(2.4) // 设置和图片相同的宽高比,保证适配
.indicator(
Indicator.dot() // 小圆点
.itemWidth(20) // 默认的宽
.itemHeight(20) // 默认的高
.color(Color.Black) // 默认的颜色
.selectedItemWidth(30) // 选中的宽
.selectedItemHeight(30) // 选中的高
.selectedColor(Color.White) // 选中的颜色
)
}
}
}

样式与结构的重用

@Extend:扩展组件(样式、事件)

@Styles:抽取通用属性、事件

@Builder:自定义构建函数(结构、样式、事件)

@Extend

用于提取组件相同属性(类似于函数)

1
2
3
4
5
6
7
8
9
10
11
12
@Extend(组件类型)
function 自定义扩展组件名(参数1, 参数2){
.fontSize(10)
.fontWeight(FontWeight.Bold)
.onClick(()=>{
...
})
}

使用:
Text('2')
.自定义扩展组件名(参数1, 参数2)

@Styles

@Styles用于抽取通用的属性(如每个组件都有的宽高属性)、事件。不支持传参。

@Styles可以定义在组件外,也可以定义在组件内,因为不支持传参,如果使用到组件内的变量(需要使用this调用)的话,就需要定义在组件内部。

1
2
3
4
5
6
7
8
9
10
11
@Styles function 自定义函数名(){
.width(100)
.height(100)
.onClick(()=>{

})
}

使用:
Text('1')
.自定义函数名()

@Builder

@Builder可以封装结构、样式、事件

@Builder同样也可以定义在组件外,组件内,在组件内定义需要使用this进行修饰。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Builder
function navItem(icon: ResourceStr, text: string){
Column({space: 10}){
Image(icon)
.width(100)
Text(text)
}
.width('100%')
}

Row(){
navItem($r('app.media.ic_01'),'阿里拍卖')
navItem($r('app.media.ic_02'),'菜鸟')
}

滚动容器Scroll

当子组件的布局尺寸超过Scroll的尺寸时,内容可以滚动。

1
2
3
4
5
6
7
8
9
10
Scroll(){
// 只支持一个子组件
Column(){
// 内容放在内部
// 尺寸超过scroll即可滚动
}
}
.width('100%')
.height(200)
.scrollable(ScrollDirection.(Vertical/Horizontal))

滚动容器的常见属性:

名称 参数类型 描述
scrollable ScrollDirection 设置滚动方向
ScrollDirection.Vertical 纵向
ScrollDirection.Horizontal 横向
scrollBar BarState 设置滚动条状态
scrollBarColor string | number | Color 设置滚动条颜色
scrollBarWidth string | number 设置滚动条的宽度
edgeEffect value: EdgeEffect 设置边缘滑动效果
EdgeEffect.none 无
EdgeEffect.Spring 弹簧
EdgeEffect.Fade 阴影

Scroll控制器

  1. 创建Scroller对象
  2. 绑定给Scroll组件
  3. 调用控制器的方法
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@Entry
@Component
struct Index {
@State message: string = '@Extend扩展组件'
// 1.创建Scroller对象
scroller: Scroller = new Scroller()

build() {
Column() {
// 2.绑定给scroll组件
Scroll(this.scroller) {
Column({ space: 10 }) {
ForEach(Array.from({ length: 10 }), (item: string, index) => {
Text('测试文本' + (index + 1))
.width('100%')
.height(100)
.textAlign(TextAlign.Center)
.backgroundColor(Color.Orange)
.fontSize(20)
.fontColor(Color.White)
.borderRadius(10)
})
}
}
.width('100%')
.height(400)
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Auto) // On一直显示,Off一直隐藏,Auto滑动显示
.scrollBarColor(Color.Blue)
.scrollBarWidth(5)
.edgeEffect(EdgeEffect.Spring)

Button('控制滚动条的位置').margin(20)
.onClick(() => {
// 回到顶部
this.scroller.scrollEdge(Edge.Top)
})
Button('获取已经滚动的距离')
.onClick(() => {
console.log('获取已经滚动的距离', this.scroller.currentOffset().yOffset);
})
}
}
}

滚动容器Scroll事件

语法.onWillScroll(x,y)=>{}

x和y是在本次滑动中x和y轴上的滑动距离

容器组件Tabs

容器结构:

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
@Entry
@Component
struct Index {
build() {
// 外层顶级容器
Tabs() {
TabContent() {
Text('这是首页的内容')
}
.tabBar('首页')

TabContent() {
Text('这是推荐页的内容')
}
.tabBar('推荐')

TabContent() {
Text('这是发现页的内容')
}
.tabBar('发现')

TabContent() {
Text('这是我的页面的内容')
}
.tabBar('我的')
}
}
}

效果:

Tabs的常用属性

属性名 作用
barPosition 调整位置 开头或结尾
vertical 调整导航 水平或垂直
scrollable 调整是否 手势滑动切换
animationDuration 点击滑动的时间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Entry
@Component
struct Index {
build() {
// Start在开始位置,End在结束位置
Tabs({ barPosition: BarPosition.End }) {
TabContent() {
Text('这是首页的内容')
}
.tabBar('首页')

TabContent() {
Text('这是推荐页的内容')
}
.tabBar('推荐')
}
.vertical(true) // true在垂直方向显示,false在水平方向显示
.scrollable(false) // 是否支持手势滑动切换
.animationDuration(5000) // 切换动画时长
}
}

Tabs - 滚动导航栏

如果导航栏的内容较多,屏幕无法容纳时,可以将他设置为滚动

可以通过Tabs组件的barMode属性即可调整为固定导航栏或滚动导航栏

.barMode(BarMode.Scrollable)

自定义TabBar

默认的TabBar中只能写字符串类型的文本,如果想要使用图标+文字的形式,必须使用@Builder定义好结构,再在tabBar中进行调用。但此时就不会有自动高亮的效果了,需要自行设置。

高亮核心思路:

  1. 监听切换事件,得到索引值,记录高亮的索引
  2. 给每个tabBar起个标记,0,1,2
  3. 在tabBar内部比较标记 == 记录的索引?高亮:不高亮
名称 功能描述
onChange(event:(index:number)=>void) Tab页签切换后触发的事件
- index:当前显示的index索引,索引从0开始计算
滑动切换、点击切换均会触发
onTabBarClick(event:(index:number)=>void) Tab页签点击之后触发的事件
- index:被点击的index索引,索引从0开始计算
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
29
30
31
32
33
34
35
36
37
@Entry
@Component
struct Index {
@Builder
myBuilder(itemIndex: number, title: string, img: ResourceStr) {
Column() {
Image(img)
.width(30)
Text(title)
.fontColor(itemIndex == this.selectedIndex ? Color.Blue : Color.Black)
}
}

// 准备状态,存储激活的索引
@State selectedIndex: number = 0

build() {
// 外层顶级容器
Tabs({ barPosition: BarPosition.End }) {
TabContent() {
Text('这是首页的内容')
}
.tabBar(this.myBuilder(0, '首页', $r('app.media.startIcon')))

TabContent() {
Text('这是推荐页的内容')
}
.tabBar(this.myBuilder(1, '推荐', $r('app.media.startIcon')))
}
.vertical(false)
.scrollable(false)
.animationDuration(0)
.onChange((index: number) => {
this.selectedIndex = index
})
}
}

自定义组件

概念:由框架直接提供的称为系统组件,由开发者定义的称为自定义组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component
struct MyComponent {
build() {
Column() {
Text('我是自定义组件')
}
}
}

@Entry
@Component
struct Index {
build() {
Column() {
MyComponent()
}
}
}

自定义组件的成员变量

成员变量的值可以被外部传参覆盖,成员函数无法被覆盖。

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
29
30
31
@Component
struct MyComponent {
// 成员变量,可以被传参覆盖
name: string = '我的名字'
num: number = 10
akaFn = () => {

}

// 成员函数,无法被覆盖,只能直接调用
aka(){

}

build() {
Column() {
Text(this.name)
Text(this.num.toString())
}
}
}

@Entry
@Component
struct Index {
build() {
Column() {
MyComponent({ name: '覆盖参数', num: 20 })
}
}
}

@BuilderParam 传递UI

利用@BuilderParam构建函数,可以让自定义组件允许外部传递UI。

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
29
30
31
@Component
struct sonCom {
// 1.定义构建函数
@BuilderParam ContentBuilder: () => void = this.defaultBuilder

@Builder
defaultBuilder() {
// 可以被替换的地方
Text('默认的内容')
}

build() {
// 2.使用构建函数,构建结构
Column() {
this.ContentBuilder()
}
}
}

@Entry
@Component
struct Index {
build() {
Column() {
sonCom() {
// 要替换的内容
Button('传入的结构')
}
}
}
}

当子组件有多个BuilderParam,必须通过传参数的方式来传入。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@Component
struct MyCard {
@BuilderParam tBuilder: () => void = this.tDefaultBuilder
@BuilderParam cBuilder: () => void = this.cDefaultBuilder

@Builder
tDefaultBuilder() {
Text('我是默认的大标题')
}

@Builder
cDefaultBuilder() {
Text('我是默认的内容')
}

build() {
Column() {
// 标题部分
Row() {
this.tBuilder()
}
.height(30)
.width('100%')
.border({ color: '#ccc', width: { bottom: 1 } })
.padding({ left: 10 })

// 内容部分
Row() {
this.cBuilder()
}
.width('100%')
.padding(10)
}
.width('100%')
.height(100)
.backgroundColor(Color.White)
.borderRadius(10)
.justifyContent(FlexAlign.Start)
}
}

@Entry
@Component
struct Index {
@Builder
ftBuilder() {
Text('我是传入的大标题')
}

@Builder
fcBuilder() {
Text('我是传入的内容')
}

build() {
Column({ space: 10 }) {
MyCard({
tBuilder: this.ftBuilder,
cBuilder: this.fcBuilder
})
MyCard()
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#ccc')
}
}

状态管理

@State补充

并不是状态变量的所有更改都会引起更新,只有被框架观察到的修改才会引起UI更新。

  1. boolean、string、number类型发生改变时,可以观察到数值的变化
  2. class或object类型发生改变时,可观察到自身赋值的变化以及第一层属性发生的变化
1
2
3
4
5
6
7
8
9
10
11
12
// 可以监测到
@State message: string = 'Hello, World'
// 可以监测到
@State person: Person = {
// 可以监测到
name: 'jack',
// 可以监测到
dog: {
// 不可以监测到
name: '柯基'
}
}

@Prop父向子单向传递

@Prop装饰的变量可以和父组件建立单向的同步关系。

@Prop装饰的变量是可变的,但是变化不会同步回其父组件。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Component
struct SonCom {
// 父组件的数据发生变化,保证可以传递过来
@Prop sCar: string = ''
changeCar = () => {
}

build() {
Column() {
Text(`子组件 - ${this.sCar}`)
Button('换车').onClick(() => {
// 如果想要修改父组件的值,必须通过调用父组件的方法
this.changeCar()
})
}
.padding(20)
.backgroundColor(Color.Orange)
}
}

@Entry
@Component
struct FatherCom {
@State fCar: string = '劳斯莱斯'

build() {
Column() {
Text(`父组件 - ${this.fCar}`)
Button('换车').onClick(() => {
this.fCar = '三轮车'
})
SonCom({
sCar: this.fCar,
// 将父组件的方法传递过去
changeCar: () => {
this.fCar = '超级大塞车'
}
})
}
.padding(50)
.backgroundColor(Color.Pink)
}
}

子组件中被Prop修饰的变量可以通过箭头函数间接调用父组件的方法修改父组件中传递的变量值。

List列表组件

列表组件也可以实现Scroll组件滑动的效果。

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
Column() {
List() {
ForEach(Array.from({ length: 20 }), () => {
ListItem() {
Row() {

}
.width('50%')
.height(100)
.backgroundColor(Color.Red)
}
.padding(10)
})
}
.width('100%')
.backgroundColor(Color.Orange)
.layoutWeight(1)
.listDirection(Axis.Vertical) // 横向或纵向排布
.lanes(1, 5) // 调整列数(行数)和间距
.alignListItem(ListItemAlign.Center) // 列对齐方式
.scrollBar(BarState.Auto) // 按需自动显示滚动条
.divider({
strokeWidth: 3, // 分割线的宽度
color: Color.White, // 分割线的颜色
startMargin: 10, // 左边线距边缘的距离
endMargin: 10 // 右边线距边缘的距离
})
}

IconFont

首先进入IconFont网站,将需要使用的图标添加到购物车,将购物车中的图标添加到项目,在项目中点击下载到本地就会得到一个文件夹。

在项目中的ets文件夹下创建一个fonts文件夹,将刚才下载的内容拖进该文件夹中。

在页面中引入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 一加载Index入口页面,就进行注册
// aboutToAppear会在组件一加载时,自动进行调用(生命周期函数)
aboutToAppear(): void {
// 1.注册字体
font.registerFont({
familyName: 'myfont', // 给字体七名字
familySrc: '/fonts/iconfont.ttf' //指向下载文件中的ttf文件
})
}

build() {
Column() {
// 2.使用字体进行测试
Text('\ue60d') // 这个代码可以从下载的介绍文件中找到对应的代码(&#xe60d;)只取x后面的字符,同时以\u打头
.fontFamily('myfont')
}
}

好处是任意放大缩小都不会失真,且对比SVG图片来说更加节省内存。

TextInput输入内容的双向绑定

需要在text的属性前加上$$

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
import InfoTop from '../components/InfoTop'
import font from '@ohos.font';

@Entry
@Component
struct Index {
@State txt: string = '123456'

build() {
Row() {
TextInput({
placeholder: this.txt,
text: $$this.txt
})
.onSubmit(() => {
// 提交用户的内容
AlertDialog.show({
message: this.txt
})
})
}.width('100%')
.height(60)
.backgroundColor(Color.Pink)
}
}

@Link双向同步

使用@Link可以实现父组件和子组件的双向同步:

使用步骤:

  1. 将父组件的状态属性传递给子组件
  2. 子组件通过@Link修饰即可
  3. 分别测试基础、复杂数据类型
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
29
30
31
32
33
34
35
36
37
38
39
@Entry
@Component
struct Index {
@State count: number = 0

build() {
Column() {
Text('父组件')
Text(this.count.toString())
Button('修改数据')
.onClick(() => {
this.count++
})
SonComponent({
count: this.count
})
}
.width('100%')
.justifyContent(FlexAlign.Center)
}
}

@Component
struct SonComponent {
@Link count: number

build() {
Column() {
Text('子组件')
Text(this.count.toString())
Button('修改数据')
.onClick(() => {
this.count--
})
}
.width('100%')
.justifyContent(FlexAlign.Center)
}
}

@Provide、@Consume后代组件

将数据传递给后代(跨级),和后代数据进行双向同步。

使用步骤:

  1. 将父组件的状态属性使用@Provide修饰
  2. 子组件通过@Consume修饰
  3. 分别测试基础和复杂数据类型

说明:装饰器只能观察到第一层的变化。对于多层嵌套的情况,比如对象数组等,它们第二层的数据变化无法检测到。

就可以使用到@Observed / @ObjectLink装饰器了。

作用:用于在涉及嵌套对象或数组的场景中进行双向数据绑定。

注意:@ObjectLink修饰符不能在Entry组件中使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 这个对象就可以被监视到
@Observed
class Person{
string name = ''
number num = 0
}

// 在子组件中进行监视
@Component
struct ItemCom{
@ObjectLink info :Person
// ...
}

路由

页面路由指的是在应用程序中实现不同页面之间的跳转,以及数据的传递。

创建页面

  1. 直接右键项目中的pages文件夹新建Page。
  2. 新建普通ets文件,配置成页面。(resources->base->profile->main_pages.json中需要增加配置)

页面跳转和后退

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 导入
import router from '@ohos.router'

// 1.调用方法 - 普通跳转(可以返回)
router.pushUrl({
url: '页面地址' // (pages/页面名称)
})

// 2.调用方法 - 替换跳转(不能返回)
router.replaceUrl({
url: '页面地址'
})

// 3.调用方法 - 返回
router.back()

页面栈

页面栈是用来存储程序运行时页面的一种数据结构,遵循先进后出的原则。

页面栈的最大容量为32个页面。

1
2
3
4
5
// 获取页面栈长度
router.getLength()

// 清空页面栈
router.clear()

路由模式

路由提供了两种不同的跳转模式

  1. Standard:无论之前是否添加过,一直添加到页面栈。
  2. Single:如果目标页面已存在,会将已有的最近同url页面移动到栈顶。
1
2
// 在第二个参数设置路由模式
router.pushUrl(options, mode)

路由传参

日常开发中,点击不同的电影、商品、标题等,需要跳转到对应的详情页面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
router.pushUrl({
url: '地址',
params{
// 以对象的形式传递参数
}
})

// 页面B接收参数
aboutToAppear(): void {
// 1.确认内容
console.log(JSON.stringify(router.getParams()))
// 2.通过as类型断言转为具体的类型
const params = router.getParmas() as 类型(需要提前定义一个与发送参数方包含一样参数的接口)
// 3.通过点语法即可取值
params.xxx
}

路由 - Navigation

生命周期

组件和页面在创建、显示、销毁的这一套过程中,会自动执行一系列的生命周期函数

aboutToAppear():创建组件实例后执行,可以修改状态变量。

aboutToDisappear():组件实例销毁前执行,不允许修改状态变量。

以下仅@Entry修饰的页面有效:

onPageShow():页面每次显示触发(路由过程、应用进入前后台)。

onPageHide():页面每次隐藏触发(路由过程、应用进入前后台)。

onBackPress():点击返回触发(return true阻止返回键默认返回效果)。

Stage模型

应用模型是系统为开发者提供的应用程序所需能力的抽象提炼,它提供了应用程序必备的组件和运行机制。

配置图标和标签

app.json5:

1
2
3
4
5
6
7
8
9
10
{
"app": {
"bundleName": "com.example.myfirst", // 包名,不可省略
"vendor": "example", // 应用开发厂商描述,不可省略
"versionCode": 1000000, // 版本号,数值越大版本越高(在模拟器中,如果更改了配置文件的版本号,需要卸载并重新运行实例)
"versionName": "1.0.0", // 给用户展示的版本号
"icon": "$media:layered_image", // 应用图标
"label": "$string:app_name" // 应用名
}
}
配置图标

文件路径:main/module.json5

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"phone",
"tablet",
"2in1"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:cainiao", //配置图标,需要将图标文件放在media文件夹中
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
]
}
],
"extensionAbilities": [
{
"name": "EntryBackupAbility",
"srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets",
"type": "backup",
"exported": false,
"metadata": [
{
"name": "ohos.extension.backup",
"resource": "$profile:backup_config"
}
],
}
]
}
}
配置标签名

文件路径:main/resources/base/element/string.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"string": [
{
"name": "module_desc",
"value": "module description"
},
{
"name": "EntryAbility_desc",
"value": "description"
},
{
"name": "EntryAbility_label",
"value": "菜鸟速递" // 配置应用名称
}
]
}

修改应用名称可以同时配置中文/英文名称,根据系统语言自动切换

UIAbility组件

每一个UIAbility实例,都对应于一个最近任务列表中的任务。

UIAbility是一种包含用户界面的应用组件,主要用于和用户进行交互。

一个应用可以有一个UIAbility也可以有多个UIAbility。

单UIAbility:任务列表只有一个任务

多UIAbility:任务列表中会有多个任务

配置启动页

文件路径:main/ets/entryability/EntryAbility.ets

1
2
3
4
5
6
7
8
9
10
11
12
13
onWindowStageCreate(windowStage: window.WindowStage): void {
// Main window is created, set main page for this ability
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

// 在这个地方配置启动页
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
});
}
修改默认启动的Ability

文件路径:main/module.json5

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"phone",
"tablet",
"2in1"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:cainiao",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
// 将以下代码剪切到需要设置为启动Ability的对象中
// "exported": true,
// "skills": [
// {
// "entities": [
// "entity.system.home"
// ],
// "actions": [
// "action.system.home"
// ]
// }
// ]
},
{
"name": "OtherAbility1",
"srcEntry": "./ets/otherability1/OtherAbility1.ets",
"description": "$string:OtherAbility1_desc",
"icon": "$media:layered_image",
"label": "$string:OtherAbility1_label",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
]
}
],
"extensionAbilities": [
{
"name": "EntryBackupAbility",
"srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets",
"type": "backup",
"exported": false,
"metadata": [
{
"name": "ohos.extension.backup",
"resource": "$profile:backup_config"
}
],
}
]
}
}
UIAbility生命周期

当用户打开、切换和返回到对应应用时,应用中的UIAbility实例会在其生命周期的不同状态之间转换。

onCreate():Ability创建时回调,执行初始化业务逻辑操作。

onDestroy:Ability销毁时回调,执行资源清理等操作。

onForeground:当应用从后台转到前台时触发。

onBackground:当应用从前台转到后台时触发。

同模块Ability拉起
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import { router } from '@kit.ArkUI'
import { common, Want } from '@kit.AbilityKit'
import { BusinessError } from '@kit.BasicServicesKit'

@Entry
@Component
struct Index {
// 2.获取上下文对象
context = getContext(this) as common.UIAbilityContext

build() {
Column() {
Text('首页')
.fontSize(100)
.onClick(() => {
router.back()
})

Button('拉起另一个Ability')
.onClick(() => {
// 1.准备Want对象
let wantInfo: Want = {
deviceId: '',
bundleName: 'com.example.myfirst',
moduleName: 'entry',
abilityName: 'OtherAbility1',
parameters: {
Infinity: '来自EntryAbility'
}
}
// 3.利用context startAbility 调起 UIAbility
this.context.startAbility(wantInfo)
.then(() => {
console.log('startAbility成功')
})
.catch((error: BusinessError) => {
console.log('启动失败', error)

})
})
}
.width('100%')
.justifyContent(FlexAlign.Center)
}
}

从网络获取数据