HarmonyOS从入门到入土

ArkTs

string

string字符串:描述信息

number

number数字:计算

boolean

boolean布尔:判断(真或假)

变量

专门用于存储数据的容器

1
2
3
4
5
let 变量名: 类型 = 值

let title: string = '标题'
let price: number = 100.1
let isSelect: boolean = true

修改变量里的数据,重新赋值

1
2
let title: string = '标题'
title = '新标题'

注意点1:字符串服药用引号引起来(单引或者双引号)’字符串’ “字符串”

注意点2:存储的时候,后面存放的内容需要和前面的类型对应

1
2
3
4
5
6
7
8
let title: string = '字符串'

let age: number = 18

age = 80 #变量修改

let isLogin: boolean = true

常量

用来存储数据(不可变)

1
const companyName: string = '华为' 

变量与常量命名规则

1.只能包含数字、字母、下划线、$,不能以数字开头

2.不能使用内置关键字或保留字(比如let、const)

3.严格区分大小写

数组

接口&对象

1
let 对象名称: 对象结构类型 = 值

1.通过interface接口约定对象结构类型

1
2
3
4
5
6
7
8
9
10
11
interface 接口名 {
属性1: 类型1
属性2: 类型2
属性3: 类型3
}

interface Person {
name: string
age: number
weight: number
}

2.定义对象并使用 (通过.访问)

1
2
3
4
5
let person: Person = {
name: '姓名',
age: 18,
weight: 90
}

对象方法

方法作用:描述对象的具体行为

1.约定方法类型

1
2
3
4
5
6
7
8
interface 接口名称 {
方法名: (参数:类型) => 返回值类型
}

interface Person {
dance: () => void
sing: (song: string) => void
}

2.添加方法(箭头函数)

1
2
3
4
5
6
7
8
9
10
11
let ym: Person = {
dance: () => {
console.log('杨幂说', '我来跳个舞')
},
sing: () => {
console.log('杨幂说', '我来唱首', song)
}
}

ym.dance()
ym.sing('爱的供养')

联合类型

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

语法:

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

let judge: number | string = 100

基于联合类型,变量可以存不同类型数据

1
2
3
let judge: number | string = 100
judge = 'A+'
judge = '优秀'

联合类型还可以将变量值,约定在一组数据范围内进行选择

例如性别选择

1
let gender: 'man' | 'woman' | 'secret' = 'secret'

枚举

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

1.定义枚举类型

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

enum ThemColor {
Red = '#ff0f29',
Orange = '#ff7100',
Green = '#30b30e'
}

2.使用枚举类型,约束变量

1
2
let color: ThemColor = ThemColor.Red
console.log('主页颜色', color)

ArkUI

ArkUI(方舟开发框架)是一套构建鸿蒙应用界面的框架

构建页面的最小单位就是“组件”

界面布局思路

**组件分类**:

1.基础组件:界面呈现的基础元素,如文字、图片、按钮等。

2.容器组件:控制布局排布。如Row行,Column列等。

布局思路:先排版,再放内容。

**组件语法**:

1.容器组件:行Row、列Column

1
2
3
容器组件() {
//内容
}

2.基础组件:文字Text、图片

1
基础组件(参数)

纵向布局

1
2
3
4
5
6
7
8
9
10
11
build() {
Column () {
Text('小说简介')
Row () {
Text('都市')
Text('生活')
Text('情感')
Text('男频')
}
}
}

组件属性方法

美化组件外观效果

1
2
3
4
5
组件() {
.属性方法1(参数)
.属性方法2(参数)
.属性方法3(参数)
}
组件属性方法 描述
.width(200) 宽度
.height(200) 高度
.backgroundColor(Color.Pink) 背景色
.fontSize(24) 字体大小
.fontWeight(FontWeight.Bold) 字体粗细
1
2
3
Text('小说简介')
.height(40)
.fontSize(20)

字体颜色

语法:

1
.fontColor(颜色值)
颜色值说明 具体演示
枚举颜色 Color.颜色名 Color.Red、Color.Pink
#开头的16进制 ‘#df3c50’ 此16进制颜色会在设计图标注
1
2
3
Text('小说简介')
.fontColor(Color.Orange)
.fontColor('#df3c50')

色值也能在其他颜色的属性方法中使用,如:背景色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Entry
@Component
struct Index {
@State message: string = 'Hello World';

build() {
Column() {
Text('HarmonyOS从入门到入土')
.width('100%')
.height(40)
.fontSize(24)
Row() {
Text('置顶 ')
.fontColor('#df3c50')
Text('新华社 ')
.fontColor('#a1a1a1')
Text('4680评论')
.fontColor('#a1a1a1')
}.width('100%')
}.width('100%')
}
}

文字溢出省略号、行高

1.文字溢出省略(设置文本超长时的显式方式)

语法:

1
2
3
4
5
Text('文本内容')
.textOverflow({
overflow: TextOverflow.Ellipsis
})
.maxLines(2)

注意:需配合.maxLines(行数)使用

Image图片组件

界面中展示图片

语法:Image(图片数据源) 支持网络图片资源和本地图片资源

网络图片资源:

1
Image('url')

本地图片资源:

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

输入框与按钮

1
2
TextInput(参数对象)
.属性方法()

1.参数对象:placeholder 提示文本

2.属性方法:.type(InputType.xxx)设置输入框type类型

type值 解释说明
normal 基本输入模式,无特殊限制
Password 密码输入模式
1
2
3
TextInput({
placeholder: '占位符文本'
}).type(InputType.Password)

登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Entry
@Component
struct Images{
@State message: string = 'HarmonyOS从入门到入土';
build() {
Column({space:15}) {
TextInput({
placeholder: '请输入用户名'
})
TextInput({
placeholder: '请输入密码'
}).type(InputType.Password)
Button('登录')
.width(200)
}

}
}

控制Column和Row内元素的间隙,可以用{space:10}

登录实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Entry
@Component
struct Images{
@State message: string = 'HarmonyOS从入门到入土';
build() {
Column({space:15}) {
Image( $r('app.media.startIcon') )
.width(50)
TextInput({
placeholder: '请输入用户名'
})
TextInput({
placeholder: '请输入密码'
}).type(InputType.Password)
Button('登录').width(200)
Row({space:160}){
Text('前往注册')
Text('忘记密码')
}
}
.width('100%')
.padding(20)
}
}

设计资源SVG图标

界面中展示的图标可以使用svg图标

svg优势在于任意放大缩小不失真、可以改颜色

使用方法:

1.设计师提供,基于项目设计的图标,拷贝到项目目录使用,与图片引用的方式相同,颜色改变使用fillColor来改变

1
2
Image($r('src'))
.fillColor('#b0473d')

2.图标库中选取,找到合适的图标资源下载为svg格式

华为官方图标库HarmonyOS 主题图标库 | icon素材免费下载 | 华为开发者联盟

布局元素组成

内边距-padding

在组件内添加间距,拉开内容与组件边缘之间的距离

1
2
3
4
5
6
7
8
9
10
Text('内边距padding')
.padding(20) //四个方向内边距均为20

Text('内边距')
.padding({
top:10,
right:20,
bottom:40,
left:80
}) //四个方向分别设置内边距

外边距-margin

在组件外添加间距,拉开两个组件之间的距离

1
2
3
4
5
6
7
8
9
10
Text('外边距margin')
.margin(20) //四个方向外边距都为20

Text('外边距margin')
.margin({
top:10,
right:20,
bottom:40,
left:80
}) //四个方向分别设置外边距

QQ音乐登录案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Entry
@Component
struct Index {
@State message: string = 'HarmonyOS从入门到入土';

build() {
Column() {
Image($r('app.media.startIcon')).width(100)
Text('大王叫我来巡山').fontWeight(700)
.margin({
top:10,
bottom:40
})
Button('QQ登录').width('100%').margin({bottom:10})
Button('微信登录').width('100%').backgroundColor('#ddd').fontColor('#000')
}.width('100%').padding(20)
}
}

边框-border

给组件添加边界,进行装饰美化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Text('边框border')
.border({
width:1, //宽度(必须设置)
color:'#3274f6', //颜色
style:BorderStyle.Soild //样式
}) //四个方向相同

//不同方向的设置
Text('边框border')
.border({
width:{
left:1,
right:2,
},
color:{
left:Color.Red,
right:Color.Blue
},
style:{
left:BorderStyle.Dashed,
right:BorderStyle.Dotted
}
})

设置组件圆角

属性:.borderRadius(参数)

参数:数值或对象(四个角单独设置)

topLeft:左上角

topRight:右上角

bottomLeft:左下角

bottomRight:右下角

