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.通过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
2.基础组件:文字Text、图片
纵向布局
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)
|
字体颜色
语法:
| 颜色值说明 |
具体演示 |
| 枚举颜色 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( $r('app.media.文件名') )
|
输入框与按钮
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)
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)
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)

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


自适应伸缩
设置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) }
|

案例-京东
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)
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('我已阅读并同意') 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) } }
|

弹性布局(flex)

弹性容器组件: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
作用:控制组件位置,可以实现层叠效果

特点:
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(){ 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%') } .height(240) .width(160) .backgroundColor(Color.White) } .height('100%') .width('100%') .backgroundColor(Color.Pink) } }
|

层叠布局
层叠布局具有较强的组件层叠能力。场景:卡片层叠效果等
特点:层叠操作更简洁,编码效率高。(绝对定位的优势是更灵活)
语法:
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') } }
|

界面开发实战-支付宝
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({ 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() { 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 }) 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%')
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: '这是个弹框' }) })
|
状态管理
之前构建的页面多为静态界面。
但如果希望构建一个动态地、有交互的界面,就需要引入”状态“的概念

点击交互触发了文本状态变更,状态变更引起了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{ @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-- }) 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,……)
语句
语句:一段可以执行的代码,是一种行为
表达式:可以被求值的代码,并将其计算出一个结果
语句执行结构:

分支语句
if分支语句
if分支语句:根据逻辑条件不同,执行不同语句。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| if (逻辑条件) { 条件成立执行的代码 }
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(() => { if (this.count > 1) { this.count-- } else { 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 () { case 值1: 与值1匹配执行的语句 break case 值2: 与值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() { 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})
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
| interface Person { stuId: number name: string gender: string age: number }
let p1: Person = { stuId: 1, name: '小丽', gender: '女', age: 12 }
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布局

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) } }
|

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
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 }
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({ 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 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 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 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的尺寸时,内容可以滚动
核心用法
用法说明:
1.Scroll设置尺寸
2.设置溢出的子组件(只支持一个子组件)
3.滚动方向(支持横向和纵向,默认纵向)
语法结构:
1 2 3 4 5 6 7 8 9 10
| Scroll () { Column() { } } .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) .scrollable(true) .animationDuration
|
滚动导航栏
如果导航栏的内容较多,屏幕无法容纳时,可以将他设置为滚动
可以通过Tabs组件的barMode属性即可调整固定导航栏或滚动导航栏
1 2 3 4 5
| Tabs() { } .barMode(BarMode.Scrollable)
|
自定义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 ) { 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 类名{ }
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(参数...) { 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 类名{ 方法名(参数...):返回值类型{ } }
|
静态属性和方法
类还可以添加静态属性、方法,后续访问需要通过类来完成。
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运算符可以用来检测某个对象是否是某个类的实例
修饰符-readonly
类的方法和属性可以通过修饰符来限制访问
修饰符包括:readonly、private、protected和public。省略不写默认为public
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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 接口 { 属性:类型 方法:方法类型 }
class 类 implements 接口 { }
|
泛型
泛型可以让【函数】等,与多种【不同的类型】一起工作,灵活可复用,即类型可变
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> { }
|
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 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 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'
|
自定义组件
基本使用
由框架直接提供的称为系统组件,由开发者定义的称为自定义组件。
1 2 3 4 5 6 7 8 9
| @Component struct HelloComponent { @State message:string = '' build() { } }
|
通用样式事件
自定义组件可以通过点语法,设置通用样式,通用事件
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(){ } }
|
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 ContentBuilder: () => void = this.defaultBuilder @Builder defaultBuilder() { Text('默认的内容') } build() { Column() { 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 { @BuilderParam tBuilder: () => void = this.tDefaultBuilder @BuilderParam cBuilder: () => void = this.cDefaultBuilder @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中统称为状态管理机制
变量必须被装饰器装饰才可以称为状态变量。


@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: '阿拉斯加' } })
|
@Prop-父子单向
@Prop 装饰器的变量可以和父组件建立单向的同步关系。
@Prop 装饰器的变量是可变的,但是变化不会同步回其父组件。

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 -> 子组件