Harmony构建应用
字符串拼接
把两个或多个字符串,拼成一个字符串。
加号+
加法两端只要有字符串,就是拼接
1 2 3
| let name: string = '小明' let age: number = 18 console.log('简介信息', '姓名是' + name + ',今年' + age + '岁了')
|
模板字符串'${...}
'
作用:拼接字符串和变量
优势:更适合于 多个变量 的字符串拼接
1 2 3
| let name: string = '小明' let age: number = 18 console.log('简介信息', `姓名是${name},今年${age}岁了`
|
类型转换(数字和字符串)
字符串转数字
- Number():字符串 直接转数字,转换失败返回NaN(字符串中包含非数字)
- parseInt():去掉小数部分 转数字,转换失败返回NaN
- parseFloat():保留小数部分 转数字,转换失败返回NaN
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| let str1: string = '1.1' let str2: string = '1.9' let str3: string = '1.192a' let str4: string = 'a'
console.log('数字是',Number(str1)) console.log('数字是',Number(str2)) console.log('数字是',Number(str3)) console.log('数字是',Number(str4))
console.log('数字是',parseInt(str1)) console.log('数字是',parseInt(str2)) console.log('数字是',parseInt(str3)) console.log('数字是',parseInt(str4))
console.log('数字是',parseFloat(str1)) console.log('数字是',parseFloat(str2)) console.log('数字是',parseFloat(str3)) console.log('数字是',parseFloat(str4))
|
数字转字符串
- toString():数字直接转字符串
- toFixed():四舍五入 转字符串,可设置保留几位小数
1 2 3 4 5 6 7 8 9 10 11
| let num1: number = 1.1 let num2: number = 1.9 console.log('字符串是', num1.toString()) console.log('字符串是', num2.toString())
let num1: number = 1.1 let num2: number = 1.9 let num3: number = 1.9152 console.log('字符串是', num1.toFixed()) console.log('字符串是', num2.toFixed()) console.log('字符串是', num3.toFixed(2))
|
交互:点击事件
说明:组件 被点击时 触发的事件
作用:监听(感知)用户 点击行为,进行对应操作
语法:.onClick( (参数) => {} )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
Row(){ Column(){ Button('点我,显示弹窗') .onClick(()=>{ AlertDialog.show({ message:'你好-这是一个弹窗' }) }) Text("我是文本") .onClick(()=>{ AlertDialog.show({ message:'你好-我是一个文本' }) }) } .width('100%') }
|
效果:
状态管理
之前构建的页面多为静态界面, 如果希望构建一个动态的、有交互的界面,就需要引入“状态”的概念
- 普通变量:只能在初始化时渲染,后续将不会再刷新
- 状态变量:需要装饰器装饰,改变会引起 UI 的渲染刷新 (必须设置 类型 和 初始值)
使用状态变量,需要在定义变量时在其前面添加**@State
**
计数器案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Entry @Component struct Index { @State count: number = 0 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
| @State count: number = 8888 @State myColor: string = '#7e7e7e'
build(){ Column({space:10}){ Image($r('app.media.eyes')) .width(200) .borderRadius({topLeft:10, topRight:10}) Text("考眼力又来了你能看到几只鸭子?") .width(200) Row(){ Row({space:5}){ Image($r('app.media.avatar')) .width(18) Text("视野联行眼镜") .fontSize(15) .fontColor('#666') } .layoutWeight(1) Row({space:3}){ Image($r('app.media.ic_love')) .width(15) .fillColor(this.myColor) Text(this.count.toString()) .fontSize(15) .fontColor(this.myColor) } .onClick(() => { this.myColor = '#ff0000' this.count += 1 }) } .width(200) } .borderRadius(10) .width('100%') .height(600) }
|
效果:
点赞前:
一元运算符
常见一元运算符:++和–
- 后置写法:先赋值后自增/自减
- 前置写法:先自增/自减再赋值
1 2 3 4 5
| let num: number = 10 let res: number = num++
let num2: number = 10 let res2: number = ++num
|
比较运算符
比较运算符 |
作用 |
> |
判断大于 |
>= |
判断大于等于 |
< |
判断小于 |
<= |
判断小于等于 |
== |
判断相等 |
!= |
判断不相等 |
逻辑运算符
逻辑运算符 |
作用 |
&& |
与,都真才真 |
|| |
或,一真为真 |
! |
非,取反 |
运算符优先级
优先级顺序 |
|
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 129 130 131 132 133 134 135 136 137
| @Entry @Component struct Index { @State count: number = 1 @State oldPrice: number = 40.4 @State newPrice: number = 20.2 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('-') .onClick(()=>{ if (this.count > 1){ this.count -- }else { AlertDialog.show({ message: "最小数量为1,不能再减了" }) } }) .width(22) .height(22) .border({width:1, color: '#e1e1e1', radius: {topLeft: 5, bottomLeft: 5}}) .textAlign(TextAlign.Center) .fontWeight(700) Text(this.count.toString()) .height(22) .border({width: { top:1, bottom: 1 }, color: '#e1e1e1'}) .padding({left: 10, right: 10}) .fontSize(14) Text('+') .onClick(()=>{ this.count ++ }) .width(22) .height(22) .border({width:1, color: '#e1e1e1', radius: {topRight: 5, bottomRight: 5}}) .textAlign(TextAlign.Center) .fontWeight(700) } } .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.toString()}件,`) .fontColor('#848484') Span('合计:') Span('¥') .fontColor('#fd4104') Span(`${(this.newPrice * this.count).toFixed(2)}`) .fontColor('#fd4104') .fontSize(16) } .fontSize(14) Text(`共减¥${((this.oldPrice-this.newPrice)*this.count).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, ……) |
查找&修改
查找:数组名[下标]
修改: 数组名[下标] = 新值
数组长度:数组名.length
1 2 3 4 5 6 7 8
| let names: string[] = ['小明', '小红', '大强', '小飞'] console.log('查找姓名', names[0]) console.log('数组长度为', names.length) names[1] = 'Jack' console.log('names数组', names)
|
增加数组元素
往开头加: 数组名.unshift(数据1, 数据2, 数据3, ......)
结尾添加:数组名.push(数据1, 数据2, 数据3, ......)
1 2 3 4 5 6 7
| let songs: string[] = ['告白气球', '洋葱', '吻别']
songs.unshift('你是我的眼')
(返回操作后数组的长度) songs.push('光辉岁月', '海阔天空') console.log('数组songs', songs)
|
删除数组元素
从开头删: 数组名.shift()
从结尾删: 数组名.pop()
1 2 3 4 5 6 7
| let songs: string[] = ['告白气球', '洋葱', '吻别']
songs.shift() console.log('数组songs', songs)
songs.pop() console.log('数组songs', songs)
|
任意位置添加/删除数组元素
语法:数组名.splice(起始位置, 删除的个数, 新增元素1, 新增元素2, ......)
1 2 3 4 5 6
| let songs: string[] = ['告白气球', '洋葱', '吻别']
songs.splice(2, 1) console.log('数组songs', songs) songs.splice(1, 0, '淘汰','蝴蝶') console.log('数组songs', songs)
|
语句
语句:一段可以执行的代码,是一个行为
表达式:可以被求值的代码,并将其计算出一个结果
语句执行结构:
分支语句
if分支
根据逻辑条件不同,执行不同语句
小括号条件结果为 true,则执行大括号里面的代码
1 2 3 4 5 6 7 8 9 10 11 12
| if (条件1) { 条件1成立执行的代码 } else if (条件2) { 条件2成立执行的代码 } else if (条件3) { 条件3成立执行的代码 } else { 都不成立执行的代码 }
|
购物车数字框案例
需求:
购物车商品 数量大于1 可以单击“-”按钮
否则 提示 “最小数量为1,不能再减了”
1 2 3 4 5 6 7 8 9 10
| Text('-') .onClick(()=>{ if (this.count > 1){ this.count -- }else { AlertDialog.show({ message: "最小数量为1,不能再减了" }) } })
|
Switch分支
一般用于精确匹配,不同的值执行不同的代码
如果没有break语句,则会直接执行 switch 中的下一个代码块(无论是否匹配成功)
1 2 3 4 5 6 7 8 9 10
| switch (表达式) { case 值1: 与值1匹配执行的语句 break case 值2: 与值2匹配执行的语句 break default: 以上都未成功匹配执行的代码 }
|
三元条件表达式
语法:条件?条件成立执行的表达式:条件不成立执行的表达式
1 2 3 4 5
| let num1: number = 5 let num2: number = 10
let res: number = num1 > num2 ? num1 : num2 console.log('結果是', res)
|
条件渲染
使用if、else和else if ,可基于 不同状态渲染对应不同UI内容
1 2 3 4 5 6 7 8 9 10 11 12
| @State counter: number = 1 build() { Column() { if (this.counter == 1) { Text('1') } else if (this.counter == 2){ Text('2') } else { Text('any') } } }
|
循环语句
循环三要素:
- 初始值(变量)
- 循环条件
- 变化量(变量计数,自增或自减)
while语句
1 2 3 4 5 6 7 8 9 10
| while (条件) { 条件成立重复执行的代码 }
let i: number = 1 while (i < 5) { console.log('while~i', '重复执行的代码') i++ }
|
for语句
1 2 3 4 5 6 7
| for (初始值; 条件; 变化量) { 重复执行的代码 }
for (let i: number = 0; i < 5; i++) { console.log('for', '重复执行的代码') }
|
退出循环:满足指定条件,可以退出循环
break:终止整个循环
continue: 退出当前一次循环的执行,继续执行下一次循环
遍历数组
1 2 3 4 5 6 7 8
| let names: string[] = ['小红', '小明', '大强'] for(let i = 0; i < names.length; i++) { console.log('名字是', names[i]) }
for (let item of names) { console.log('名字是', item) }
|
for ... of ...
:
语法: for (let item of 数组名) {}
for … of : 在 … 之中 进行循环
item: 声明的一个变量, 用来在循环的时候接收每一个数组元素
对象数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| interface Person { stuId: number name: string gender: string age: number }
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 }, ]
|
ForEach-渲染控制
ForEach 可以基于数组的个数,渲染组件的个数。
语法: ForEach(arr, (item, index) => {})
1 2 3 4 5 6 7 8 9 10 11 12 13
| @State titles:string[] = ['电子产品', '精品服饰', '母婴产品', '影音娱乐', '海外旅游']
Column() { ForEach(this.titles, (item: string, index) => { Text(item) .fontSize('24') .fontWeight(700) .fontColor(Color.Orange) .width('100%') .padding(15) }) }
|
效果:
案例-新闻列表
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
| @State articles: Article[] = [ { title: '近200+自动驾驶数据集全面调研!一览如何数据闭环全流程', createTime: '2024-01-31 09:59:43' }, { title: 'MySQL Shell 8.0.32 for GreatSQL编译二进制包', createTime: '2024-01-31 09:55:53' }, { title: '在Redis中如何实现分布式事务的一致性?', createTime: '2024-01-31 09:54:51' }, ]
Column(){ ForEach(this.articles, (item: Article, index)=>{ Column(){ Row(){ Text(item.title) .width('100%') .fontSize(16) .lineHeight(20) } Row(){ Text(item.createTime) .width('100%') .margin({top:15}) .fontSize(12) .fontColor('#ff646464') } .border({width:{bottom:2},color:{bottom:'#f4f4f4'}}) } .width('100%') .padding(10) }) }
|
效果:
阶段综合-生肖抽卡
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.bg_01')) .width(80) }
|
效果:
Grid布局
Grid布局基本使用:规则的行列布局中非常常见
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| Column(){ Grid(){ ForEach([1,2,3,4,5,6],()=>{ GridItem(){ Badge({ count: 1, position: BadgePosition.RightTop, style:{ badgeSize: 20, fontSize: 14, badgeColor: '#fa2a2d' } }){ Image($r('app.media.bg_01')) .width(80) } } }) } .columnsTemplate('1fr 1fr 1fr') .rowsTemplate('1fr 1fr') .width('100%') .height(300) .margin({top:100})
Button('立即抽卡') .width(200) .backgroundColor('#ed5b8c') .margin({top:50}) }
|
动态渲染
每个列表项 – 两个数据:
- 图片的地址
- 抽中的数量
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
| interface ImageCount { url: ResourceStr count: number }
@State images: ImageCount[] = [ { url: $r('app.media.bg_00'), count: 0 }, { url: $r('app.media.bg_01'), count: 1 }, { url: $r('app.media.bg_02'), count: 2 }, { url: $r('app.media.bg_03'), count: 3 }, { url: $r('app.media.bg_04'), count: 4 }, { url: $r('app.media.bg_05'), count: 5 } ]
Grid() { ForEach(this.images, (item: ImageCount, index: number) => { GridItem() { Badge({ count: item.count, position: BadgePosition.RightTop, style: { fontSize: 14, badgeSize: 20, badgeColor: '#fa2a2d' } }) { Image(item.url) .width(80) } } }) } .columnsTemplate('1fr 1fr 1fr') .rowsTemplate('1fr 1fr') .width('100%') .height(300) .margin({ top: 100 })
|
抽卡遮罩层(弹层)
思路分析:
- 布局角度:层叠布局 Stack
- 结构角度:Column > Text + Image + Button
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
| Stack(){ Column() { } .width('100%') .height('100%')
Column({space:30}){ Text('获得生肖卡') .fontColor('#f5ebcf') .fontSize(25) .fontWeight(FontWeight.Bold) Image($r('app.media.img_00')) .width(200) Button('开心收下') .width(200) .height(50) .backgroundColor(Color.Transparent) .border({width:2, color:'#fff9e0'}) } .justifyContent(FlexAlign.Center) .width('100%') .height('100%') .backgroundColor('#cd000000') }
|
遮罩和显隐动画
遮罩层显隐:透明度opacity 0-1;层级zIndex -1~99
图片缩放:缩放scale 0-1
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
| @State maskOpacity: number = 0 @State maskZIndex: number = -1
@State maskImgX: number = 0 @State maskImgY: number = 0
Stack() { Button('立即抽卡') .width(200) .backgroundColor('#ed5b8c') .margin({ top: 50 }) .onClick(() => { this.maskOpacity = 1 this.maskZIndex = 99 this.maskImgX = 1 this.maskImgY = 1 })
Column({ space: 30 }) { Text('获得生肖卡') .fontColor('#f5ebcf') .fontSize(25) .fontWeight(FontWeight.Bold) Image($r('app.media.img_00')) .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 }) } }
.opacity(this.maskOpacity) .zIndex(this.maskZIndex)
.animation({ duration: 200 })
|