一、组件基础
1.什么是ArkTS
ArkTS是HarmoyOS优选的助力应用开发的语言,ArkTS围绕应用开发在TypeScript(简称TS)生态基础上做了进一步的扩展,继承了TS所有的特性,是TS的超集。
扩展的能力如下:
- 基本语法
- 定义了声明式UI、自定义组件、动态扩展UI元素
- 提供了ArkUI系统组件、提供了组件事件、方法和属性
- 共同构建UI开发主体
- 状态状态
- 组件状态、组件数据共享、应用数据的共享、设备共享
- 渲染控制
- 条件渲染、循环循环、数据懒加载
bash
-- 声明式UI
通过一段HTML标签展示出对应的页面,还是通过使用document.createElement('tag')创建标签方便呢?
显示HTML,其实HTML本身就是声明式的,通过描述的方式取声明UI界面
一些前端框架也是用声明式UI,如:Vue使用template模板
再例如SwiftUI等app开发技术也是声明式的
2.组件结构
!
ArkTS通过装饰器
@Entry
和@Component
装饰器struct
关键字声明的数据结构,构成了一个自定义组件自定义组件中提供一个builde函数,开发者需要在函数内部用链式调用的方式进行基本的UI描述
UI描述方法可以参考UI描述规范
如:
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-common-components-text-display-V5
3.自定义组件
component 自定义组件的存放位置如下
Footer.ets
typescript
@Component
export default struct Footer{
build() {
Text('这是页脚');
}
}
MyPage.ets
typescript
//导入自定义组件
import Footer from '../compnonents/Footer'
@Entry
@Component
struct MyPage{
build() {
Column(){
Text("蔡徐坤篮球打的很好")
.fontSize(50)
.fontColor(Color.Red)
.fontWeight(FontWeight.Bold)
Footer();
}
}
}
4.系统组件(ArkUI)
常用的系统组件:
- Text
- Column
- Row
- Button
- TextInput
文档地址:
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-build-layout-V5
typescript
@Entry
@Component
struct LoginPage {
build() {
Column(){ //垂直方向布局
Row(){ //水平方向布局
Text('手机号:')
TextInput(); //输入框
}
Row(){ //水平方向布局
Text('验证码:')
TextInput();
}
Row(){
Button('登录')
.backgroundColor(Color.Green)
Button('重置')
.backgroundColor(Color.Red)
}
}
}
}
二.状态管理
@State 可以定义状态变量
1.组件变量,不具备驱动UI更新能力
typescript
@Entry
@Component
struct StatePage {
count = 100; //变量
build() {
//Text只可以放String或者是Resource
Text(this.count.toString())
.onClick(() => {
this.count++;//count的值加1
})
}
}
2 .状态变量加上@State 装饰器
加上@state装饰器后变量必须声明数据类型和赋值
但是给的类型不能是any,undefined,null,以及不建议给复杂类型和联合类型
typescript
@Entry
@Component
struct StatePage {
@State count:number = 100; //变量
build() {
//Text是可以放String或者是Resource
Text(this.count.toString())
.onClick(() => {
this.count++; //count的值加1
})
}
}
在鸿蒙开发中的页面调试建议:
typescriptimport promptAction from '@ohos.promptAction'; promptAction.showToast({ message: this.count.toString(), //弹出的内容 duration:5000 //持续的时间,单位是毫秒 1秒=1000毫秒 })
3.练习案例
实现登录表单数据手机、重置、模拟提交
typescript
import promptAction from '@ohos.promptAction';
@Entry
@Component
struct LoginPage2 {
@State mobile:string = '';//手机号
@State password:string = '';//密码
build() {
Column(){ //垂直布局
Row(){ //水平布局
Text('手机号')
TextInput()
.onChange((value) => {
this.mobile = value; //将输入框中输入的内容给到变量mobile
// promptAction.showToast({
// message: value,
// duration: 2000
// })
})
}
Row(){ //水平布局
Text('密码')
TextInput()
.onChange((value)=>{
this.password = value;//将密码框中输入的内容赋值给password
})
}
Row(){ //水平布局
Button('重置')
Button('登录')
.onClick(() => {
//只要手机号和密码不为空
if(this.mobile && this.password){
promptAction.showToast({
message:`${this.mobile} 登录成功`,
duration:5000
})
} else {
promptAction.showToast({
message:`请输入手机号或密码`,
duration:5000
})
}
})
}
}
}
}
三.样式处理
1. 样式语法(链式 & 枚举)
ArkTS中以声明方式组合和扩展组件来描述应用程序的UI
同时还提供了基本的属性、事件和子组件配置方法,帮助我们开发者实现应用交互逻辑
2. 样式属性
属性方法是以
.
链式调用方式配置系统组件的样式和其它属性建议每个属性方法单独写一行
typescript
@Entry
@Component
struct StylePage1 {
build() {
Text('演示')
.backgroundColor(Color.Orange) //背景颜色
.fontSize(50) //字体大小,在鸿蒙中单位默认是vp
.width('100%') //宽度
.height(200) //高度
.textAlign(TextAlign.Center) //文本水平对其方式
}
}
3. 枚举值
枚举:有限的列举
对于系统组件,ArkUI还未属性预定义了一些枚举类型
官网:
https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-appendix-enums-V5
typescript
@Entry
@Component
struct StylePage1 {
build() {
Text('演示')
.backgroundColor(Color.Orange) //背景颜色
.fontSize(50) //字体大小,在鸿蒙中单位默认是vp
.width('100%') //宽度
.height(200) //高度
.textAlign(TextAlign.Center) //文本水平对其方式
}
}
4. VP
在鸿蒙开发中,单位默认是
vp
什么是VP
virtual pixel: 虚拟像素
屏幕密度相关像素:根据屏幕像素转换成物理像素,当数值不带单位,默认的单位就是vp
在实际像素为1440物理像素的屏幕上。1vp
约等于3px
(物理像素)
typescript
import promptAction from '@ohos.promptAction'
@Entry
@Component
struct VpPage {
build() {
Text('演示')
.backgroundColor(Color.Red) //背景颜色
.fontSize(50) //默认是vp,字体的大小
.width('100%')
.height(200) //行高
.textAlign(TextAlign.Center) //文本水平对其方式
.fontColor(Color.White) //文本颜色
.onAreaChange((oldArea,newArea)=>{//屏幕大小发生改变的时候触发
promptAction.showToast({
message:newArea.width.toString()
})
})
}
}
不同的设备屏幕的宽度
vp
是不一致的,那怎么适配呢?
5. 适配
采用的是伸缩布局、网格布局、栅格系统进行布局适配
伸缩
layoutWeight(flex:number) 占剩余空间的多少份,可以理解成css中
flex:1
typescript
@Entry
@Component
struct LayoutPage {
build() {
Row(){
Text('left')
.backgroundColor('red')
.layoutWeight(1) //layoutWeight伸缩,占剩余空间的多少份
Text('right')
.backgroundColor('green')
.layoutWeight(2)
}
.width('100%')
}
}
等比例
设置元素宽高比:
aspectRatio(ratio:number)
typescript
@Entry
@Component
struct LayoutPage {
build() {
Column(){
Row(){
Text('left')
.backgroundColor(Color.Red)
.width('50%')
.aspectRatio(1) //宽高等比例
}
.width('100%')
}
}
}
vp:是鸿蒙默认单位,和屏幕的像素有关(
1vp ≈ 3px
),最终表现视觉大小在任何设备一致鸿蒙一般是以伸缩layoutWeight、网格、栅格进行适配布局的,如果要等比例缩放可以设置宽高等比
6.练习
设计稿一般是1080px
- Nav
- 左侧返回按钮24vp宽高,背景颜色
#f5f5f5
,图标12vp尺寸,颜色是#848484
- 标题18vp
- Comment
- 头像尺寸32vp宽高、右侧间距10vp
- 标题15vp,颜色默认
- 内容16vp,颜色
#565656
- 底部12vp,颜色
#c3c4c5
鸿蒙图标库:https://developer.huawei.com/consumer/cn/design/harmonyos-icon/
typescript
@Entry
@Component
struct PoePage {
build() {
Column(){
//导航
Row(){
Image($r('app.media.chevron_left'))
.width(24)
.aspectRatio(1) //宽高等比
//.fillColor('blue') //svg图标可以使用填充颜色
Text('评论回复')
.layoutWeight(1) //占据剩余的部分
.textAlign(TextAlign.Center) //文本居中
.padding({
right:24 //距离右边内边距24
})
}
.height(40)
//评论部分
Row(){
Image($r('app.media.kun'))
.width(32)
.aspectRatio(1) //等比缩放
.borderRadius(16) //圆角
Column(){
Text('坤坤')
.width('100%')
Text('篮球打得很好')
.width('100%')
Row(){
Text('10.21.*.* IP属于湖北武汉')
.fontSize(12)
.fontColor('#c3c4c5')
}
}
}
}
.width('100%')
.height('100%')
}
}
7. 样式复用
在开发过程中会出现大量代码在进行重复的样式设置,可以通过
@Styles
实现样式的复用在组件内和组件外均可
7.1 Styles
- 当前
Styles
仅仅支持通用属性和通用事件- 通用属性文档:
https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/5_2_u901a_u7528_u5c5e_u6027-V5
- 通用属性文档:
- 支持全局定义和组件内定义,同时存在组件内覆盖全局生效
typescript
import promptAction from '@ohos.promptAction'
//组件外
@Styles
function functionName(){
.backgroundColor('red')
.height(200)
.padding({left:20})
.onClick(() => {
promptAction.showToast({
message:'ok'
})
})
}
@Entry
@Component
struct StylesPage {
//组件内
// @Styles
// functionName(){
// .backgroundColor('red')
// .height(200)
// .padding({left:20})
// .onClick(() => {
// promptAction.showToast({
// message:'ok'
// })
// })
// }
build() {
Column(){
Text('Text1')
.functionName()
Text('Text2')
.functionName()
}
}
}
7.2 Styles练习
需求:有一个Text和一个按钮,背景颜色都是绿色,并且点击的时候都加1
typescript
// @Styles
// function sameStyle(){
// .backgroundColor('green')
// .onClick(()=>{
// this.count++;
// })
// }
@Entry
@Component
struct StlyesDemoPage {
@State count:number = 1;
@Styles
sameStyle(){
.backgroundColor('green')
.onClick(()=>{
this.count++;
})
}
build() {
Row(){
Column(){
Text(this.count.toString())
.sameStyle()
Button('+1')
.sameStyle()
}
}
}
}
7.3 Extends
@Extends:用于扩展原生组件样式,通过传入参数提供更加灵活的样式复用
- 使用@Extends 装饰器的函数只能是全局的
- 函数可以进行传参,如果参数是状态变量,状态更新后会刷新UI
- 且参数可以是一个函数,实现复用事件且可处理不同逻辑
- 只能放在组件外
typescript
import promptAction from '@ohos.promptAction';
@Extend(Text) function myExtend(color:string,callbck:()=>void){
.backgroundColor(color) //颜色参数传入
.width(100)
.height(100)
.textAlign(TextAlign.Center)
.borderRadius(10)
.onClick(() => callbck())
}
@Entry
@Component
struct ExtendsPage {
@State color:string = '#ccc';
build() {
Column(){
Text('Text1')
.myExtend(this.color, () => {
this.color = '#069'
})
Text('Text2')
.myExtend('blue',() => {
promptAction.showToast({message:'做其他的事情'})
})
}
}
}
7.4 样式多态
stateStyles() 可以依据组件的内部状态不同,快速的设置不同的样式
stateStyles是属性方法,可以根据UI内部装填来设置样式,类似于css的伪类,但是语法不一样的。
ArkUI提供了四种状态
- focused:获取焦点状态
- normal:正常状态
- pressed:按压状态
- disabled:不可用状态
typescript
import promptAction from '@ohos.promptAction';
@Entry
@Component
struct StateStylesPage {
@State disabled:boolean = false;
@State focused:boolean = false;
build() {
Column(){
Text('toggle disabled --->' + this.disabled)
.onClick(() => {
//!表示取反
this.disabled = !this.disabled;
})
Text('toggle focused ---> ' + this.focused)
.onClick(() => {
this.focused = ! this.focused;
})
Text('clickMe')
.enabled(!this.disabled) //如果将enabled设置为false,则表示不允许点击
.focusable(true)
.onClick(() => {
promptAction.showToast({message:'click'})
})
}
}
}
四. class语法
在鸿蒙的开发中关于对象状态范式,采用
class
方式,后续组件传值遇到深层对象嵌套要利用class
1. class创建对象和class
类型
typescript
class Person{
age:number
name:string
constructor(name:string,age:number) {
this.name = name;
this.age = age;
}
}
//创建对象
//当类型使用和构造函数使用
//有构造很函数了可以直接new
const p:Person = new Person('jack',20);
//当类型使用这种后续比较多,我们使用字面量对象
//没有构造函数需要用字面量的方式
// const p1:Person = {
// name:'tom',
// age:18
// }
2. implements 和interface接口
typescript
// 定义了一个接口
import promptAction from '@ohos.promptAction'
interface IPerson{
name:string,
age:number,
say:() => void
}
//实现接口.实现接口后,必须给属性初始值和实现方法
class Person implements IPerson{
name:string = "";
age:number = 0;
say(){
promptAction.showToast({message:this.name})
}
}
3. extends继承
typescript
// 定义了一个接口
import promptAction from '@ohos.promptAction'
interface IPerson{
name:string,
age:number,
say:() => void
}
//实现接口.实现接口后,必须给属性初始值和实现方法
class Person implements IPerson{
name:string = "";
age:number = 0;
say(){
promptAction.showToast({message:this.name})
}
}
class HelloPerson extends Person {
}
4. 复杂对象
当装饰器的数据类型为class或者是Object的时候,可以观察到自身的赋值的变化,和其属性值的变化
5. 对象类型状态
typescript
//对象模型
class User{
name?:string
age?:number
}
@Entry
@Component
struct ClassDemoPage {
@State user:User = {name:'jacklove',age:18} as User
build() {
Column(){
Text(this.user.name)
Text(this.user.age?.toString())
Button('年龄加1')
.onClick(() => {
//let age = this.user.age as number; //断言不是undefined是number
//this.user.age = age + 1
this.user.age = this.user.age as number;
this.user.age++;
})
}
}
}
6. 嵌套对象类型状态
typescript
//对象模型
class User{
name:string = ''
age:number = 0
}
class UserData{
code:number = 0
message:string = ''
//嵌套一个对象
data:User = {name:'',age:0}
}
@Entry
@Component
struct ClassDemo2Page {
@State
res:UserData = {
code:1000,
message:'获取用户信息成功',
data: {name:'tom',age:20}
}
build() {
Column(){
Text(this.res.data.name)
Text(this.res.data.age.toString())
Button('年龄加1')
.onClick(() => {
//this.res.data.age++ //这样不行
const user = this.res.data;
//替换属性,触发UI更新
this.res.data = {name:user.name,age:user.age + 1}
})
}
}
}
7. 对象数组类型状态
typescript
//对象模型
class User{
name:string = ''
age:number = 0
}
@Entry
@Component
struct ClassDemo2Page {
@State
list:User[] = [
{name:'jack',age:18},
{name:'tom',age:20}
]
build() {
Column(){
Text(JSON.stringify(this.list[0]))
Text(JSON.stringify(this.list[1]))
Button('年龄加1')
.onClick(() => {
//this.list[0].age++
const user = this.list[0];
this.list[0] = {name:user.name,age:user.age + 1}
})
}
}
}
嵌套对象和对象数组,采用赋值的方式进行更新,可以更新UI
五、界面渲染
1.条件渲染
条件渲染可以根据应用的不同状态,使用if...else 和 else...if 渲染对应的状态下的UI内容
- 条件渲染,是根据状态数据进行判断展示不同的UI
- 条件渲染,会销毁和创建组件,组件状态将不会保留
1.1 使用if...else 实现loading效果
typescript
@Entry
@Component
struct LoadingPage {
@State loading:boolean = false;
build() {
Column(){
if(this.loading){
LoadingProgress()
.width(100)
.aspectRatio(1)
//.color('red')
} else {
Text('后台数据')
Text('后台数据')
Text('后台数据')
}
Button('更新数据')
.onClick(() => {
this.loading = true
//2秒后将loading的值设置为false
setTimeout(() => {
this.loading = false
},2000)
})
}
}
}
1.2 添加渲染会销毁和重新创建组件,组件的状态不会保留
typescript
@Component
struct CounterComp{
@State count:number = 0;
build() {
Text(this.count.toString())
.onClick(() => {
this.count ++;
})
}
}
@Entry
@Component
struct Demo2Page {
@State show:boolean = true;
build() {
Column(){
if(this.show){
CounterComp();
}
Button('toggle')
.onClick(() => {
this.show = !this.show
})
}
}
}
2.循环渲染
foreach 接口 基于数组类型进行循环渲染,需要与容器配置使用
语法:
typescript
ForEach(
arr:Array<Object>, //数据源
itemGenerator:(item: Object, index: number) => void, //组件生成函数
keyGenerator?:(item: Object, index: number) => string //键值生成函数。
)
typescript
class MyUser{
id:string = ''
name:string = ''
age:number = 0
}
@Entry
@Component
struct ForeachPage {
@State userList:MyUser[] = [
{id:'1',name:'2年半的练习生',age:26},
{id:'2',name:'坤坤',age:26}
]
build() {
Column(){
//循环渲染
ForEach(
//1.数据源
this.userList,
//2.组件生成函数
(item:MyUser,index:number) => {
//内容
Text(`${item.id} - ${item.name} - ${item.age}`)
.width('100%')
.height(20)
}
)
//
Button('加载更多...')
.onClick(() => {
const arr:MyUser[] = [];
//新数组中产生10个对象
for(let index = 0; index < 10; index++){
arr.push({id:Math.random().toString(),name:'jacklove',age:19})
}
//将新的数组添加到userList中
this.userList.push(...arr)
})
}
.width('100%')
.height('100%')
}
}
循环渲染,需要与容器配置使用
语法:
typescript
ForEach(
arr:Array<Object>, //数据源
itemGenerator:(item: Object, index: number) => void, //组件生成函数
keyGenerator?:(item: Object, index: number) => string //键值生成函数。
)
typescript
class MyUser{
id:string = ''
name:string = ''
age:number = 0
}
@Entry
@Component
struct ForeachPage {
@State userList:MyUser[] = [
{id:'1',name:'2年半的练习生',age:26},
{id:'2',name:'坤坤',age:26}
]
build() {
Column(){
//循环渲染
ForEach(
//1.数据源
this.userList,
//2.组件生成函数
(item:MyUser,index:number) => {
//内容
Text(`${item.id} - ${item.name} - ${item.age}`)
.width('100%')
.height(20)
}
)
//
Button('加载更多...')
.onClick(() => {
const arr:MyUser[] = [];
//新数组中产生10个对象
for(let index = 0; index < 10; index++){
arr.push({id:Math.random().toString(),name:'jacklove',age:19})
}
//将新的数组添加到userList中
this.userList.push(...arr)
})
}
.width('100%')
.height('100%')
}
}