通过前面ArkTS开发语言(上)之TypeScript入门以及ArkTS开发语言(中)之ArkTS的由来和演进俩文我们知道了ArkTS的由来以及演进,知道了ArkTS具备了声明式语法和组件化特性,今天,搭建一个可刷新的排行榜页面。在排行榜页面中,使用循环渲染控制语法来实现列表数据渲染,使用@Builder创建排行列表布局内容,使用装饰器@State、@Prop、@Link来管理组件状态。最后我们点击系统返回按键,来学习自定义组件生命周期函数。
首先我们先要明白如下几个基本概念,方便我们更好的理解ArkTS,掌握和理解本demo的知识点。
应用界面是由一个个页面组成,ArkTS是由ArkUI框架提供,用于以声明式开发范式开发界面的语言。
声明式UI构建页面的过程,其实是组合组件的过程,声明式UI的思想,主要体现在两个方面:
类似苹果的SwiftUI中通过组合视图View,安卓Jetpack Compose中通过组合@Composable函数,ArkUI作为HarmonyOS应用开发的UI开发框架,其使用ArkTS语言构建自定义组件,通过组合自定义组件完成页面的构建。
ArkTS通过struct声明组件名,并通过@Component和@Entry装饰器,来构成一个自定义组件。
@Entry
@Component
struct TitleView {...}
在自定义组件内需要使用build方法来进行UI描述。
@Entry
@Component
struct TitleView
...
build() {
...
}
}
build方法内可以容纳内置组件和其他自定义组件,如Column和Text都是内置组件,由ArkUI框架提供,TitleView为自定义组件,需要开发者使用ArkTS自行声明。
@Entry
@Component
struct TitleView {
...
build() {
Column(...) {
Text(...)
...
ForEach(...{
TodoItem(...)
},...)
}
...
}
}
@State装饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的build方法进行UI刷新。
@Prop与@State有相同的语义,但初始化方式不同。@Prop装饰的变量必须使用其父组件提供的@State变量进行初始化,允许组件内部修改@Prop变量,但更改不会通知给父组件,即@Prop属于单向数据绑定。
@Link装饰的变量可以和父组件的@State变量建立双向数据绑定,需要注意的是:@Link变量不能在组件内部进行初始化。
@Builder装饰的方法用于定义组件的声明式UI描述,在一个自定义组件内快速生成多个布局内容。
@State、@Prop、@Link三者关系如图所示:
4. 组件生命周期函数
自定义组件的生命周期函数用于通知用户该自定义组件的生命周期,这些回调函数是私有的,在运行时由开发框架在特定的时间进行调用,不能从应用程序中手动调用这些回调函数。 右图是自定义组件生命周期的简化图示:
后期会专门出一期文章讲解自定义组件的生命周期,敬请期待。
举个列子:
Column() {
if (this.count > 0) {
Text('count is positive')
}
}
条件渲染的具体使用规则,注意事项,以及使用场景见if/else:条件渲染,这里不过过多描述。
举个列子:
ForEach(
arr: any[], // 用于迭代的数组
itemGenerator: (item: any, index?: number) => void, // 生成子组件的lambda函数
keyGenerator?: (item: any, index?: number) => string // 用于给定数组项生成唯一且稳定的键值
)
条件渲染的具体使用规则,注意事项,以及使用场景见ForEach:循环渲染,这里不过过多描述。只不过ForEach循环渲染相对条件渲染来说相对复杂,后续有空我会专门介绍渲染控制。
了解以上五点,加上前面讲解到的ArkTS开发语言(上)之TypeScript入门和初识ArkTS开发语言(中)之ArkTS的由来和演进,我想大家对ArkTS有了基本的认识,也能做出一些简单的功能,下面我就以官方的一个待办列表对初识ArkTS开发语言做一个实战性总结。
下面我们用ArkTS实战开发一些自定义组件,然后组合实现一个待办列表,最终效果图如下:
我们已经知道,ArkTS通过struct声明组件名,并通过@Component和@Entry装饰器,来构成一个自定义组件。
使用@Entry和@Component装饰的自定义组件作为页面的入口,会在页面加载时首先进行渲染。
@Entry
@Component
struct ToDoList {...}
使用@Component装饰的自定义组件,如ToDoItem这个自定义组件则对应如下内容,作为页面的组成部分。
@Component
struct ToDoItem {...}
在自定义组件内需要使用build方法来进行UI描述。
@Entry
@Component
struct ToDoList
...
build() {
...
}
}
build方法内可以容纳内置组件和其他自定义组件,如Column和Text都是内置组件,由ArkUI框架提供,ToDoItem为自定义组件,需要开发者使用ArkTS自行声明。
@Entry
@Component
struct ToDoList {
...
build() {
Column(...) {
Text(...)
...
ForEach(...{
TodoItem(...)
},...)
}
...
}
}
那么如何实现上面的效果图了,我们对页面组成进行拆分。
会发现,整个页面是有一个标题和若干个ToDoItem组成的,因此我们只需自定义实现标题组件和一个ToDoItem组件即可。
接下来我们就以此分析并实现这俩个组件。
其实,标题组件很简单,就是一个简单的文本展示,我们只需用ArkUI提供的Text实现即可,这里为了加深对家对自定义组件语法和规则使用,我还是简单抽取成一个自定义公共组件吧。
自定义标题组件代码如下:
@Entry
@Component
struct ToDoTitle {
...
build() {
Row() {
Column() {
Text("代办")
.fontSize(50)
}
.width('100%')
}
.height('100%')
}
}
当然,直接在代办列表页使用系统自带的Text基础组件也可以,如下所示:
@Entry
@Component
struct ToDoList {
...
build() {
Row() {
Column() {
// 直接在使用Text
Text("代办")
.fontSize(50)
...
}
.width('100%')
}
.height('100%')
}
}
ToDoItem相对于标题来说要复杂的多,认真分析其组成,ToDoItem是有一个水平摆放的组件,前面是一个图片,后面是一个普通文本,这是他的外观,同时它具备了一些行为,也就是我们常说的点击事件,在ArkTS中就是指组件状态,这里需要记录ToDoItem的点击的俩种状态:
下面,我就从ToDoItem组件的外观布局属性配置以及组件状态俩方面来讲解ToDoItem的具体事项
自定义组件的组成使用基础组件和容器组件等内置组件进行组合。但有时内置组件的样式并不能满足我们的需求,ArkTS提供了属性方法用于描述界面的样式。属性方法支持以下使用方式:
Text('Hello World')
.fontSize(50)
Text('Hello World')
.fontSize(this.size)
Text('Hello World')
.fontSize(this.size)
.width(100)
.height(100)
Text('Hello World')
.fontSize(this.size)
.width(this.count + 100)
.height(this.count % 2 === 0 ? 100 : 200)
Text('Hello World')
.fontSize(this.size)
.width(this.count + 100)
.height(this.count % 2 === 0 ? 100 : 200)
.fontColor(Color.Red)
.fontWeight(FontWeight.Bold)
对于有多种组件需要进行组合时,容器组件则是描述了这些组件应该如何排列的结果。
ArkUI中的布局容器有很多种,在不同的适用场合选择不同的布局容器实现,ArkTS使用容器组件采用花括号语法,内部放置UI描述。
这里我们将介绍最基础的两个布局——列布局和行布局。
对于如下每一项的布局,两个元素为横向排列,选择Row布局
类似下图所示的布局,整体都是从上往下纵向排列,适用的布局方式是Column列布局。
Column() {
Text($r('app.string.page_title'))
...
ForEach(this.totalTasks,(item) => {
TodoItem({content:item})
},...)
}
实际开发中由于交互,页面的内容可能需要产生变化,以每一个ToDoItem为例,其在完成时的状态与未完成时的展示效果是不一样的。
声明式UI的特点就是UI是随数据更改而自动刷新的,我们这里定义了一个类型为boolean的变量isComplete,其被@State装饰后,框架内建立了数据和视图之间的绑定,其值的改变影响UI的显示。
@State isComplete : boolean = false;
@State装饰器的作用
用圆圈和对勾这样两个图片,分别来表示该项是否完成,这部分涉及到内容的切换,需要使用条件渲染if / else语法来进行组件的显示与消失,当判断条件为真时,组件为已完成的状态,反之则为未完成。
if (this.isComplete) {
Image($r('app.media.ic_ok'))
.objectFit(ImageFit.Contain)
.width($r('app.float.checkbox_width'))
.height($r('app.float.checkbox_width'))
.margin($r('app.float.checkbox_margin'))
} else {
Image($r('app.media.ic_default'))
.objectFit(ImageFit.Contain)
.width($r('app.float.checkbox_width'))
.height($r('app.float.checkbox_width'))
.margin($r('app.float.checkbox_margin'))
}
由于两个Image的实现具有大量重复代码,ArkTS提供了@Builder装饰器,来修饰一个函数,快速生成布局内容,从而可以避免重复的UI描述内容。这里使用@Bulider声明了一个labelIcon的函数,参数为url,对应要传给Image的图片路径。
@Builder labelIcon(url) {
Image(url)
.objectFit(ImageFit.Contain)
.width($r('app.float.checkbox_width'))
.height($r('app.float.checkbox_width'))
.margin($r('app.float.checkbox_margin'))
}
使用时只需要使用this关键字访问@Builder装饰的函数名,即可快速创建布局。
if (this.isComplete) {
this.labelIcon($r('app.media.ic_ok'))
} else {
this.labelIcon($r('app.media.ic_default'))
}
为了让待办项带给用户的体验更符合已完成的效果,给内容的字体也增加了相应的样式变化,这里使用了三目运算符来根据状态变化修改其透明度和文字样式,如opacity是控制透明度,decoration是文字是否有划线。通过isComplete的值来控制其变化。
Text(this.content)
...
.opacity(this.isComplete ? CommonConstants.OPACITY_COMPLETED : CommonConstants.OPACITY_DEFAULT)
.decoration({ type: this.isComplete ? TextDecorationType.LineThrough : TextDecorationType.None })
最后,为了实现与用户交互的效果,在组件上添加了onClick点击事件,当用户点击该待办项时,数据isComplete的更改就能够触发UI的更新。
@Component
struct ToDoItem {
@State isComplete : boolean = false;
@Builder labelIcon(icon) {...}
...
build() {
Row() {
if (this.isComplete) {
this.labelIcon($r('app.media.ic_ok'))
} else {
this.labelIcon($r('app.media.ic_default'))
}
...
}
...
.onClick(() => {
this.isComplete= !this.isComplete;
})
}
}
上面,只是完成了一个ToDoItem组件的开发,当我们有多条待办数据需要显示在页面时,就需要使用到ForEach循环渲染语法。
假设,我们有五条待办数据需要展示在页面上。
total_Tasks:Array<string> = [
'早起晨练',
'准备早餐',
'阅读名著',
'学习ArkTS',
'看剧放松'
]
ForEach基本使用中,只需要了解要渲染的数据以及要生成的UI内容两个部分,例如这里要渲染的数组为以上的五条待办事项,要渲染的内容是ToDoItem这个自定义组件,也可以是其他内置组件。
ForEach基本使用如下图所示:
ToDoItem这个自定义组件中,每一个ToDoItem要显示的文本参数content需要外部传入,参数传递使用花括号的形式,用content接受数组内的内容项item。
最终完成的代码及其效果如下。
@Entry
@Component
struct ToDoList {
...
build() {
Row() {
Column() {
Text(...)
...
ForEach(this.totalTasks,(item) => {
TodoItem({content:item})
},...)
}
.width('100%')
}
.height('100%')
}
}
ToDoList页面的最终效果图:
现在我们明白了ArkTS声明式开发范式的基本组成,这里我用一张图做一个总结概述
ArkTS声明式开发范式:
装饰器用来装饰类、结构体、方法以及变量,赋予其特殊的含义,如上述示例中 @Entry 、 @Component 、 @State 都是装饰器。具体而言, @Component 表示这是个自定义组件; @Entry 则表示这是个入口组件; @State 表示组件中的状态变量,此状态变化会引起 UI 变更。
自定义组件
可复用的 UI 单元,可组合其它组件,如上述被 @Component 装饰的 struct Hello。
UI 描述
声明式的方式来描述 UI 的结构,如上述 build() 方法内部的代码块。
内置组件
框架中默认内置的基础和布局组件,可直接被开发者调用,比如示例中的 Column、Text、Divider、Button。
事件方法
用于添加组件对事件的响应逻辑,统一通过事件方法进行设置,如跟随在Button后面的onClick()。
属性方法
用于组件属性的配置,统一通过属性方法进行设置,如fontSize()、width()、height()、color() 等,可通过链式调用的方式设置多项属性。
从UI框架的需求角度,ArkTS在TS的类型系统的基础上,做了进一步的扩展:定义了各种装饰器、自定义组件和UI描述机制,再配合UI开发框架中的UI内置组件、事件方法、属性方法等共同构成了应用开发的主体。在应用开发中,除了UI的结构化描述之外,还有一个重要的方面:状态管理。如上述示例中,用 @State 装饰过的变量 myText ,包含了一个基础的状态管理机制,即 myText 的值的变化会自动触发相应的 UI 变更 (Text组件)。ArkUI 中进一步提供了多维度的状态管理机制。和 UI 相关联的数据,不仅可以在组件内使用,还可以在不同组件层级间传递,比如父子组件之间,爷孙组件之间,也可以是全局范围内的传递,还可以是跨设备传递。另外,从数据的传递形式来看,可分为只读的单向传递和可变更的双向传递。开发者可以灵活的利用这些能力来实现数据和 UI 的联动。
总体而言,ArkUI开发框架通过扩展成熟语言、结合语法糖或者语言原生的元编程能力、以及UI组件、状态管理等方面设计了统一的UI开发范式,结合原生语言能力共同完成应用开发。这些构成了当前ArkTS基于TS的主要扩展。
ArkUI完整的开发范式可参考[方舟开发框架概述]。(https://developer.harmonyos.com/cn/docs/documentation/doc-guides/arkui-overview-0000001281480754)