作者:杨亮Jerry
作为多年的大前端程序开发工作者,就目前的形式,个人浅见,在未来3-5年,移动端依旧是Android系统和iOS系统的天下。不过基于鸿蒙系统的应用开发还是值得我们去花点时间去了解下的,阅读并实践官网的开发文档和实践案例后,我们会发现其编程模式和Android的Jetpack Compose以及基于Dart语言开发Flutter跨平台应用程序非常相似,它们都是声明式的编程方式,非常容易理解。相信有过Compose或者Flutter开发经验的小伙伴去学习HarmonyOS应用开发会非常简单,容易上手。
虽然现在工作不是全部时间在Coding上了,但是自己在技术上还是要有所坚持,后续会抽时间继续学习HarmonyOS的开发,毕竟艺多不压身(天朝程序猿😂)。
一、ArkTS与TypeScript
1、声明式UI基本概念
(1)、应用界面是由一个个页面组成,ArkTS是由ArkUI框架提供,用于以声明式开发范式开发界面的语言。
(2)、声明式UI构建页面的过程,其实是组合组件的过程,声明式UI的思想,主要体现在两个方面:
- 描述UI的呈现结果,而不关心过程
- 状态驱动视图更新
2、按照官网上的说法,ArkTS是HarmonyOS的主力应用开发语言。
它在TypeScript(简称TS)的基础上,匹配ArkUI框架,扩展了声明式UI、状态管理等相应的能力,让开发者以更简洁、更自然的方式开发跨端应用。
ArkTS、TypeScript和JavaScript之间的关系: JavaScript是一种属于网络的高级脚本语言,已经被广泛用于Web应用开发,常用来为网页添加各式各样的动态功能,为用户提供更流畅美观的浏览效果。 TypeScript 是 JavaScript 的一个超集,它扩展了 JavaScript 的语法,通过在JavaScript的基础上添加静态类型定义构建而成,是一个开源的编程语言。 ArkTS兼容TypeScript语言,拓展了声明式UI、状态管理、并发任务等能力。
TypeScript的语法非常简单,有过Java、Kotlin、Dart等语言开发经验的小伙伴,会非常容易上手。可以在其官网进一步了解TS的语法知识。
3、ArkTS基础知识
(1)、ArkUI开发框架
上面这张图是ArkUI开发框架的整体架构,其中,基于TS扩展的声明式UI范式中所用的语言就是ArkTS。
(2)、ArkTS声明式开发范式
上面这张图是从官网下载下来的一个介绍ArkTS声明式开发范式 的代码示例,UI界面会显示两段文本和一个按钮,当开发者点击按钮时,文本内容会从'Hello World'变为‘Hello ArkUI’。
装饰器 用来装饰类、结构体、方法以及变量,赋予其特殊的含义,如上述示例中 @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的结构化描述之外,还有一个重要的方面:状态管理。如上述示例中,用 @State 装饰过的变量 myText ,包含了一个基础的状态管理机制,即 myText 的值的变化会自动触发相应的 UI 变更 (Text组件)。ArkUI 中进一步提供了多维度的状态管理机制。
二、ArkTS开发实践
接下来会按照官网的实例进行第一次ArkTS的开发实践,基于自己的理解进行一些修改,学习基本知识。
上面两张图就是我们要实现的UI效果和交互逻辑,即要呈现一个学生名单列表,每个Item由一个Icon和一个Name横向组成,当选中某一个Item的时候,Icon要切换成选中图标,另外Name的样式也会发生一些变化。使用ArkTS实现这个学生名单列表的方法基本和Jetpack Compose以及Flutter的开发方式是一样的,下面进行代码拆分。
1、自定义组件的组成
ArkTS
通过 struct
声明组件名,并通过 @Component
和 @Entry
装饰器,来构成一个自定义组件。
使用 @Entry
和 @Component
装饰的自定义组件作为页面的 入口
,会在页面加载时首先进行渲染。
import { StudentListPage } from '../view/StudentListPage';
@Entry
@Component
struct Index {
build() {
Column() {
StudentListPage();
}
.width('100%')
.height('100%')
.padding('10vp')
.backgroundColor($r('app.color.page_background'))
}
}
这个Index使用 @Entry
和 @Component
装饰,所以会在页面加载时首先进行渲染,可以理解为当前页面的入口。
2.使用@Component装饰的自定义组件
如图所示,对列表进行了组件的拆分。
(1)、使用 @Component
来装饰自定义组件,组成学生列表的Item。在ArkTS中,对组件配置属性与布局方法很简单,支持链式调用。
(2)、状态:@State
使用 @State
装饰符改变组件状态,声明式UI的特点就是UI是随数据更改而自动刷新的,我们这里定义了一个类型为 boolean
的变量 isChecked
,其被 @State
装饰后,框架内建立了数据和视图之间的绑定,其值的改变影响UI的显示。
@State isChecked: boolean = false;
- 可以使用
if/else
表达式来渲染组件的显示与消失,当判断条件为true时,组建为已完成的状态,图片Icon显示选中图标,反之则为未完成状态,图片Iocn显示已选中状态。 - ArkTS提供了
@Builder
装饰器,来修饰一个函数,快速生成布局内容,便面重复的UI描述内容。由于两个Image的实现具有大量重复代码,ArkTS提供了@Builder
装饰器,来修饰一个函数,快速生成布局内容,从而可以避免重复的UI描述内容。这里使用@Bulider
声明了一个checkIcon
的函数,参数为url,对应要传给Image的图片路径。
@Builder checkIcon(icon: Resource) {
Image(icon)
.objectFit(ImageFit.Contain)
.width($r('app.float.checkbox_width'))
.height($r('app.float.checkbox_height'))
.margin($r('app.float.checkbox_margin'))
}
- 字体使用了
三目运算符
来根据状态变化修改其透明度和文字样式,如opacity是控制透明度,decoration是文字是否有划线。通过isChecked
的值来控制其变化。
Row() {
if (this.isChecked) {
this.checkIcon($r('app.media.ic_checked'))
} else {
this.checkIcon($r('app.media.ic_unchecked'))
}
Text(this.name)
.fontColor(this.isChecked ? Color.Red : Color.Black)
.fontSize(this.isChecked ? $r('app.float.item_checked_font_size') : $r('app.float.item_font_size'))
.fontWeight(500)
.opacity(this.isChecked ? 0.5 : 1.0)
.decoration({ type: this.isChecked ? TextDecorationType.LineThrough : TextDecorationType.None })
}
- 在组件Row上添加
onClick
点击事件,根据isChecked
状态的更改来触发UI的更新。
Row() {
...
.borderRadius(22)
.backgroundColor($r('app.color.start_window_background'))
.width('100%')
.height($r('app.float.list_item_height'))
.onClick(() => {
this.isChecked = !this.isChecked;
})
(3)、StudentListItem
自定义组件完整代码(Item)
@Component
export default struct StudentListItem {
@State isChecked: boolean = false;
private name?: string;
@Builder checkIcon(icon: Resource) {
Image(icon)
.objectFit(ImageFit.Contain)
.width($r('app.float.checkbox_width'))
.height($r('app.float.checkbox_height'))
.margin($r('app.float.checkbox_margin'))
}
build() {
Row() {
if (this.isChecked) {
this.checkIcon($r('app.media.ic_checked'))
} else {
this.checkIcon($r('app.media.ic_unchecked'))
}
Text(this.name)
.fontColor(this.isChecked ? Color.Red : Color.Black)
.fontSize(this.isChecked ? $r('app.float.item_checked_font_size') : $r('app.float.item_font_size'))
.fontWeight(500)
.opacity(this.isChecked ? 0.5 : 1.0)
.decoration({ type: this.isChecked ? TextDecorationType.LineThrough : TextDecorationType.None })
}
.borderRadius(22)
.backgroundColor($r('app.color.start_window_background'))
.width('100%')
.height($r('app.float.list_item_height'))
.onClick(() => {
this.isChecked = !this.isChecked;
})
}
}
3、循环渲染列表数据
ForEach基本使用中,只需要了解 要渲染的数据
以及要生成的 UI内容
两个部分,例如这里要渲染的数组为以上的几个学生名单,要渲染的内容是 StudentListItem
这个自定义组件,也可以是其他内置组件。
下面这张图是从官网上拷贝过来的,很清晰的描述了ForEach基本使用方式。
StudentListItem
这个自定义组件中,每一个 StudentListItem
要显示的文本参数 name
需要外部传入,参数传递使用 花括号
的形式,用 name
接受数组内的内容项item。
ForEach(this.studentList, (item: string) => {
StudentListItem({ name: item })
}, (item: string) => JSON.stringify(item))
StudentListPage的完整代码:
import DataModel from '../viewmodel/DataModel';
import StudentListItem from './StudentListItem';
@Component
export struct StudentListPage {
private studentList: Array<string> = [];
aboutToAppear() {
this.studentList = DataModel.getStudentList();
}
build() {
Column({ space: 16 }) {
Text($r("app.string.Student_List_Title"))
.fontSize('28fp')
.fontWeight(FontWeight.Bold)
.lineHeight('33vp')
.width('100%')
.margin({
top: '24vp',
bottom: '12vp',
})
.textAlign(TextAlign.Center)
ForEach(this.studentList, (item: string) => {
StudentListItem({ name: item })
}, (item: string) => JSON.stringify(item))
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.page_background'))
}
}
在组件生命周期
aboutToAppear()
中去初始化加载数据。
加载数据的Model的代码:
export class DataModel {
private studentList: Array<string> = [
"丁程鑫",
"贺峻霖",
"肖战",
"成毅",
"刘耀文",
"李天泽",
"马嘉祺",
];
getStudentList(): Array<string> {
return this.studentList;
}
}
export default new DataModel();
总结
本文是根据HarmonyOs官网进行学习阅读 ArkTS开发语言介绍
基本课程的阅读笔记,以及实现了一个学生名单列表的简单页面的Demo样例 StudentListPage
,实现效果是使用 ForEach
实现了一个List列表,每个Item中使用了 Row
来实现横向布局,Row
中嵌套了 Icon
内置组件和 Text
内置组件,根据状态装饰符 @State
装饰的bool布尔变量 isChecked
值的改变,结合 if/else
表达式 、三目运算符
等来修改 Icon
和 Text
组件的UI,其中又实用了 ArkTS
提供的 @Builder
装饰器,来修饰一个函数,快速生成布局内容,便面重复的UI描述内容。
整体来说,HramonyOS
使用 ArkTS
声明式语言的开发方式来描述 UI
,这和 Jetpack Compose
开发、Flutter
的 Dart语言
跨平台开发、以及iOS的Swift开发方式非常相似,尤其组件名称和声明方式和Compose、Flutter非常相似,如果有过这几种系统、语言开发经验,上手HarmonyOS的ArkTS开发应该会很快。