鸿蒙应用开发之UI框架方舟ArkUI使用参考

内容提要

  • ArkUI框架概述
  • 常用UI组件
  • 路由管理
  • 组件状态管理
  • 组件生命周期

一、ArkUI框架概述

HarmonyOS提供了⼀套UI开发框架,即⽅⾈开发框架(ArkUI框架)。⽅⾈开发框架可为开发者提
供应⽤UI开发所必需的能⼒,⽐如多种组件、布局计算、动画能⼒、UI交互、绘制等。

⽅⾈开发框架针对不同⽬的和技术背景,提供两种开发⽅式:
基于ArkTS的声明式开发范式(简称“声明式开发范式”),这里以声明式为例进行解析。
兼容JS的类Web开发范式(简称“类Web开发范式”)。

说明:ArkUI声明式开发,其代码是嵌入到ArkTS中实现的。

1、 组件分类

HarmonyOS为开发者提供应⽤程序界⾯所需的各种组件,将这些组件进⾏分类,⽅便开发者快速
上⼿。
常⽤的UI组件有:基础组件、容器组件与媒体组件三种。

2、组件通过属性

组件通⽤属性:所有组件都可以使⽤的属性。
通⽤的属性⾮常多,建议在使⽤的时候,再去官网查阅相关的API。

例如:尺⼨属性
width(宽)
height(⾼)
size(⾼宽尺⼨)
padding(内边距)
margin(外边距)
layoutWeight(⽗容器尺⼨确定时,设置了layoutWeight属性的⼦元素与兄弟元素占主轴尺⼨
按照权重进⾏分配,忽略元素本身尺⼨设置,表示⾃适应占满剩余空间)。
constraintSize(设置约束尺⼨,组件布局时,进⾏尺⼨范围限制。constraintSize的优先级⾼
于Width和Height。若设置的minWidth⼤于maxWidth,则minWidth⽣效,minHeight与
maxHeight同理)。

例如:⽂本样式
fontColor(设置字体颜⾊)
fontSize(设置字体⼤⼩,字体默认⼤⼩16。不⽀持设置百分⽐字符串)
fontStyle(设置字体样式)
fontWeight(设置⽂本的字体粗细,number类型取值[100, 900],取值间隔为100,默认为
400,取值越⼤,字体越粗)
fontFamily(设置字体列表。默认字体'HarmonyOS Sans')
lineHeight(设置⽂本的⽂本⾏⾼,设置值不⼤于0时,不限制⽂本⾏⾼,⾃适应字体⼤⼩,
Length为number类型时单位为fp)。
decoration(设置⽂本装饰线样式及其颜⾊)

3、组件通用事件

组件通用事件:所有组件都可以使⽤的事件。

点击事件:onClick(event: (event?: ClickEvent) => void)

触摸事件:onTouch(event: (event?: TouchEvent) => void)

挂载卸载事件:

组件显示(挂载)事件:onAppear(event: () => void)

组件卸载事件:onDisAppear(event: () => void)

拖拽事件:

第⼀次拖拽此事件绑定的组件时:

onDragStart(event: (event?: DragEvent, extraParams?: string) => CustomBuilder | DragItemInfo)

拖拽进⼊组件范围内时:

onDragEnter(event: (event?: DragEvent, extraParams?: string) => void)

拖拽在组件范围内移动时:

onDragMove(event: (event?: DragEvent, extraParams?: string) => void)

拖拽离开组件范围内时:

onDragLeave(event: (event?: DragEvent, extraParams?: string) => void)

绑定此事件的组件可作为拖拽释放⽬标,当在本组件范围内停⽌拖拽⾏为时:

onDrop(event: (event?: DragEvent, extraParams?: string) => void)

按键事件:onKeyEvent(event: (event?: KeyEvent) => void)

焦点事件:

当前组件获取焦点时触发:onFocus(event: () => void)

当前组件失去焦点时触发:onBlur(event:() => void)

⿏标事件:

⿏标进⼊或退出组件时触发该回调:onHover(event: (isHover?: boolean) => void)

当前组件被⿏标按键点击时或者⿏标在组件上悬浮移动时,触发该回调:

onMouse(event: (event?: MouseEvent) => void)

组件区域变化事件:onAreaChange(event: (oldValue: Area, newValue: Area) => void)

组件可⻅区域变化事件:

onVisibleAreaChange(ratios: Array<number>, event: (isVisible: boolean,currentRatio: number) => void)

4、创建组件

ArkTS以声明⽅式组合和扩展组件来描述应⽤程序的UI,同时还提供了基本的属性、事件和⼦组件
配置⽅法,帮助开发者实现应⽤交互逻辑。

根据组件构造⽅法的不同,创建组件包含有参数和⽆参数两种⽅式(创建组件时不需要new运算
符)。
(1)⽆参数
如果组件的接⼝定义没有包含必选构造参数,则组件后⾯的“()”不需要配置任何内容。

例如,Divider组件不包含构造参数:

Column() {
Text('item 1')
Divider()
Text('item 2')
}
12345

(2)有参数
如果组件的接⼝定义包含构造参数,则在组件后⾯的“()”配置相应参数。