1
2
3
4
5
6
7
8
Text('圆角语法')
.borderRadius(5) //四个圆角相同
.borderRadius({
topLeft:5,
topRight:10,
bottomRight:15,
bottomLeft:20
}) //四个方向的圆角单独设置

特殊形状的圆角设置

1.正圆

1
2
3
4
Text('正圆')
.width(100) //宽度高度一样
.height(100)
.borderRadius(50) //圆角是宽或高的一半

2.胶囊按钮(左右半圆)

1
2
3
4
Text('胶囊按钮')
.width(150) //宽度大,高度小
.height(50)
.borderRadius(25) //圆角是高的一半

背景属性-背景图片

属性方法 属性
背景色 backgroundColor
背景图 backgroundImage
背景图位置 backgroundImagePosition
背景图尺寸 backgroundImageSize
背景图-backgroundImage

属性:.backgroundImage(背景图地址)

1
2
Text()
.backgroundImage($r('app.media.xxx'))

属性:.backgroundImage(背景图地址, 背景图平铺方式-枚举ImageRepeat)

背景平铺方式:(可省略)

NoRepeat:不平铺,默认值

X:水平平铺

Y:垂直平铺

XY:水平垂直均平铺

1
2
Text()
.backgroundImage($r('app.media.xxx'), ImageRepeat.XY)
背景图位置-backgroundImagePosition

作用:调整背景图在组件内的显示位置,默认显示位置为组件左上角

属性:.backgroundImagePosition(坐标对象或枚举)

参数:

位置坐标:{ x: 位置坐标, y: 位置坐标 }

枚举:Alignment

1
2
3
4
Text()
.backgroundImage($r('app.media.xxx'))
.backgroundImagePosition({ x:100, y:100 })
.backgroundImagePosition(Alignment.Center)

背景图尺寸-backgroundImageSize

作用:背景图缩放

属性:.backgroundImageSize(宽高对象 或枚举)

参数:

背景图宽高:{width: 尺寸, height: 尺寸}

枚举ImageSize:

Contain:等比例缩放背景图,等宽或高与组件尺寸相同停止缩放

Cover:等比例缩放背景图至图片完全覆盖组件范围

Auto:默认,原图尺寸

1
2
3
4
Text()
.backgroundImage($r('app.media.xxx'))
.backgroundImageSize({ width: 250, height: 100 })
.backgroundImageSize(ImageSize.Cover)

线性布局-主轴对齐方式

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

Column容器:子元素垂直方向排列

Row容器:子元素水平方向排列

排布主方向上的对齐方式(主轴)

属性:.justifyContent(枚举FlexAlign)

image-1

1..justifyContent(FlexAlign.Start):(排布主方向)主轴起始位置对齐

2..justifyContent(FlexAlign.Center):主轴居中对齐

3..justifyContent(FlexAlign.End):主轴结束位置对齐

4..justifyContent(FlexAlign.SpaceBetween):贴边显示,中间元素均匀分布间隙

5..justifyContent(FlexAlign.SpaceAround):间隙环绕 以0.5 1 1 1 0.5的间隙分布,靠边只有一半的间隙

6..justifyContent(FlexAlign.SpaceEvently):间隙均匀环绕,靠边也是完整的一份间隙

线性布局-交叉轴对齐方式

属性:alignItems()

参数:枚举类型

交叉轴在水平方向:HorizontalAlign

交叉轴在垂直方向:VerticalAlign

image-2

image-3

自适应伸缩

设置layoutWeight属性的子元素与兄弟元素,会按照权重进行分配主轴的空间

语法:.layoutWeight(数字)

1
2
3
4
5
6
7
8
9
10
Row() {
Text('左侧')
.layoutWeight(1)
.height(50)
.backgroundColor(Color.Pink)
.Text('右侧')
.width(70)
.height(50)
.backgroundColor(Color.Orange)
}

image-4

案例-京东

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
@Entry
@Component
struct Index {
build() {
Column() {
//顶部区域
Row() {
Image($r('app.media.jd_cancel'))
.width(20)
Text('帮助')
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)

//logo图标
Image($r('app.media.jd_logo'))
.width(250)
.height(250)

//国家/地址
Row() {
Text('国家/地址')
.layoutWeight(1)
.fontColor('#666666')
Text('中国(+86)')
.margin({right:5})
.fontColor('#666666')
Image($r('app.media.jd_right'))
.width(20)
.fillColor('#666666')
}
.width('100%')
.height(40)
.backgroundColor('#fff')
.borderRadius(20)
.padding({left:15, right:10})

//手机号输入框
TextInput({
placeholder: '请输入手机号'
})
.placeholderColor('#666666')
.height(40)
.borderRadius(20)
.backgroundColor('#fff')
.margin({top:20})

//已阅读同意
Row() {
Checkbox()
.width(10)
.margin({top:7})
//一段文本中,有文本样式需要单独定制,Text包裹span
Text(){
Span('我已阅读并同意')
Span('《京东隐私政策》').fontColor('#3274f6')
Span('《京东用户服务协议》').fontColor('#3274f6')
Span('未注册的手机号将自动创建京东账号')
}
.fontSize(12)
.fontColor('#666666')
.lineHeight(20)
}
.alignItems(VerticalAlign.Top)
.margin({top:20})

//登录
Button('登录')
.width('100%')
.backgroundColor('#bf2838')
.margin({top:25})

//新用户注册
Row({space:25}){
Text('新用户注册').fontSize(14).fontColor('#666666')
Text('账户密码登录').fontSize(14).fontColor('#666666')
Text('无法登录').fontSize(14).fontColor('#666666')
}
.margin({top:15})

//填充组件;作用:填充空白区域(像弹簧)
Blank()
//底部其他登录方式
Column() {
Text('其他登录方式')
.height(22)
.fontSize(14)
.fontColor('#666666')
.margin({bottom: 28})
Row(){
Image($r('app.media.jd_huawei')).width(34)
Image($r('app.media.jd_wechat')).width(34).fillColor('#56a44a')
Image($r('app.media.jd_weibo')).width(34).fillColor('#c8493b')
Image($r('app.media.jd_QQ')).width(34).fillColor('#4ba0e8')
}
.width('100%')
.margin({bottom:30})
.justifyContent(FlexAlign.SpaceAround)
}
.width('100%')
.height(70)
}
.width('100%')
.padding(20)
.height('100%')
.backgroundImage($r('app.media.jd_login_bg'))
.backgroundImageSize(ImageSize.Cover)
}
}

image-5

弹性布局(flex)

image-6

弹性容器组件:flex()

1
2
3
4
5
6
Flex() {
子组件1
子组件2
子组件3
子组件N
}

1.主轴方向:direction

2.主轴对齐方式:justifyContent

3.交叉轴对齐方式:alignItems

4.布局换行:warp

Flex默认主轴水平往右,交叉轴垂直往下 -> row

1.主轴方向可以通过direction来调整方向

direction: FlexDirection.Row / Column

2.主轴对齐方式可以通过justifyContent调整

justifyContent: FlexAlign.SpaceAround

3.交叉轴对齐方式可以通过alignItems调整

alignItems: ItemAlign.Stretch / Start / Center / End

单行或者单列的情况,优先使用线性布局(本质基于Flex设计的,且还做了性能优化)

4.Flex换行 - warp

wrap属性:Flex是单行布局还是多行布局

1.FlexWrap.NoWrap 单行布局

2.FlexWrap.Wrap 多行布局

1
2
3
Flex({
wrap: FlexWrap.Wrap
})

Flex布局:伸缩布局。当子盒子的总和溢出父盒子,默认进行压缩显示。

绝对定位-position

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

image-7

特点:

1.参照父组件左上角进行偏移

2.绝对定位后的组件不再占用自身原有位置且可以任意调整位置,不影响其他元素

语法:

.position(位置对象)

参数:{ x: 水平偏移量, y: 垂直偏移量 }

1
2
3
4
5
Text('文字内容')
.position({
x: 0,
y: 0
})

后面的组件层次更高会盖住前面的组件

如果在不懂结构的情况下,调整组件的层级可以使用.Zindex(数字),值越大层级越高

人气卡片综合案例

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
@Entry
@Component
struct Index{
build() {
Column(){
//思路:先整体,再局部,再细节
Column(){
//定位的vip(不会影响到其他子元素的展示)
Text('VIP')
.position({x:0,y:0})
.zIndex(666)
.width(40).height(20)
.backgroundColor('#e49642')
.borderRadius({
topLeft:10,
bottomRight:10
})
.border({width:2,color:'#fbe7a3'})
.fontColor('#fbe7a3')
.fontStyle(FontStyle.Italic)
.fontSize(14)
.fontWeight(700)
.textAlign(TextAlign.Center)
//上图
Image($r('app.media.position_moco'))
.width('100%')
.height(210)
.borderRadius(10)
//下文本(图+文本)
Row(){
Image($r('app.media.position_earphone'))
.width(20)
.backgroundColor('#55b7f4')
.borderRadius(10)
.padding(3)
.fillColor(Color.White)
.margin({left:6,right:6})
Text('飞狗MOCO')
.fontWeight(700)
}
.height(30)
.width('100%')
// .backgroundColor(Color.Orange)
}
.height(240)
.width(160)
.backgroundColor(Color.White)
}
.height('100%')
.width('100%')
.backgroundColor(Color.Pink)
}
}