// Image组件的必选参数src
Image('https://xyz/test.jpg')
// Text组件的⾮必选参数content
// string类型的参数
Text('test')
// $r形式引⼊应⽤资源,可应⽤于多语⾔场景
Text($r('app.string.title_value'))
// ⽆参数形式
Text()

5、配置属性

属性⽅法以“.”链式调⽤的⽅式配置系统组件的样式和其他属性,建议每个属性⽅法单独写⼀⾏。

// 配置Text组件的字体⼤⼩
Text('test')
.fontSize(12)
// 配置组件的多个属性
Image('test.jpg')
.alt('error.jpg')
.width(100)
.height(100)
// 除了直接传递常量参数外,还可以传递变量或表达式。
Text('hello')
.fontSize(this.size)
Image('test.jpg')
.width(this.count % 2 === 0 ? 100 : 200)
.height(this.offset + 100)
// 对于系统组件,ArkUI还为其属性预定义了⼀些枚举类型供开发者调⽤
Text('hello')
.fontSize(20)
.fontColor(Color.Red)
.fontWeight(FontWeight.Bold)

6、配置事件

事件⽅法以“.”链式调⽤的⽅式配置系统组件⽀持的事件,建议每个事件⽅法单独写⼀⾏。

// 使⽤箭头函数配置组件的事件⽅法
Button('Click me')
.onClick(() => {
this.myText = 'ArkUI';
})
// 使⽤匿名函数表达式配置组件的事件⽅法,要求使⽤bind,以确保函数体中的this指向当前组
件。
Button('add counter')
.onClick(function(){
this.counter += 2;
}.bind(this))

7、配置⼦组件

如果组件⽀持⼦组件配置,则需在尾随闭包"{...}"中为组件添加⼦组件的UI描述。Column、Row、
Stack、Grid、List等组件都是容器组件。

// 以下是简单的Column组件配置⼦组件的示例
Column() {
Text('Hello')
.fontSize(100)
Divider()
Text(this.myText)
.fontSize(100)
.fontColor(Color.Red)
}
123456789

容器组件均⽀持⼦组件配置,可以实现相对复杂的多级嵌套。

二、常用UI组件介绍

1、图片组件(Image)

图⽚组件,⽀持本地图⽚和⽹络图⽚的渲染展示。
语法:Image(src: string | PixelMap | Resource)
图⽚的数据源,⽀持本地图⽚和⽹络图⽚。当使⽤相对路径引⽤图⽚资源时,

例如Image("common/test.jpg"),不⽀持跨包/跨模块调⽤该Image组件,建议使⽤$r⽅式来管理需全局使⽤的图⽚资源。