image-8

层叠布局

层叠布局具有较强的组件层叠能力。场景:卡片层叠效果等

特点:层叠操作更简洁,编码效率高。(绝对定位的优势是更灵活)

语法:

1
2
3
4
5
Stack() {
Item1()
Item2()
Item3()
}

位置调整可使用Stack({alignContent: Alignment.xxx})实现。

TopStart:左上角

Top:正上方

TopEnd:右上角

Start:正左方

Center:正中心

End:正右方

BottomStart:左下角

Bottom:正下方

BottomEnd:右下角

同时还可以加入.zIndex(数字)调整层级

B站视频卡片综合案例

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
@Entry
@Component
struct Index {
build() {
Column() {

Column() {
//上面图片区域(层叠布局)
Stack( {alignContent: Alignment.Bottom} ) {
Image($r('app.media.bz_img'))
.borderRadius({
topLeft:10,
topRight:10
})
Row() {

Row( {space: 5} ){
Image($r('app.media.bz_play'))
.width(14)
.fillColor(Color.White)
Text('288万')
.fontSize(12)
.fontColor(Color.White)
}
.margin( {right:10} )

Row( {space: 5} ){
Image($r('app.media.bz_msg'))
.width(14)
.fillColor(Color.White)
Text('8655')
.fontSize(12)
.fontColor(Color.White)
}

Blank()

Text('4:33')
.fontSize(12)
.fontColor(Color.White)
}
.height(24)
.width('100%')
.padding( {left:5, right:5} )
}
.width('100%')
.height(125)

//底部文字区域
Column() {
Text('【凤凰传奇新歌】 欢迎来到国风统治区:唢呐一响神曲《铁衣流派推广曲》')
.fontSize(13)
.lineHeight(16)
.textOverflow( {overflow:TextOverflow.Ellipsis} )
.maxLines(2)
Row() {
Text('19万点赞')
.fontSize(10)
.fontColor('#e66c43')
.backgroundColor('#fef0ef')
Image($r('app.media.bz_more'))
.width(14)
}
.margin({top:10})
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
.padding(5)

}
.width(200)
.height(200)
.backgroundColor(Color.White)
.borderRadius(10)
.margin({top:10})
}
.width('100%')
.height('100%')
.backgroundColor('#ccc')
}
}

image-9

界面开发实战-支付宝

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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
import sendableColorSpaceManager from '@ohos.graphics.sendableColorSpaceManager'

@Entry
@Component
struct Index {
build() {
//整体Stack布局 + 底部的tab
//column/row默认不具备可滚动的效果,需要用到组件Scroll
Stack({ alignContent: Alignment.Bottom }) {
//主体展示区
Stack({ alignContent: Alignment.Top }) {
//头部
Row() {
//左边
Column() {
Text('北京').fontSize(18).fontColor('#fff')
Text('晴 2℃').fontSize(12).fontColor('#fff')
Image($r('app.media.zfb_head_down'))
.position({
x:40,
y:0
})
.width(12)
.fillColor('#fff')
}
//中间
Row() {
Image($r('app.media.zfb_head_search'))
.width(20)
.fillColor('#666')
.margin({ left:5, right:5 })
Text('北京交通一卡通')
.layoutWeight(1)
Text('搜索')
.width(55)
.fontColor('#5b73de')
.fontWeight(400)
.textAlign(TextAlign.Center)
.border({
width: {left:1},
color: '#ccc'
})
}
.height(32)
.layoutWeight(1)
.backgroundColor('#fff')
.borderRadius(5)
.margin({ left:25, right:12 })
//右边
Image($r('app.media.zfb_head_plus'))
.width(30)
.fillColor('#fff')
}
.padding({ left:10, right:10 })
.width('100%')
.height(60)
.backgroundColor('#5b73de')

//主体页面
Scroll() {
Column() {
//Top快捷按钮区域
Row() {
Column() {
Image($r('app.media.zfb_top_scan'))
.width(36)
.fillColor('#fff')
Text('扫一扫')
.fontColor('#fff')
}
.layoutWeight(1)
Column() {
Image($r('app.media.zfb_top_pay'))
.width(36)
.fillColor('#fff')
Text('收付款')
.fontColor('#fff')
}
.layoutWeight(1)
Column() {
Image($r('app.media.zfb_top_travel'))
.width(36)
.fillColor('#fff')
Text('出行')
.fontColor('#fff')
}
.layoutWeight(1)
Column() {
Image($r('app.media.zfb_top_card'))
.width(36)
.fillColor('#fff')
Text('卡包')
.fontColor('#fff')
}
.layoutWeight(1)

}
.backgroundColor('#5b73de')
.padding({ top:5, bottom:15 })
//主体区 背景色#f6f6f6
Column() {
//导航区
Column({space:10}) {
Row() {
Column() {
Image($r('app.media.zfb_nav1'))
.width(28)
.margin({ bottom:8})
Text('滴滴出行')
.fontSize(12)
.fontColor('#666')
}
.layoutWeight(1)
Column() {
Image($r('app.media.zfb_nav2'))
.width(28)
.margin({ bottom:8})
Text('生活缴费')
.fontSize(12)
.fontColor('#666')
}
.layoutWeight(1)
Column() {
Image($r('app.media.zfb_nav3'))
.width(28)
.margin({ bottom:8})
Text('股票')
.fontSize(12)
.fontColor('#666')
}
.layoutWeight(1)
Column() {
Image($r('app.media.zfb_nav4'))
.width(28)
.margin({ bottom:8})
Text('蚂蚁森林')
.fontSize(12)
.fontColor('#666')
}
.layoutWeight(1)
Column() {
Image($r('app.media.zfb_nav5'))
.width(28)
.margin({ bottom:8})
Text('手机充值')
.fontSize(12)
.fontColor('#666')
}
.layoutWeight(1)

}

Row() {
Column() {
Image($r('app.media.zfb_nav6'))
.width(28)
.margin({ bottom:8})
Text('余额宝')
.fontSize(12)
.fontColor('#666')
}
.layoutWeight(1)
Column() {
Image($r('app.media.zfb_nav7'))
.width(28)
.margin({ bottom:8})
Text('花呗')
.fontSize(12)
.fontColor('#666')
}
.layoutWeight(1)
Column() {
Image($r('app.media.zfb_nav8'))
.width(28)
.margin({ bottom:8})
Text('飞猪旅行')
.fontSize(12)
.fontColor('#666')
}
.layoutWeight(1)
Column() {
Image($r('app.media.zfb_nav9'))
.width(28)
.margin({ bottom:8})
Text('淘票票')
.fontSize(12)
.fontColor('#666')
}
.layoutWeight(1)
Column() {
Image($r('app.media.zfb_nav10'))
.width(28)
.margin({ bottom:8})
Text('饿了么')
.fontSize(12)
.fontColor('#666')
}
.layoutWeight(1)

}

Row() {
Column() {
Image($r('app.media.zfb_nav11'))
.width(28)
.margin({ bottom:8})
Text('读书听书')
.fontSize(12)
.fontColor('#666')
}
.layoutWeight(1)
Column() {
Image($r('app.media.zfb_nav12'))
.width(28)
.margin({ bottom:8})
Text('基金')
.fontSize(12)
.fontColor('#666')
}
.layoutWeight(1)
Column() {
Image($r('app.media.zfb_nav13'))
.width(28)
.margin({ bottom:8})
Text('直播广场')
.fontSize(12)
.fontColor('#666')
}
.layoutWeight(1)
Column() {
Image($r('app.media.zfb_nav14'))
.width(28)
.margin({ bottom:8})
Text('医疗健康')
.fontSize(12)
.fontColor('#666')
}
.layoutWeight(1)
Column() {
Image($r('app.media.zfb_nav15_more'))
.width(28)
.margin({ bottom:8})
Text('更多')
.fontSize(12)
.fontColor('#666')
}
.layoutWeight(1)

}

}
.padding(10)

//产品区
Row({ space:5 }) {
Image($r('app.media.zfb_pro_pic1'))
.layoutWeight(1)
Image($r('app.media.zfb_pro_pic2'))
.layoutWeight(1)
Image($r('app.media.zfb_pro_pic3'))
.layoutWeight(1)
}
.padding(10)

Column({ space:10 }) {
Image($r('app.media.zfb_pro_list1'))
.width('100%')
Image($r('app.media.zfb_pro_list2'))
.width('100%')
}
.padding(10)

}
.height(1000)
.width('100%')
.backgroundColor('#fff')
.borderRadius({
topLeft:15,
topRight:15
})

}
.width('100%')
.padding( {top:60, bottom:60} )
}

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

//底部Tab导航区
Row() {

Column(){
Image($r('app.media.zfb_tab_home'))
.width(35)
}
.layoutWeight(1)
Column(){
Image($r('app.media.zfb_tab_money'))
.width(28)
.margin({bottom:2})
Text('理财')
.fontSize(12)
}
.layoutWeight(1)
Column(){
Image($r('app.media.zfb_tab_life'))
.width(28)
.margin({bottom:2})
Text('生活')
.fontSize(12)
}.
layoutWeight(1)
Column(){
Image($r('app.media.zfb_tab_chat'))
.width(28)
.margin({bottom:2})
Text('消息')
.fontSize(12)
}
.layoutWeight(1)
Column(){
Image($r('app.media.zfb_tab_me'))
.width(28)
.margin({bottom:2})
Text('我的')
.fontSize(12)
}
.layoutWeight(1)
.layoutWeight(1)

}
.width('100%')
.height(60)
.backgroundColor('#fbfcfe')
}
.width('100%')
.height('100%')
.backgroundColor('#5b73de')

}
}

算法设计

字符串拼接

作用:把两个或者多个字符串,拼成一个字符串

通常拼接的是字符串和变量

字符串拼接 + 拼串

1
2
3
4
let name: string = 'qinhuai'
let age: number = 18
console.log('简介信息:', '姓名' + name)
console.log('简介信息:', '年龄' + age)

注意:”+”两边只要有字符串,就是拼串的作用和(如果两边都是数字,就是计算求和的作用)

1
2
3
let num1: number = 100
let num2: number = 200
console.log('总数:', num1 + num2)

模板字符串

使用``进行,内部”${变量}”引用变量

作用:拼接字符串和变量

优势:更适合于多个变量的字符串拼接

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

类型转换(数字和字符串)

1.字符串转数字

Number(*):字符串直接转数字,转换十八返回NaN(字符串中包含非数字)

parseInt(*):去掉小数部分转数字,转换失败返回NaN

parseFloat(*):保留小数部分转数字,转换失败返回NaN

2.数字转字符串

toString():数字直接转字符串

toFixed(保留几位小数):四舍五入转字符串,可设置保留即为小数

交互

点击事件

组件被点击时触发的事件

作用:监听(感知)用户点击行为,进行对应操作

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

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

状态管理

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

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

image-10

点击交互触发了文本状态变更,状态变更引起了UI渲染

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//普通变量

let msg1: string = 'xxx'
@Entry
@Component
struct Index {
msg2: string = 'xxx'
build() {
Column() {
Text(msg1)
Text(this.msg2)
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//状态变量

@Entry
@Component
struct Index {
@State msg3: string = 'xxxx'
build() {
Column() {
Text(this.msg3).onClick( () => {
this.msg3 = 'xxx.xxx'
})
}
}
}

注意:定义在组件内普通变量和状态变量,都需要通过this访问

计数器案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Entry
@Component
struct Index{
//[组件的]状态变量 this.xxx
@State count: number = 1
build() {
Row() {
Button('-')
.onClick(() => {
this.count -= 1
})
Text(this.count.toString()).margin(10)
Button('+')
.onClick(() => {
this.count += 1
})
}
.padding(20)
}
}

算术运算符

算术运算符也叫数字运算符,主要包括加减乘除取余(求模)等

算术运算符 作用
+ 加法运算
- 减法运算
* 乘法运算
/ 除法运算
% 取余(求模)

赋值运算符

赋值运算符:对变量进行复制的运算符,如:=

赋值运算符 作用
+= 加法赋值
-= 减法赋值
*= 乘法赋值
/= 除法赋值
%= 取余赋值

点赞案例

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
@Entry
@Component
struct Index{
//声明状态 颜色 点赞数
@State myColor: string = '#7e7e7e'
@State myCount: number = 8888

build() {
Column(){
Column({space:8}){
//图片
Image($r('app.media.eyes'))
.width('100%')
.borderRadius({topLeft:6, topRight:6})
//标题文字
Text('考眼力又来了你能看到几只鸭子?')
.fontSize(14)
.lineHeight(18)
.padding({left:5,right:5})
//来源和点赞
Row(){
Text() {
ImageSpan($r('app.media.avatar'))
.width(16)
.margin({right:3})
Span('视野联行眼镜')
.fontSize(12)
.fontColor('#7e7e7e')
}
Row(){
Image($r('app.media.ic_love'))
.margin({right:3})
.width(14)
.fillColor(this.myColor)
Text(this.myCount.toString())
.fontSize(12)
.fontColor(this.myColor)
}
.onClick(()=>{
//修改了数字
this.myCount += 1
//修改颜色
this.myColor = '#ff0000'
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding({left:5,right:5})
}
.width('50%')
}
.padding(20)
}
}

一元运算符

常见一元运算符:++和–

后置写法:先赋值后自增/自减

前置写法:先自增/自减再赋值

1
2
let num: number = 10
let res: number = num++ //后自增
1
2
let num: number = 10
let res: number = ++num //先自增

比较运算符

作用:用来判断比较两个数据大小,返回一个布尔值(true/false)

比较运算符 作用
> 判断大于
>= 判断大于等于
< 判断小于
<= 判断小于等于
== 判断相等
!= 判断不相等

逻辑运算符

作用:扩充判断条件

逻辑运算符 作用
&& 与,都真才真
|| 或,一真则真
! 非,取反

运算符优先级

优先级 顺序
1 小括号 ()
2 一元 ++ 、–、!
3 算数 先* 、/ 、%后+、-
4 比较 >、>=、<、<=
5 比较 ==、!=
6 逻辑运算符 先&&后||
7 赋值 =

综合案例-美团购物车

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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
@Entry
@Component
struct Index {
@State count: number = 1
@State oldPrice: number = 40.4
@State newPrice: number = 10.4
build() {
Column() {
Column() {
// 产品
Row({ space: 10}){
// 图片
Image($r('app.media.product1'))
.width(100)
.borderRadius(8)
// 文字
Column({space: 10}) {
Column({ space: 6}) {
Text('冲销量1000ml缤纷八果水果捞')
.lineHeight(20)
.fontSize(14)
Text('含1份折扣商品')
.fontSize(12)
.fontColor('#7f7f7f')
}
.width('100%')
.alignItems(HorizontalAlign.Start)
Row(){
// 价格
Row({ space: 5}) {
Text() {
Span('¥')
.fontSize(14)
Span(this.newPrice.toFixed(2))
.fontSize(18)
}
.fontColor('#ff4000')
Text() {
Span('¥')
Span(this.oldPrice.toFixed(2))
}
.fontSize(14)
.fontColor('#999')
.decoration({type: TextDecorationType.LineThrough, color: '#999'})
}
// 加减
Row() {
Text('-')
.width(22)
.height(22)
.border({width:1, color: '#e1e1e1', radius: {topLeft: 5, bottomLeft: 5}})
.textAlign(TextAlign.Center)
.fontWeight(700)
.onClick(() => {
// this.count = this.count - 1
// 让状态变量,在原有数据的基础上自减1
this.count--
})
Text(this.count.toString())
.height(22)
.border({width: { top:1, bottom: 1 }, color: '#e1e1e1'})
.padding({left: 10, right: 10})
.fontSize(14)
Text('+')
.width(22)
.height(22)
.border({width:1, color: '#e1e1e1', radius: {topRight: 5, bottomRight: 5}})
.textAlign(TextAlign.Center)
.fontWeight(700)
.onClick(() => {
this.count++
})
}
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
.height(75)
.layoutWeight(1)
.justifyContent(FlexAlign.SpaceBetween)
}
.width('100%')
.alignItems(VerticalAlign.Top)
.padding(20)

// 结算
Row({ space: 10 }){
// 价格
Column({space: 5}) {
Text() {
Span(`已选 ${this.count} 件,`)
.fontColor('#848484')
Span('合计:')
Span('¥')
.fontColor('#fd4104')
Span((this.count * this.newPrice).toFixed(2))
.fontColor('#fd4104')
.fontSize(16)
}
.fontSize(14)
Text('共减¥' + (this.count * (this.oldPrice - this.newPrice)).toFixed(2))
.fontColor('#fd4104')
.fontSize(12)
}
.alignItems(HorizontalAlign.End)
// 结算按钮
Button('结算外卖')
.width(110)
.height(40)
.backgroundColor('#fed70e')
.fontColor('#564200')
.fontSize(16)
.fontWeight(600)
}
.width('100%')
.height(70)
.backgroundColor('#fff')
.position({x:0, y: '100%'})
.translate({y: '-100%'})
.padding({ left: 20, right: 20 })
.justifyContent(FlexAlign.End)
}
}
.width('100%')
.height('100%')
.backgroundColor('#f3f3f3')
}
}

数组的操作

主要针对数组中的数据进行查找、修改、增加或删除

操作 语法
查找 数组名[下标]、数组名.length
修改 数组名[下标]=新值
增加 数组名.push(数据1,数据2, …)、数组名。unshift(数据1,数据2, …)
删除 数组名.pop()、数组名.shift()
任意位置增加或删除 数组名.splice(操作的起始位置,删除的个数,新增1,新增2,……)
增加数组元素

往开头加:数组名.unshift(数据1,数据2,数据3,……)

结尾添加:数组名.push(数据1,数据2,数据3,……)

删除数组元素

从开头删:数组名.shift()

从结尾删:数据名.pop()

任意位置添加/删除数组元素

语法:数组名.splice(起始位置,删除个数,新增元素1,新增元素2,……)

语句

语句:一段可以执行的代码,是一种行为

表达式:可以被求值的代码,并将其计算出一个结果

语句执行结构:

image-11

分支语句
if分支语句

if分支语句:根据逻辑条件不同,执行不同语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//单分支语法
if (逻辑条件) {
条件成立执行的代码
}
//小括号条件结果为true,则执行大括号里面的代码
//小括号结果不是布尔类型时,会将类型转换为布尔值

//双分支语法
if () {
条件成立执行的代码
}
else {
条件不成立执行的代码
}

购物车数字框

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
@Entry
@Component
struct Index {
@State count: number = 5
build() {
Column() {
Row() {
Text('-')
.width(40)
.height(40)
.border({width: 2, color: '#999', radius: {topLeft: 3, bottomLeft:3}})
.textAlign(TextAlign.Center)
.onClick(() => {
// 如果数字大于1, 可以减
if (this.count > 1) {
this.count--
}
// 否则, 给个提示
else {
// console.log('提示', '最小值为1, 不能再减了')
AlertDialog.show({
message: '最小值为1, 不能再减了'
})
}

})

Text(this.count.toString())
.height(40)
.padding({left: 20, right: 20 })
.border({width: {top: 2, bottom: 2}, color: '#999'})
.fontSize(14)

Text('+')
.width(40)
.height(40)
.border({width: 2, color: '#999', radius: {topRight: 3, bottomRight: 3}})
.textAlign(TextAlign.Center)
.onClick(() => {
this.count++
})
}
}
.padding(20)
}
}
if多分支

if多分支,可以解决多种分支的情况

1
2
3
4
5
6
7
8
9
10
11
12
if (条件1) {
条件1成立执行的代码
}
else if (条件2) {
条件2成立执行的代码
}
else if (条件3) {
条件3成立执行的代码
}
else {
都不成立执行的代码
}
switch分支

switch分支一般用于精确匹配,不同的值执行不同的代码

1
2
3
4
5
6
7
8
9
10
switch () {
case1:
与值1匹配执行的语句
break
case2:
与值2匹配执行的语句
break
default:
以上都为成功匹配执行的代码
}

注意:如果没有break语句,则会直接执行switch中的下一个代码块(无论是否匹配成功)

三元条件表达式

语法:条件 ? 条件成立执行的表达式 : 条件不成立执行的表达式

1
2
3
let num1: number = 5
let num2: number = 10
let res: number = num1 > num2 ? num1 : num2
条件渲染

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Entry
@Component
struct Index {
@State age: number = 16
build() {
//条件渲染:不同的条件,控制不同的UI界面展示
Column() {
if (this.age < 18) {
Text('未成年,18岁以下')
}
else if (this.age < 60) {
Text('成年人,18-60岁')
}
else {
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
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
110
111
112
@Entry
@Component
struct Index {
@State count: number = 0
build() {
Column() {
Column() {
// 底部菜单
Row({space: 10}) {
// 左侧菜单
Row() {
Column({space: 5}) {
Image($r('app.media.ic_dianpu'))
.width(20)
Text('店铺')
.fontSize(10)
.fontColor('#262626')
}
Column({space: 5}) {
Image($r('app.media.ic_kefu'))
.width(20)
.fillColor('#666')
Text('客服')
.fontSize(10)
.fontColor('#262626')
}
Column({space: 5}) {
Image($r('app.media.ic_cart2'))
.width(20)
.fillColor('#666')
Text('购物车')
.fontSize(10)
.fontColor('#262626')
}
}
.layoutWeight(1)
.justifyContent(FlexAlign.SpaceBetween)

// 右侧按钮 -- 可以购买
if (this.count > 0) {
Row({space: 5}) {
Button('加入购物车')
.width(105)
.height(40)
.backgroundColor('#ffcc00')
.fontSize(14)
.fontWeight(600)
Button('立即购买')
.width(105)
.height(40)
.backgroundColor('#f92c1b')
.fontSize(14)
.fontWeight(600)
}
}

else {
// 右侧按钮 -- 不能购买
Row() {
Button('查看类似商品')
.width(170)
.height(40)
.backgroundColor('#ffcc00')
.fontSize(14)
.fontWeight(600)
}
}

}
.width('100%')
.height(60)
.backgroundColor('#f7f7f7')
.padding({left: 20, right: 10})

// 消息提示:库存 <= 0 显示,否则隐藏
if (this.count <= 0) {
Row() {
// 左侧
Row({ space: 5 }){
Image($r('app.media.ic_lingdang'))
.width(12)
.fillColor('#de6a1c')
Text('该商品暂时没有库存,看看相似商品吧')
.fontSize(10)
.fontColor('#de6a1c')
}
// 右侧
Image($r('app.media.ic_shangjiantou'))
.width(15)
.padding(3)
.fillColor('#d0662c')
.backgroundColor('rgba(0,0,0,0.1)')
.borderRadius(8)
}
.width('100%')
.height(36)
.backgroundColor('#fbf9da')
.position({x: 0, y: '-36'})
.padding({left: 20, right: 20})
.justifyContent(FlexAlign.SpaceBetween)
}

}
.position({x:0,y:'100%'})
.translate({y: '-100%'})
}
.width('100%')
.height('100%')
.padding({bottom:20})
.backgroundColor('#f2f2f2')
}
}
循环语句
whlie语句

作用:重复执行指定的一段代码

1
2
3
while (条件) {
条件成立重复执行的代码
}

循环三要素:

1.初始值(变量)

2.循环条件

3.变化量(变量计数,自增或自减)

for循环

作用:重复执行指定的一段代码

1
2
3
for (初始值; 条件; 变化量) {
重复执行的代码
}
退出循环

作用:满足指定条件,可以退出循环

break:终止整个循环

continue:退出当前以此循环的执行,继续执行下一次循环

遍历数组

遍历:将数组里面的每个数据,按顺序访问一遍

1
2
3
4
let names: string = ['小红', '小明', '大强']
for (let i = 0; i < names.length; i++) {
console.log('名字是', names[i])
}
遍历数组-for…of

语法:for (let item of 数组名) {}

for…of:再…之中进行循环

item:声明的一个变量,用来在循环的时候接收每一个数组元素

1
2
3
4
let names: string = ['小红', '小明', '大强']
for (let item of names) {
console.log('数组中的每一项', item)
}

对象数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//1.定义接口
interface Person {
stuId: number
name: string
gender: string
age: number
}

//2.基于接口构建对象
let p1: Person = {
stuId: 1,
name: '小丽',
gender: '女',
age: 12
}

//2.基于接口构建对象数组
let pArr: Person[] = [
{stuId: 1, name: '小丽', gender: '女', age: 12},
{stuId: 2, name: '小王', gender: '男', age: 11},
{stuId: 3, name: '大强', gender: '男', age: 13},
{stuId: 4, name: '小张', gender: '男', age: 11},
{stuId: 5, name: '小美', gender: '女', age: 12},
]

打印对象数组需要使用到JSON.stringify(数组)

ForEach-渲染控制

ForEach可以基于数组的个数,渲染组件的个数

语法:

ForEach(arr, (item, index) => {})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@State titles:string[] = ['电子产品', '精品服饰', '母婴产品', '影音娱乐', '海外旅游']

build() {
Column() {
ForEach(this.titles, (item: string, index) => {
Text(item)
.fontSize('24')
.fontWeight(700)
.fontColor(Color.Orange)
.width('100%')
.padding(15)
})
}
}

生肖抽奖卡案例

Badge角标组件

1
2
3
4
5
6
7
8
9
10
11
12
Badge({
count: 1, //角标数值
position: BadgePosition.RightTop, //角标位置
style: {
fontSize: 12, //文字大小
badgeSize: 16//圆形大小
badgeColor: '#fa2a2d' //圆形底色
}
}) {
Image($r('app.media.xxx'))
.width(80)
}

Grid布局

image-12

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 {
build() {
Grid() {
ForEach([1,2,3,4,5,6,7,8,9,10,11,12], () => {
GridItem() {
Column() {
}
.width('100%')
.height('100%')
.backgroundColor(Color.Green)
.border({width:1})
}
})
}
.columnsTemplate('1fr 1fr 1fr 1fr')
.rowsTemplate('1fr 2fr 1fr')
.rowsGap(5)
.columnsGap(5)
.width('100%')
.height(500)
.backgroundColor(Color.Pink)
}
}

image-13

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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
import image from '@ohos.multimedia.image'

//定义接口(每个列表项的数据结构)
interface ImageCount {
url: string
count: number
}

@Entry
@Component
struct Index {
//基于接口准备数据
@State images: ImageCount[] = [
{url: 'app.media.bg_00', count: 0},
{url: 'app.media.bg_01', count: 0},
{url: 'app.media.bg_02', count: 0},
{url: 'app.media.bg_03', count: 0},
{url: 'app.media.bg_04', count: 0},
{url: 'app.media.bg_05', count: 0}
]

//奖池
@State arr: string[] = ['pg', 'hw', 'xm']
@State prize: string = '' //没中奖
//控制遮罩的显隐
@State maskOpacity: number = 0 //透明度
@State maskZIndex: number = -1 //显示层级

//控制图片的缩放
@State maskImgX: number = 0 //水平缩放比
@State maskImgY: number = 0 //垂直缩放比

//随机的生肖卡序号
@State randomIndex: number = 2

//控制中大奖遮罩的显隐
@State isGet: boolean = false

build() {
Stack() {
Column() {
Grid() {
ForEach(this.images, (item:ImageCount, index:number)=> {
GridItem () {
Badge ({
count: item.count,
position: BadgePosition.RightTop,
style:{
fontSize:14,
badgeSize:20,
badgeColor:'#fa2a2d'
}
}) {
Image($r(item.url))
.width(100)
}
}
})
}
.columnsTemplate('1fr 1fr 1fr')
.rowsTemplate('1fr 1fr')
.width('100%')
.height(300)
.margin( {top:100} )

Button('立即抽卡')
.width(200)
.backgroundColor('#ed5b8c')
.margin({top: 50})
.onClick( () => {
//点击时,修改遮罩参数,让遮罩显示
this.maskOpacity = 1
this.maskZIndex = 99
//点击时,图片需要缩放
this.maskImgX = 1
this.maskImgY = 1

//计算随机数 Math.random(),[0,1]
this.randomIndex = Math.floor(Math.random() * 6)
})
}
.width('100%')
.height('100%')

//抽卡遮罩层(弹层)
Column( {space:30} ) {
Text('获得生肖卡')
.fontColor('#f5ebcf')
.fontSize(25)
.fontWeight(FontWeight.Bold)
Image($r(`app.media.img_0${this.randomIndex}`))
.width(200)
//控制元素的缩放
.scale({
x:this.maskImgX,
y:this.maskImgY
})
.animation({
duration: 500
})
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

//开心收下,对象数组的情况需要更新,需要修改替换整个对象
this.images[this.randomIndex] = {
url: `app.media.img_0${this.randomIndex}`,
count: this.images[this.randomIndex].count + 1
}

//每次收完卡片,需要进行简单的检索,判断是否集齐
//需求:判断数组项的count,是否都大于0,只要有一个等于0,就意味着没集齐
let flag: boolean = true //假设集齐
//验证是否集齐
for (let item of this.images) {
if (item.count == 0) {
flag = false //没集齐
break //后面的没必要判断了
}
}

this.isGet = flag

//判断是否中奖了,如果是 需要抽奖
if (flag) {
let randomIndex: number = Math.floor(Math.random() * 3)
this.prize = this.arr[randomIndex]
}
})
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height('100%')
.backgroundColor('#cc000000')
//设置透明度
.opacity(this.maskOpacity)
.zIndex(this.maskZIndex)
//动画 animation,当我们元素有状态的改变,可以添加animation做动画
.animation({
duration: 200
})

// 抽大奖的遮罩层
if (this.isGet) {
Column( {space: 30} ) {
Text('恭喜获得手机一部')
.fontColor('#f5ebcf')
.fontSize(25)
.fontWeight(700)
Image($r(`app.media.${this.prize}`))
.width(300)
Button('再来一次')
.width(200)
.height(50)
.backgroundColor(Color.Transparent)
.border( {width: 2, color: '#fff9e0'} )
.onClick( () => {
this.isGet = false
this.prize = ''
this.images= [
{url: 'app.media.bg_00', count: 0},
{url: 'app.media.bg_01', count: 0},
{url: 'app.media.bg_02', count: 0},
{url: 'app.media.bg_03', count: 0},
{url: 'app.media.bg_04', count: 0},
{url: 'app.media.bg_05', count: 0}
]
})
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height('100%')
.backgroundColor('#cc000000')
}
}
}
}

Swiper轮播组件

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

基本用法

语法结构

1
2
3
4
5
6
7
8
9
10
Swiper() {
//轮播内容
Image($r('...'))
Image($r('...'))
Image($r('...'))
Image($r('...'))
}
//设置尺寸(内容自动拉伸)
.width('100%')
.height(100)
常见属性
属性方法 传值 作用 默认值
loop boolean 是否开启循环 true
autoPlay boolean 是否自动播放 false
interval number 自动播放的时间间隔(ms) 3000
vertical boolean 纵向滑动轮播 false
…其他属性见官网
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Swiper() {
//轮播内容
Image($r('...'))
Image($r('...'))
Image($r('...'))
Image($r('...'))
}
//设置尺寸(内容自动拉伸)
.width('100%')
.height(100)
.loop(true)
.autoPlay(true)
.interval(3000)
.vertical(false)
样式自定义
1
2
3
4
5
6
7
8
9
10
11
12
Swiper() {
...
}
.indicator(
Indicator.dot() //小圆点
.itemWidth(20) //默认的宽
.itemHeight(20) //默认的高
.color(Color.Black) //默认的颜色
.selectedItemWidth(30) //选中的宽
.selectedItemHeight(30) //选中的高
.selectedColor(Color.White) //选中的颜色
)

设置宽高比例:.aspectRatio(数字)

样式&结构重用

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

作用:扩展组件的样式、事件,实现复用效果

1
2
3
4
5
6
7
8
9
10
11
12
13
@Extend (Text)
function bannerItem (bgColor: ResourceColor, msg: string) {
.textAlign(TextAlign.Center)
.backgroundColor(bgColor)
.fontColor(Color.White)
.fontSize(30)

.onClick(() => {
AlertDialog.show({
message: msg
})
})
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//全局定义
@Style function commonStyles() {
.width(100)
.height(200)
.onClick( ()=>{
//...
})
}

@Component
struct FancyDemo {
//组件内定义
@Styles setBg() {
.backgroundColor(Color.Red)
}

builder() {
Text()
.commonStyles()
.setBg()
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//全局Builder
@Builder
function navItem(icon: ResourceStr, text: string) {
Column({ space: 10 }) {
Image(icon)
.width('80%');
Text(text);
}
.width('25%')
.onClick(()=>{
AlertDialog.show({
message: '点了' + text
})
})
}

局部builder类似,只是定义在组件内,this.xxx()

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
//全局Builder
@Builder
function navItem(icon: ResourceStr, txt: string) {
Column({ space: 10 }) {
Image(icon)
.width('80%')
Text(txt)

}
.width('25%')
.onClick(() => {
AlertDialog.show({
message: '点了' + txt
})
})
}

@Entry
@Component
struct BuilderDemo {
@State message: string = '@Builder';
//局部 Builder
@Builder
navItem(icon: ResourceStr, txt: string) {
Column({ space: 10 }) {
Image(icon)
.width('80%')
Text(txt)

}
.width('25%')
.onClick(() => {
AlertDialog.show({
message: '点了' + txt
})
})
}

build() {
Column({ space: 20 }) {
Text(this.message)
.fontSize(30)

Row() {
Row() {
navItem($r('app.media.ic_reuse_01'),'阿里拍卖')

navItem($r('app.media.ic_reuse_02'),'菜鸟')

this.navItem($r('app.media.ic_reuse_03'),'巴巴农场')

this.navItem($r('app.media.ic_reuse_04'),'阿里药房')
}
}
}
.width('100%')
.height('100%')
}

}

滚动容器Scroll

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

核心用法

用法说明:

1.Scroll设置尺寸

2.设置溢出的子组件(只支持一个子组件)

3.滚动方向(支持横向和纵向,默认纵向)

语法结构:

1
2
3
4
5
6
7
8
9
10
Scroll () {
//只支持一个子组件
Column() {
//内容放在内部
//尺寸超过Scroll即可滚动
}
}
.width('100%')
.height(200)
.scrollable(ScrollDirection.xxx)
名称 参数类型 描述
scrollable ScrollDirection 设置滚动方向。
ScrollDirection.Vertical 纵向
ScrollDirection.Horizontal 横向
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
@Entry
@Component
struct Index {
build() {
Column() {
Scroll() {
Column({ space: 10 }) {
ForEach(Array.from({ length: 5 }), (item: string, index) => {
Text('测试文本' + (index + 1))
.width('100%')
.height(100)
.textAlign(TextAlign.Center)
.backgroundColor(Color.Orange)
.fontSize(20)
.fontColor(Color.White)
.borderRadius(10)
})
}
.padding(10)
.width('100%')
}
.scrollable(ScrollDirection.Horizontal)
.width('300%')
.height(500)
}
}
}
常见属性
名称 参数类型 描述
scrollable ScrollDirection 设置滚动方向。
ScrollDirection.Vertical 纵向
ScrollDirection.Horizontal 横向
scrollBar BarState(on | off | auto) 设置滚动条状态
scrollBarColor string | number | Color 设置滚动条的颜色
scrollBarWidth string | number 设置滚动条的宽度
edgeEffect value:edgeEffect 设置边缘滚动效果。
EdgeEffect.None 无
EdgeEffect.Spring 弹簧
EdgeEffect.Fade 阴影
控制器

核心步骤:

1.实例化Scroller的控制器

2.绑定给Scroll组件

3.控制器的方法控制滚动,控制器属性获取滚动距离

scrollEdge(Edge.xxx)控制滚动,属性:top | buttom

currentOffset().xOffset和currentOffset().yOffset两个都可以获取滚动的距离只是方向的区别

容器组件Tabs

当页面内容较多时,可以通过Tabs组件进行分类展示

基本使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct TabbarDemo {
build() {
//外层顶级容器
Tabs() {
TabContent() {
//内容区域:只能一个子组件
Text('首页内容')
}
.tabBar('首页') //导航栏

TabContent() {
Text('推荐内容')
}
.tabBar('推荐')
....
}
}
}
常用属性
属性 描述
barPosition 调整位置 开头 或 结尾(参数 Start | End)
vertical 调整导航 水平 或 垂直 (垂直导航true | 水平false)
scrollable 调整是否手势滑动切换 (允许滑动true | 不允许false)
animationDuration 点击滑动动画时间 (ms)
1
2
3
4
5
6
Tabs( { barPositon: BarPosition.End }) {
//内容...
}
.vertical(true) //垂直导航 true | 水平false
.scrollable(true) //允许滑动 true | 不允许 false
.animationDuration //切换动画的时间,毫秒
滚动导航栏

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

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

1
2
3
4
5
Tabs() {
//内容....
}
.barMode(BarMode.Scrollable) //滚动
//.barMode(BarMode.Fixed) //默认值
自定义TabBar

TabBar在底部,一般会显示图形和文字,甚至有特殊的图标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Builder
tabBarBuilder(title: string, img: ResourceStr) {
Column() {
Image(img)
.width(30)
Text(title)
}
}

Tabs() {
TabContent() {
//内容...
}
.tabBar(this.tabBarBuilder('xxx'),$r('app.media.xxx'))
}
自定义TabBar高亮切换

核心思路:

1.监听切换时间 -> 的搭配索引值,记录高亮的索引

2.给每个tabBar起个标记,0,1,2

3.在tabBar内部比较 标记 == 记录的索引 ? 高亮 : 不高亮

名称 功能描述
onChange(event: (index: number) => void) Tab页签切换后触发的事件。
-index:当前显示的index索引,索引从0开始计算。
滑动切换、点击切换均会触发
onTabBarClick(event: (index: number) => void)10+ 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
@State selectedIndex: number = 0
@Builder
tabBarBuilder(itemIndex: number, title: string, img: ResourceStr, selImg: ResourceStr ) {
//如果激活的是自己,图片/文本 都需要调整样式 -> 需要区分不同的tabBar
Column() {
Image(itemIndex == this.selectedIndex ? selImg : img)
.width(30)
Text(title)
.fontColor( itemIndex == this.selectedIndex ? Color.Red : Color.Black)
}
}

Tabs() {
TabContent() {
//内容...
}
.tabBar(this.tabBarBuilder('xxx'),$r('app.media.xxx'), $r('app.media.xxx_selected')
}

onChange( (index: number)=> {
this.selectedIndex = index
})

Class类

类时用于创建对象模板。同时类声明也会引入一个新类型,可定义其实例属性、方法和构造函数。

1
2
3
4
5
6
7
8
9
10
11
//类名 首字母大写 (规范)
class 类名{
//1.实例属性(字段)

//2.构造函数

//3.方法
}

//使用类 实例化对象 基于类 创建对象
const p:类名 = new 类名()
实例属性(字段)

通过实例属性(字段),可以保存各种类型的数据

1
2
3
4
5
6
7
8
9
10
11
12
// 类
class 类名{
//字段名、类型、初始值
字段名1:类型='xxx'
//可选字段可以不设置初始值
字段名2?:类型
}

//可选字段在使用时需要配合 可选链操作符 避免出错
const p: 类名 = new 类名()
p.字段名1
p?.字段名2
构造函数

不同实例,将来需要有不同的字段初始值,就需要通过构造函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class 类{
字段A:类型
字段B:类型
constructor(参数...) {
//通过new实例化的时候 会调用constructor
//通过关键字 this 可以获取到实例对象
this.字段名A = 参数
}
}

const 实例1 = new 类(参数...)
const 实例2 = new 类(参数...)


class Food{
name: string
price: number
constructor(name:string, price:number) {
this.name = name
this.price = price
}
}
const f1 = new Food('西红柿鸡蛋', 15)
const f1 = new Food('土豆炖鸡块', 24)
定义方法

类中可以定义方法,并且在内部编写逻辑

1
2
3
4
5
6
class 类名{
方法名(参数...):返回值类型{
//逻辑...
//可以通过this获取实例对象
}
}
静态属性和方法

类还可以添加静态属性、方法,后续访问需要通过类来完成。

1
2
3
4
5
6
7
8
9
//定义
class 类{
static 字段:类型
static 方法(){}
}

//使用
类.字段
类.方法()
继承extends和super关键字

类可以通过继承快速获取另外一个类的字段和方法

1
2
3
4
5
6
7
8
9
10
11
class 父类{
//字段
//方法
//构造函数
}

class 子类 extends 父类{
//自己的字段(属性)
//自己的方法
//可以重写父类方法
}

子类通过super可以访问父类的实例字段、实例方法和构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class 父类 {
func(){

}
}

class 子类 extends 父类 {
constructor() {
super() //调用父类构造函数
}
方法(){
super.方法() //调用父类方法
}
}
instanceof

instanceof运算符可以用来检测某个对象是否是某个类的实例

1
实例对象 instanceof Class
修饰符-readonly

类的方法和属性可以通过修饰符来限制访问

修饰符包括:readonly、private、protected和public。省略不写默认为public

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//修饰符 readonly
class Cat {
name: string
age: number
readonly legs: number = 4

constructor(name: string, age: number) {
this.name = name
this.age = age
}
}

let c1 = new Cat('小花'2)
c1.name = '小美'
c1.legs = 6 //此处无法修改,会报错
console.log('姓名:', c1.name)
修饰符-private

private修饰的成员不能再声明该成员的类之外访问,包括子类

1
2
3
4
class 类{
private 属性:类型
private 方法(){}
}
修饰符-protected

protected修饰符的作用与private修饰符非常相似

不同点是protected修饰的成员允许在派生类(子类)中访问

1
2
3
4
class 类{
protected 属性:类型
protected 方法(){}
}
修饰符名 作用 访问限制
readonly 只读 无限制
private 私有 类内部可以访问
protected 保护 类及子类可以访问
public 公共 无限制

剩余参数和展开运算符

剩余参数:将函数和方法中一个不定数量的参数表示为一个数组

1
2
3
function 函数名(参数1, 参数2, ...剩余参数数组) {

}

展开运算符:出于程序稳定性,以及运行性能考虑,在ArkTS中... (展开运算符) 只能用在数组上

1
2
3
4
5
const numArr1: number[] = [1,2,3,4]
const numArr2: number[] = [5,6,7]

//展开运算符合并
const numArr: number[] = [...numArr1, ...numArr2]

接口继承和实现

接口继承使用的关键字是extends,与class类的继承相似

1
2
3
4
5
6
7
interface 接口1 {
属性1:类型
}

interface 接口2 extends 接口1 {
属性2:类型
}

接口实现:

可以通过接口结合implements来限制类,必须要有某些属性和方法

1
2
3
4
5
6
7
8
interface 接口 {
属性:类型
方法:方法类型
}

classimplements 接口 {
//需要实现 接口中定义的属性和方法,未实现将报错
}

泛型

泛型可以让【函数】等,与多种【不同的类型】一起工作,灵活可复用,即类型可变

1
2
3
4
5
6
function 函数名 <Type>(temp:Type) {
return temp
}

fn <string> ('123')
fn <number> (1)

泛型函数

封装一个函数,传入什么样的参数,就立刻返回什么样的参数

同时ArkTs会默认根据传参,进行类型推断,动态的配置T类型参数的值

1
2
3
4
5
6
7
8
9
10
11
functioni fn<T> (param: T) : T {
return param
}
fn<string> ('abc')
fn<number> (123)
fn<boolean> (true)
fn<number[]> ([1,2,3,4,5])

//同时也可以不用写类型
fn(true)
fn([1,2,3,4,5])

泛型约束

之前的类型参数,可以传递任何类型,没有限制。

如果希望有限制 -> 泛型约束

1
2
3
4
5
interface 接口 {
属性:类型
}
function 函数<Type extends 接口>(){}
//传入的类型必须要有接口中的属性

多个泛型参数

日常开发的时候,如果有需要,可以添加多个类型变量

1
2
3
4
5
6
7
function funcA<T, T2> (param1: T, param2: T2) {
console.log('参数1', param1)
console.log('参数2', param2)
}

funcA<string, number> ('大白菜', 998)
funcA<string[], boolean[]> (['小老虎'], [false])

泛型接口

定义接口的时候,结合泛型定义,就是泛型接口

1
2
3
interface 接口<Type> {
//内部使用Type
}
1
2
3
4
5
6
7
8
9
interface IdFunc<Type> {
id: (value: Type) => Type
ids: () => Type[]
}

let obj: IdFunc<number> = {
id(value) { return value },
ids() { return [1, 3, 5] }
}

泛型类

定义类的时候,结合泛型定义,就是泛型类

1
2
3
class 类名<Type>{
//内部可以使用 Type
}
1
2
3
4
5
6
7
8
9
10
11
12
13
//定义
class Person <T> {
id: T
constructor(id: T) {
this.id = id
}
getId(): T {
return this.id
}
}

//使用
let p = new Person<number>(10)

模块化语法

模块化: 把一个大的程序,【拆分】成若干的小的模块,通过【特定的语法】,进行任意组合

默认导入导出

默认导出:指一个模块,只能默认导出的一个值或对象。使用时,可以自定义导入名称。

一个ets文件就是一个模块,每个模块之间都是独立的

使用步骤:

1.当前模块中导出模块

2.需要使用的地方导入模块

1
2
//默认导出
export default 需要导出的内容
1
2
//默认导入
import xxx from '模块路径'

按需导入和导出

按需导出:指一个模块,可以按照需要,导出多个特性。

1
2
3
4
5
6
7
8
9
10
// 逐个导出单个特性
export let name1 = ..., name2 = ..., ..., nameN;
export function FunctionName(){...}
export class ClassName {...}

//一次性导出
export {name1, name2, ..., nameN};

//导入
import {name1, name2, name3 as 别名} from "module-name";

全部导入

将所有的按需导入,全部导入进来 -> 导出部分不需要调整,调整导入的语法即可

1
2
import * as Untils from './untils'
//通过 Untils 即可获取untils模块中导出的所有内容

自定义组件

基本使用

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

1
2
3
4
5
6
7
8
9
//定义
@Component
struct HelloComponent {
//状态变量
@State message:string = ''
build() {
//...描述UI
}
}

通用样式事件

自定义组件可以通过点语法,设置通用样式,通用事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//引用组件样式并添加属性
@Entry
@Component
struct Index {
build() {
Column() {
HelloComponent()
.width(200)
.height(100)
.backgroundColor(Color.Orange)
onClick(() => {
console.log('外部添加的点击事件')
})
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
//自定义组件样式
@Preview
@Component
export struct HelloComponent {
@State info: string = '默认info'
build() {
Row() {
Text(this.info)
Button('修改数据')
}
}
}

引用自定义组件样式,额外添加的属性是添加在整个组件的外层,并非是自定义组件中的某个Row或者Column。

成员函数变量

除了必须实现build()函数外,还可以定义其他的成员函数,以及成员变量。

成员变量的值 -> 外部可传参覆盖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
struct HelloComponent {
//状态变量
@State msg:string = ''
//成员变量-数据
info:string = ''
//成员变量-函数
sayHello = () => {}

//成员函数
sayHi(){}
build(){
//...描述UI
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Entry
@Component
struct CustomComponentDemo {
build() {
Column() {
//使用组件内部定义的初始值
HelloComponent()
//使用传入的值,覆盖子组件的默认值
HelloComponent({info: '你好', msg: 'ArkTS'})
//函数也可以传入
HelloComponent({sayHello(){console.log('传入的逻辑')}})
}
}
}

@BuilderParam传递UI

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

1
2
3
4
5
6
7
8
9
10
11
12
@Entry
@Component
struct Index {
build() {
Column({space: 15}) {
SonCom() {
//直接传递进来(尾随闭包)
Button('待付款')
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component
struct SonCom {
//定义 BuilderParam 接受外部传入的ui,并设置默认值
@BuilderParam ContentBuilder: () => void = this.defaultBuilder
//默认的Builder
@Builder
defaultBuilder() {
Text('默认的内容')
}
build() {
Column() {
//使用@BuilderParam装饰的成员变量
this.ContentBuilder()
}
}
}

多个@BuilderParam参数

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component
struct SonCom {
//由外部传入UI
@BuilderParam tBuilder: () => void = this.tDefaultBuilder
@BuilderParam cBuilder: () => void = this.cDefaultBuilder

//设置默认Builder
@Builder
tDefaultBuilder() {...}
@Builder
cDefaultBuilder() {...}

build() {
Column() {
this.tBuilder
this.cBuilder
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Entry
@Component
struct Index {
@Builder
fTBuilder() {...}
@Builder
fCBuilder() {...}

build() {
Column({space: 15}) {
SonCom({
tBuilder: this.fTBuilder,
cBuilder: this.fTBuilder
})
}
}
}

状态管理

当运行时的状态变量变化,带来UI的重新渲染,在ArkUI中统称为状态管理机制

变量必须被装饰器装饰才可以称为状态变量。

image-14

image-15

@State 自己的状态

注意:不是状态变量的所有更改都会引起刷新。只有可以被框架观察到的修改才会引起UI刷新。

1.boolean、string、number类型时,可以观察到数值的变化

2.class或者Object时,可观察自身的赋值的变化,第一层属性赋值的变化,即Object.keys(observedObjecr)返回的属性。

1
2
3
4
5
6
7
8
//状态变量
@State message: string = 'Hello,World';
@State person: Person = {
name: 'jack',
dog: {
name:'柯基'
}
}
1
2
3
4
5
6
7
8
9
10
11
12
Button('修改title外层属性')
.onClick(()=>{
this.person.name = '666'
})
Button('修改title嵌套属性')
.onClick(() => {
//无法触发更新
//this.person.dog.name = '内部的 666'
this.person.dog = {
name: '阿拉斯加'
}
})

@Prop-父子单向

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

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

image-16

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Entry
@Component

@State info: string = 'xxx'

build() {
Column() {
Text(this.info)
SonCom({
info: this.info,
changeInfo: (newInfo: string) => {
this.info = newInfo
}
})
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
struct SonCom {
@Prop info: string
changeInfo = (newInfo: string) => {

}
build() {
Button('info' + this.info)
.onClick(() => {
this.changeInfo('改变')
})
}
}

1.prop传值 -> 单向传递

子组件,可以修改到prop传值,但是修改的更新不会同步到父组件

通常不太会直接修改prop传值,父组件的状态一旦变化,会自动向下同步

修改就被覆盖了

2.如果实在想更新,希望保证父子同步 => 调用父组件传递过来的方法

如果没有写箭头函数,意味着,this指向调用者,而此处执行环境this -> 子组件