@Entry
@Component
struct Index {
@State imgWidth:number = 100;
build() {
Column() {
// 使⽤Resources资源图⽚,固定写法$r('app.media.图⽚名称')
Image($r('app.media.icon_header_logo'))
// 设置宽度
.width(this.imgWidth)
// 设置图⽚的插值效果
.interpolation(ImageInterpolation.High)
.onClick(()=>{
this.imgWidth+=10;
})
// 使⽤⽹络图⽚
Image('https://img10.360buyimg.com/img/jfs/t1/232436/19/11137/1806
0/659cb904F4aa90538/4e4ab1be4fe416f7.jpg')
.width(300)
.interpolation(ImageInterpolation.High)
// 使⽤本地图⽚,必须书写相对路径
Image("pages/image/imgs/1.webp")
.width(100)
.interpolation(ImageInterpolation.High)
}
.width('100%')
}
}

注意事项:
(1)在使⽤Resources⽬录下的资源图⽚时,格式为:$r('app.media.图⽚名称'),不需要书写图⽚扩展名
(2)使⽤本地图⽚资源时,需要从pages⽬录书写相对路径
(3)使⽤interpolation可以防⽌图⽚放⼤的锯⻮效果,interpolation的值有4个:

2、关于Resources资源⽬录

资源分类与访问:
应⽤开发过程中,经常需要⽤到颜⾊、字体、间距、图⽚等资源,在不同的设备或配置中,这些资
源的值可能不同。
应用资源:借助资源⽂件能⼒,开发者在应⽤中⾃定义资源,⾃⾏管理这些资源在不同的设备
或配置中的表现。
系统资源:开发者直接使⽤系统预置的资源定义(即分层参数,同⼀资源ID在设备类型、深浅
⾊等不同配置下有不同的取值)。
应⽤开发中使⽤的各类资源⽂件,需要放⼊特定⼦⽬录中存储管理。资源⽬录的示例如下所示,
base⽬录、限定词⽬录、rawfile⽬录称为资源⽬录,element、media、profile称为资源组⽬录。

stage模型多⼯程情况下,共有的资源⽂件放到AppScope下的resources⽬录。

base⽬录
base⽬录是默认存在的⽬录,⼆级⼦⽬录element⽤于存放字符串、颜⾊、布尔值等基础元素,
media、profile存放媒体、动画、布局等资源⽂件。
⽬录中的资源⽂件会被编译成⼆进制⽂件,并赋予资源⽂件ID。通过指定资源类型(type)和资源
名称(name)引⽤。

限定词⽬录
en_US和zh_CN是默认存在的两个限定词⽬录,其余限定词⽬录需要开发者根据开发需要⾃⾏创
建。⼆级⼦⽬录element、media、profile⽤于存放字符串、颜⾊、布尔值等基础元素,以及媒
体、动画、布局等资源⽂件。
同样,⽬录中的资源⽂件会被编译成⼆进制⽂件,并赋予资源⽂件ID。通过指定资源类型(type)
和资源名称(name)来引⽤。

rawfile⽬录
⽀持创建多层⼦⽬录,⽬录名称可以⾃定义,⽂件夹内可以⾃由放置各类资源⽂件。
⽬录中的资源⽂件会被直接打包进应⽤,不经过编译,也不会被赋予资源⽂件ID。通过指定⽂件路
径和⽂件名引⽤。
资源组⽬类型包括element、media、profile,⽤于存放特定类型的资源⽂件。

资源访问
对于应⽤资源,在⼯程中,通过"$r('app.type.name')"形式引⽤。其中,app为应⽤内
resources⽬录中定义的资源;type为资源类型或资源的存放位置,取值包含“color”、“float”、“string”、“plural”、“media”;name为资源命名,由开发者定义资源时确定。

对于rawfile⽬录资源,通过"$rawfile('filename')"形式引⽤。其中,filename为rawfile⽬录下
⽂件的相对路径,⽂件名需要包含后缀,路径开头不可以以"/"开头。

对于rawfile⽬录的descriptor,可通过资源管理的getRawFd接⼝引⽤,其返回值
descriptor.fd为hap包的fd。此时,访问rawfile⽂件需要结合{fd, offset, length}⼀起使⽤。

// 资源的具体使⽤⽅法如下:
Text($r('app.string.string_hello'))
  .fontColor($r('app.color.color_hello'))
  .fontSize($r('app.float.font_hello'))
Text($r('app.string.string_world'))
  .fontColor($r('app.color.color_world'))
  .fontSize($r('app.float.font_world'))
Image($r('app.media.my_background_image')) // media资源的$r引⽤
Image($rawfile('test.png')) // rawfile$r引⽤rawfile⽬录下图
⽚
Image($rawfile('newDir/newTest.png')) // rawfile$r引⽤rawfile⽬录下图
⽚

资源匹配
应⽤使⽤某资源时,系统会根据当前设备状态优先从相匹配的限定词⽬录中寻找该资源。

只有当resources⽬录中没有与设备状态匹配的限定词⽬录,或者在限定词⽬录中找不到该资源时,才会去base⽬录中查找。

rawfile是原始⽂件⽬录,不会根据设备状态去匹配不同的资源。

3、⽂本组件(Text)

Text组件:显示⼀段⽂本的组件
语法:Text(content?: string | Resource)

@Entry
@Component
struct TextDemo {
build() {
Column({ space: 20 }) {
Text("Text组件⽤于显示⼀段⽂本的组件")
.fontColor(Color.Red)
// 默认⼤⼩为16
.fontSize(16)
// ⽂本的对⻬⽅式,默认左对⻬
.textAlign(TextAlign.Start)
Text("Text组件⽤于显示⼀段⽂本的组件")
.fontColor(Color.Green)
// 默认⼤⼩为16
.fontSize(26)
// 设置⽂本居中对⻬
.textAlign(TextAlign.Center)
// 设置装饰线和颜⾊
.decoration({ type: TextDecorationType.Underline, color: Color.Ora
nge })
Text("Text组件⽤于显示⼀段⽂本的组件")
.fontColor(Color.Blue)
// 默认⼤⼩为16
.fontSize(26)
// 设置⽂本居中对⻬
.textAlign(TextAlign.End)
// 设置⽂本的最⼤⾏数。
.maxLines(1)
// 设置⽂本超⻓时的显示⽅式
.textOverflow({overflow:TextOverflow.Ellipsis})
}
}
}

注意:textOverflow在设置超⻓⽂本显示⽅式时,需配合maxLines使⽤,单独设置不⽣效。

4、⽂本输⼊组件(TextInput)

TextInput组件:单⾏⽂本输⼊框组件。
语法:TextInput(value?:{placeholder?: ResourceStr, text?: ResourceStr, controller?:
TextInputController})

@Entry
@Component
struct TextInputDemo {
@State username:string = ""
@State password:string = ""
@State age:number = 0
@State tel:string = ""
@State email:string = ""
@State sex:string = ""
private sexArr: string[] = ['男', '⼥','保密']
build() {
Column({space:10}){
Row(){
Text("姓名:")
TextInput({placeholder:"请输⼊姓名"})
.onChange(value=>{
this.username = value
})
}
Row(){
Text("密码:")
TextInput({placeholder:"请输⼊密码"})
.width('80%')
// 设置输⼊框类型:密码输⼊模式
.type(InputType.Password)
.onChange(value=>{
this.password = value
})
}
Row(){
Text("年龄:")
TextInput({placeholder:"请输⼊年龄"})
// 设置输⼊框类型为纯数字
.type(InputType.Number)
.onChange(value=>{
this.age = parseInt(value)
})
}
Row(){
Text("电话:")
TextInput({placeholder:"请输⼊电话"})
// 设置输⼊框类型:电话号码输⼊模式
.type(InputType.PhoneNumber)
.onChange(value=>{
this.tel = value

})
}
Row(){
Text("邮箱:")
TextInput({placeholder:"请输⼊邮箱"})
// 设置输⼊框类型:邮箱地址输⼊模式
.type(InputType.Email)
.onChange(value=>{
this.email = value
})
}
Row(){
Text("性别:")
// 滑动选择⽂本内容的组件(类似于下拉框效果)
TextPicker({range:this.sexArr,selected:2})
.onChange(value=>{this.sex=value})
}
Divider()
Text("姓名:"+this.username).fontSize(30).fontColor('#0a0')
Text("密码:"+this.password).fontSize(30).fontColor('#a00')
Text("年龄:"+this.age).fontSize(30).fontColor('#0a0')
Text("电话:"+this.tel).fontSize(30).fontColor('#0a0')
Text("邮箱:"+this.email).fontSize(30).fontColor('#0a0')
Text("性别:"+this.sex).fontSize(30).fontColor('#0a0')
}
}
}

注意事项:TextInput组件的type属性,⽤于设置输⼊框的类型,InputType枚举说明

5、按钮组件(Button)

Button组件:按钮组件,可快速创建不同样式的按钮。
⽅法1: Button(options?: {type?: ButtonType, stateEffect?: boolean})

⽅法2: Button(label?: ResourceStr, options?: { type?: ButtonType, stateEffect?: boolean })
使⽤⽂本内容创建相应的按钮组件,此时Button⽆法包含⼦组件。

除⽀持通⽤属性外,还⽀持以下属性(type与stateEffect可以采⽤参数书写,也可以参数⽤属性
书写)

ButtonType枚举说明

@Entry
@Component
struct ButtonDemo {
build() {
Column({ space: 10 }) {
// 普通的按钮
Button("确定", { type: ButtonType.Normal})
.width(200)
// 按钮中添加图⽚
Button({ type: ButtonType.Normal }){
Row({space:10}){
Image('pages/button/imgs/search.png').width(24).height(24).int
erpolation(ImageInterpolation.High)
Text("搜索").fontColor('#fff')
}.height(40)
}.width(200)
// 设置按钮圆⻆
Button({ type: ButtonType.Capsule }){
Row({space:10}){
Image('pages/button/imgs/search.png').width(24).height(24).int
erpolation(ImageInterpolation.High)
Text("搜索").fontColor('#fff')
}.height(40)
}.width(200)
// 设置按钮圆⻆
Button({ type: ButtonType.Circle }){
Row({space:10}){
Image('pages/button/imgs/search.png').width(24).height(24).int
erpolation(ImageInterpolation.High)
}.height(40)
// 设置按钮的背景颜⾊
}.width(200).backgroundColor('#0a0')
// 按钮添加点击事件
.onClick(()=>{
console.log("点击");
})
}
.width('100%')
.margin({top:20})
}
}

6、切换按钮组件(Toggle)

Toggle组件:提供勾选框样式、状态按钮样式及开关样式。
语法:Toggle(options: { type: ToggleType, isOn?: boolean })

ToggleType枚举说明

@Entry
@Component
struct ToggleDemo {
build() {
Column({space:10}){
Row({space:20}){
Text("爱好:")
Row(){
// 类似复选框
Toggle({type:ToggleType.Checkbox})
Text("读书")
}
Row(){
Toggle({type:ToggleType.Checkbox,isOn:true})
Text("唱歌")
}
Row(){
Toggle({type:ToggleType.Checkbox})
Text("跑步")
}
}
Divider()
Row(){
Text("开关:")
Toggle({type:ToggleType.Switch})
// 当点击时,触发状态的改变
.onChange((isOn)=>{
console.log(isOn+"")
})
}
Divider()
Row(){
Toggle({type:ToggleType.Button}){
Text("按钮").fontColor("#fff").fontSize(20)
}.backgroundColor('#0a0').width(200).height(30)
}
}
}
}

7、进度条组件(Progress)

Progress组件:进度条组件,⽤于显示内容加载或操作处理等进度。
语法:Progress(options: {value: number, total?: number, type?: ProgressType})
创建进度组件,⽤于显示内容加载或操作处理进度。

@Entry
@Component
struct ProgressDemo {
@State num:number = 10;
build() {
Column({space:10}){
// ProgressType.Linear : 线性样式
Progress({value:this.num,total:100,type:ProgressType.Linear})
// ProgressType.Ring : 环形⽆刻度样式
Progress({value:this.num,total:100,type:ProgressType.Ring}).width(10
0)
// ProgressType.Eclipse : ⽉圆⽉缺的进度展示效果
Progress({value:this.num,total:100,type:ProgressType.Eclipse}).width
(100)
// ProgressType.ScaleRing : 环形有刻度样式
Progress({value:this.num,total:100,type:ProgressType.ScaleRing}).wid
th(100).style({scaleCount:20,scaleWidth:30})
// ProgressType.Capsule : 胶囊样式
Progress({value:this.num,total:100,type:ProgressType.Capsule}).width
(200)
// 点击按钮,更改进度条的值
Button("进度").onClick(()=>{
if (this.num>=100) {
this.num=0;
}
this.num+=5
})
}.width('100%')
.margin({top:30})
}
}

8、加载动效组件(LoadingProgress)

LoadingProgress组件:⽤于显示加载动效的组件
语法:LoadingProgress()

@Entry
@Component
struct LoadingProgressDemo {
build() {
Column({ space: 5 }) {
LoadingProgress()
.color(Color.Green)
}.width('100%').margin({ top: 5 })
}
}

9、⼆维码组件(QRCode)

QRCode组件:⽤于显示单个⼆维码的组件
语法:QRCode(value: string)

@Entry
@Component
struct QRCodeDemo {
private value: string = '我是QB哥,期待与您⼀起探讨技术'
build() {
Column({ space: 5 }) {
Text('普通⼆维码').width('90%').fontColor(0xCCCCCC).fontSize(30)
QRCode(this.value).width(200).height(200)
// 设置⼆维码颜⾊
Text('设置⼆维码颜⾊').width('90%').fontColor(0xCCCCCC).fontSize(30)
QRCode(this.value).color(0x00aa00).width(200).height(200)
// 设置⼆维码背景⾊
Text('设置⼆维码背景⾊').width('90%').fontColor(0xCCCCCC).fontSize(30)
QRCode(this.value).width(200).height(200).backgroundColor(Color.Oran
ge)
}.width('100%').margin({ top: 5 })
}
}

10、搜索框组件(Search)

Search组件:搜索框组件,适⽤于浏览器的搜索内容输⼊框等应⽤场景。
语法:Search(options?: { value?: string, placeholder?: string, icon?: string, controller?:
SearchController })


@Entry
@Component
struct SearchDemo {
@State msg: string = ""
@State txt: string = ""
build() {
Column({ space: 20 }) {
// 设置搜索框
Search({ placeholder: "请输⼊搜索内容" })
// 设置搜索按钮的⽂本内容
.searchButton("搜索")
// 设置搜索框的⾼度
.height(40)
// 设置搜索框的背景颜⾊
.backgroundColor("#eee")
// 设置搜索框中提示⽂本颜⾊
.placeholderColor('#0a0')
// 设置搜索框中输⼊的⽂本字体样式
.textFont({ size: 18, weight: FontWeight.Bold })
// 点击搜索按钮,提交搜索数据对应的事件
.onSubmit(value => {
this.msg = value
})
// 搜索框中数据改变时触发的事件
.onChange(value => {
this.txt = value
})
Text("输⼊的内容:" + this.txt).fontSize(20).fontColor('#fa0')
Text("搜索关键字:" + this.msg).fontSize(20).fontColor("#0af")
}.margin(10)
}
}

11、布局组件

关于布局
布局指⽤特定的组件或者属性来管理⽤户⻚⾯所放置UI组件的⼤⼩和位置。在实际的开发过程中,需要遵守以下流程保证整体的布局效果。
确定⻚⾯的布局结构。
分析⻚⾯中的元素构成。
选⽤适合的布局容器组件或属性控制⻚⾯中各个元素的位置和⼤⼩约束。

如何选择布局
声明式UI提供了以下9种常⻅布局,开发者可根据实际应⽤场景选择合适的布局进⾏⻚⾯开发。

布局位置
position、offset等属性影响了布局容器相对于⾃身或其他组件的位置。

对⼦元素的约束

垂直与⽔平布局组件(Column/Row)
线性布局(Row/Column):
线性布局(LinearLayout)是开发中最常⽤的布局,通过线性容器Row和Column构建。
线性布局是其他布局的基础,其⼦元素在线性⽅向上(⽔平⽅向和垂直⽅向)依次排列。
Column容器内⼦元素按照垂直⽅向排列。
Row容器内⼦元素按照⽔平⽅向排列。

基本概念
布局容器:具有布局能⼒的容器组件,可以承载其他元素作为其⼦元素,布局容器会对其⼦元
素进⾏尺⼨计算和布局排列。
布局⼦元素:布局容器内部的元素。
主轴:线性布局容器在布局⽅向上的轴线,⼦元素默认沿主轴排列。Row容器主轴为⽔平⽅
向,Column容器主轴为垂直⽅向。
交叉轴:垂直于主轴⽅向的轴线。Row容器交叉轴为垂直⽅向,Column容器交叉轴为⽔平⽅
向。
间距:布局⼦元素的间距。

参数:

属性:

布局子元素在交叉轴上的对齐方式:
在布局容器内,可以通过alignItems属性设置⼦元素在交叉轴(排列⽅向的垂直⽅向)上的对
⻬⽅式。且在各类尺⼨屏幕中,表现⼀致。其中,交叉轴为垂直⽅向时,取值为VerticalAlign
类型,⽔平⽅向取值为HorizontalAlign。

alignSelf属性⽤于控制单个⼦元素在容器交叉轴上的对⻬⽅式,其优先级⾼于alignItems属
性,如果设置了alignSelf属性,则在单个⼦元素上会覆盖alignItems属性。

布局子元素在主轴上的排列方式:
在布局容器内,可以通过justifyContent属性设置⼦元素在容器主轴上的排列⽅式。可以从主
轴起始位置开始排布,也可以从主轴结束位置开始排布,或者均匀分割主轴的空间。

布局案例参考:

@Entry
@Component
struct ColumnAndRow {
build() {
// ⻚⾯整体纵向布局
Column({space:20}) {
// ⻚⾯顶部,logo,独占⼀⾏
Row() {
Image($r('app.media.icon_header_logo'))
.width(80)
.interpolation(ImageInterpolation.High)
}
.width('100%')
.height('30%')
.justifyContent(FlexAlign.Center)
// 账号
Row(){
Text("账号:")
TextInput({placeholder:'请输⼊账号'})
.layoutWeight(1)
}.width('100%')
// 密码
Row(){
Text("密码:")
TextInput({placeholder:'请输⼊密码'})
.type(InputType.Password)
.layoutWeight(1)
}.width('100%')
// 按钮
Row(){
Button("登录")
.width('30%')
Button("注册")
.width('30%')
.backgroundColor('#4b4d4d')
}
.width('100%')
.justifyContent(FlexAlign.SpaceEvenly)
// 忘记密码
Row(){
Text("忘记密码,请找回").width('100%')
.textAlign(TextAlign.End)
.fontColor('#aaa')
}
.width('100%')
  }.padding(10)
}
}

12、列表组件(List)

使⽤渲染控制语句来辅助UI的构建,这些渲染控制语句包括控制组件是否显示的条件渲染语句,基
于数组数据快速⽣成组件的循环渲染语句以及针对⼤数据量场景的数据懒加载语句。

if/else条件渲染

使⽤渲染控制语句来辅助UI的构建,这些渲染控制语句包括控制组件是否显示的条件渲染语句,基
于数组数据快速⽣成组件的循环渲染语句以及针对⼤数据量场景的数据懒加载语句。

@Entry
@Component
struct IfElseDemo {
@State flag:boolean = true;
build() {
// ⻚⾯整体纵向布局
Column({space:20}) {
// ⻚⾯顶部,logo,独占⼀⾏
Row() {
Image($r('app.media.icon_header_logo'))
.width(80)
.interpolation(ImageInterpolation.High)
}
.width('100%')
.height('30%')
.justifyContent(FlexAlign.Center)
if (this.flag){
// 账号
Row(){
Text("账号:")
TextInput({placeholder:'请输⼊账号'})
.layoutWeight(1)
}.width('100%')
// 密码
Row(){
Text("密码:")
TextInput({placeholder:'请输⼊密码'})
.type(InputType.Password)
.layoutWeight(1)
}.width('100%')
// 按钮
Row(){
Button("登录")
.width('30%')
Button("去注册")
.width('30%')
.backgroundColor('#4b4d4d')
.onClick(()=>{this.flag = false})
}
.width('100%')
.justifyContent(FlexAlign.SpaceEvenly)
// 忘记密码
Row(){
Text("忘记密码,请找回").width('100%')
.textAlign(TextAlign.End)
.fontColor('#aaa')
}
.width('100%')
}
if (!this.flag){
// 账号
Row(){
Text("账号:")
TextInput({placeholder:'请输⼊账号'})
.layoutWeight(1)
}.width('100%')
// 密码
Row(){
Text("密码:")
TextInput({placeholder:'请输⼊密码'})
.type(InputType.Password)
.layoutWeight(1)
}.width('100%')
Row(){
Text("⼿机:")
TextInput({placeholder:'请输⼊⼿机号'})
.type(InputType.PhoneNumber)
.layoutWeight(1)
}.width('100%')
Row({space:5}){
Text("验证码:")
TextInput({placeholder:'请输⼊验证码'})
.type(InputType.Normal)
.layoutWeight(1)
Button("获取验证码")
}.width('100%')
// 按钮
Row(){
Button("注册")
.width('30%')
Button("有账号,去登录")
.width('50%')
.backgroundColor('#0a0')
.onClick(()=>{this.flag = true})
}
.width('100%')
.justifyContent(FlexAlign.SpaceEvenly)
}
}.padding(10)
}
}

ForEach循环控制

ForEach接⼝基于数组类型数据来进⾏循环渲染,需要与容器组件配合使⽤,且接⼝返回的组件应
当是允许包含在ForEach⽗容器组件中的⼦组件。例如,ListItem组件要求ForEach的⽗容器组件
必须为List组件。

语法规则:

ForEach(
arr: Array,
itemGenerator: (item: any, index?: number) => void,
keyGenerator?: (item: any, index?: number) => string
)

todoList案例:

@Entry
@Component
struct ForEachDemo {
// 存储任务
@State taskArr:Array<Task> = [new Task('周末去爬⼭',false),new Task('周五
下午游泳',true),new Task('周⽇早上跑步',false)]
// 新任务
@State newTask:Task = new Task()
// 任务总数
@State total:number = this.taskArr.length
// 已完成数
@State worked:number = 1
build() {
Column({space:10}){
// 添加任务
Row(){
Text("任务名称:")
TextInput({placeholder:'请输⼊新任务'}).layoutWeight(1).type(InputTy
pe.Normal)
.onChange(value=>{
this.newTask = new Task(value , false)
})
Button("添加").onClick(()=>{
this.taskArr.push(this.newTask)
this.total = this.taskArr.length
})
}
// 任务列表
Row(){
Stack() {
DataPanel({ values: [this.total - this.worked , this.worked], ma
x: this.total, type: DataPanelType.Circle }).width(168).height(168)
Column() {
Text(`${(this.worked / this.total*100).toFixed()}`).fontSize(3
5).fontColor('#182431')
Text(`已完成${this.worked}/${this.total}`).fontSize(8.17).lineH
eight(11.08).fontWeight(500).opacity(0.6)
}
Text('%')
.fontSize(9.33)
.lineHeight(12.83)
.fontWeight(500)
.opacity(0.6)
.position({ x: 104.42, y: 78.17 })
  }
}
Column(){
Text(`已完成数:${this.worked}`).fontWeight(500)
Text(`任务总数:${this.total}`).fontWeight(500)
}.width('100%').justifyContent(FlexAlign.Center)
List({space:10}){
ListItem(){
Text("任务列表:").fontWeight(FontWeight.Bold)
}
ForEach(this.taskArr,(item:Task)=>{
ListItem(){
Row(){
if (item.isOk) {
Text(item.taskName).fontColor(Color.Gray).decoration({type
:TextDecorationType.LineThrough}).layoutWeight(1)
}else{
Text(item.taskName).fontColor(Color.Orange).layoutWeight(1
)
}
Blank()
Row(){
Toggle({ type: ToggleType.Checkbox, isOn: item.isOk })
.size({ width: 20, height: 20 })
.selectedColor('#007DFF')
.onChange((isOn: boolean) => {
item.isOk = isOn
})
}
}.width('100%')
}
})
}
.layoutWeight(1)
.scrollBar(BarState.Auto)
}.margin(20)
}
}
class Task{
taskName:string
isOk:boolean
constructor(taskName?:string , isOk?:boolean) {
this.taskName = taskName
this.isOk = isOk
}
}

注意:在点击复选框,数据并未发⽣改变,针对这个问题,后⾯讲解@State 状态管理时处

13、⽹格组件

(1)grid组件
grid组件:⽹格容器,由“⾏”和“列”分割的单元格所组成,通过指定“项⽬”所在的单元格做出各
种各样的布局。包含GridItem⼦组件。

@Entry
@Component
struct GridDemo {
private menuItems:Array<string> = ['menu1.webp','menu2.webp','menu3.web
p','menu4.webp','menu5.webp','menu6.webp','menu7.png','menu8.png','menu9.w
ebp','menu10.png'];
build() {
Grid(){
ForEach(this.menuItems,(item:string)=>{
GridItem(){
Image('pages/grid/imgs/'+item)
}
})
}.columnsTemplate('1fr 1fr 1fr 1fr 1fr')
.columnsGap(2)
.rowsGap(2)
}
}

14、轮播组件Swiper

Swiper组件提供滑动轮播显示的能力。Swiper本身是一个容器组件,当设置了多个子组件后,可以对这些子组件进行轮播显示。

通常,在一些应用首页显示推荐的内容时,需要用到轮播显示的能力。

案例解析:

...
private swiperController: SwiperController = new SwiperController()
...
Swiper(this.swiperController) {
  Text("0")
    .width('90%')
    .height('100%')
    .backgroundColor(Color.Gray)
    .textAlign(TextAlign.Center)
    .fontSize(30)

  Text("1")
    .width('90%')
    .height('100%')
    .backgroundColor(Color.Green)
    .textAlign(TextAlign.Center)
    .fontSize(30)

  Text("2")
    .width('90%')
    .height('100%')
    .backgroundColor(Color.Blue)
    .textAlign(TextAlign.Center)
    .fontSize(30)
}
.loop(true)

15、自定义组件

自定义组件的创建和渲染流程

(1)自定义组件的创建:自定义组件的实例由ArkUI框架创建。

(2)初始化自定义组件的成员变量:通过本地默认值或者构造方法传递参数来初始化自定义组件的成员变量,初始化顺序为成员变量的定义顺序。

(3)如果开发者定义了aboutToAppear,则执行aboutToAppear方法。

(4)在首次渲染的时候,执行build方法渲染系统组件,如果子组件为自定义组件,则创建自定义组件的实例。在首次渲染的过程中,框架会记录状态变量和组件的映射关系,当状态变量改变时,驱动其相关的组件刷新。

当应用在后台启动时,此时应用进程并没有销毁,所以仅需要执行onPageShow。

自定义组件重新渲染

当事件句柄被触发(比如设置了点击事件,即触发点击事件)改变了状态变量时,或者LocalStorage / AppStorage中的属性更改,并导致绑定的状态变量更改其值时:

(1)框架观察到了变化,将启动重新渲染。

(2)根据框架持有的两个map(自定义组件的创建和渲染流程中第4步),框架可以知道该状态变量管理了哪些UI组件,以及这些UI组件对应的更新函数。执行这些UI组件的更新函数,实现最小化更新。

自定义组件的删除

如果if组件的分支改变,或者ForEach循环渲染中数组的个数改变,组件将被删除:

(1)在删除组件之前,将调用其aboutToDisappear生命周期函数,标记着该节点将要被销毁。ArkUI的节点删除机制是:后端节点直接从组件树上摘下,后端节点被销毁,对前端节点解引用,前端节点已经没有引用时,将被JS虚拟机垃圾回收。

(2)自定义组件和它的变量将被删除,如果其有同步的变量,比如@Link、@Prop、@StorageLink,将从同步源上取消注册。

不建议在生命周期aboutToDisappear内使用async await,如果在生命周期的aboutToDisappear使用异步操作(Promise或者回调方法),自定义组件将被保留在Promise的闭包中,直到回调方法被执行完,这个行为阻止了自定义组件的垃圾回收。

三、路由管理

⻚⾯路由:提供通过不同的url访问不同的⻚⾯,包括跳转到应⽤内的指定⻚⾯、⽤应⽤内的某个⻚⾯替换当前⻚⾯、返回上⼀⻚⾯或指定的⻚⾯等。
⻚⾯路由,需要导⼊模块:import router from '@ohos.router

Router模块提供了两种跳转模式,分别是router.pushUrl()router.replaceUrl()。这两种模式决定了目标页是否会替换当前页。

  • router.pushUrl():目标页不会替换当前页,而是压入页面栈。这样可以保留当前页的状态,并且可以通过返回键或者调用router.back()方法返回到当前页。
  • router.replaceUrl():目标页会替换当前页,并销毁当前页。这样可以释放当前页的资源,并且无法返回到当前页。
// 在Home页面中
function onJumpClick(): void {
  router.pushUrl({
    url: 'pages/Detail' // 目标url
  }, router.RouterMode.Standard, (err) => {
    if (err) {
      console.error(`Invoke pushUrl failed, code is ${err.code}, message is ${err.message}`);
      return;
    }
    console.info('Invoke pushUrl succeeded.');
  });
}
// 在Login页面中
function onJumpClick(): void {
  router.replaceUrl({
    url: 'pages/Profile' // 目标url
  }, router.RouterMode.Standard, (err) => {
    if (err) {
      console.error(`Invoke replaceUrl failed, code is ${err.code}, message is ${err.message}`);
      return;
    }
    console.info('Invoke replaceUrl succeeded.');
  })
}

四、组件状态管理

状态管理介绍

在声明式UI编程框架中,UI是程序状态的运行结果,用户构建了一个UI模型,其中应用的运行时的状态是参数。当参数改变时,UI作为返回结果,也将进行对应的改变。这些运行时的状态变化所带来的UI的重新渲染,在ArkUI中统称为状态管理机制。

自定义组件拥有变量,变量必须被装饰器装饰才可以成为状态变量,状态变量的改变会引起UI的渲染刷新。如果不使用状态变量,UI只能在初始化时渲染,后续将不会再刷新。

下图展示了State和View(UI)之间的关系。

  • View(UI):UI渲染,指将build方法内的UI描述和@Builder装饰的方法内的UI描述映射到界面。
  • State:状态,指驱动UI更新的数据。用户通过触发组件的事件方法,改变状态数据。状态数据的改变,引起UI的重新渲染

基本概念

  • 状态变量:被状态装饰器装饰的变量,状态变量值的改变会引起UI的渲染更新。示例:@State num: number = 1,其中,@State是状态装饰器,num是状态变量。
  • 常规变量:没有被状态装饰器装饰的变量,通常应用于辅助计算。它的改变永远不会引起UI的刷新。以下示例中increaseBy变量为常规变量。
  • 数据源/同步源:状态变量的原始来源,可以同步给不同的状态数据。通常意义为父组件传给子组件的数据。以下示例中数据源为count: 1。
  • 命名参数机制:父组件通过指定参数传递给子组件的状态变量,为父子传递同步参数的主要手段。示例:CompA: ({ aProp: this.aProp })。
  • 从父组件初始化:父组件使用命名参数机制,将指定参数传递给子组件。子组件初始化的默认值在有父组件传值的情况下,会被覆盖。例如:
@Component
struct MyComponent {
  @State count: number = 0;
  private increaseBy: number = 1;

  build() {
  }
}

@Component
struct Parent {
  build() {
    Column() {
      // 从父组件初始化,覆盖本地定义的默认值
      MyComponent({ count: 1, increaseBy: 2 })
    }
  }
}

五、组件生命周期

(1)页面生命周期,即被@Entry装饰的组件生命周期,提供以下生命周期接口

onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景。
onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。
onBackPress:当用户点击返回按钮时触发。

(2)组件生命周期,即一般用@Component装饰的自定义组件的生命周期,提供以下生命周期接口

aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行。

aboutToDisappear:在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量,特别是@Link变量的修改可能会导致应用程序行为不稳定。

更多精彩内容请继续关注本站!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值