随着华为宣布鸿蒙后续的版本不再兼容Android应用之后,对于现在的开发环境来说有一些冲击,一部分人想做鸿蒙应用开发,另一部分人觉得鸿蒙现在就想替换安卓还为之尚早,不管怎么说,学习是没有错的,哪怕是作为知识储备也是好的,今天就简单说一下鸿蒙应用开发支持的主流语言 ArkTS
。
前言
说到ArkTS
就得说一下DevEco Studio
的演变过程,在我写一篇关于鸿蒙的文章时,DevEco Studio
才刚推出不久,当时所支持的语言是Java、JS、C++等,在后续的版本中逐渐去掉了Java,C++,最终使用到了ArkTS,那么我们下面来了解一下ArkTS的由来。
正文
ArkTS是HarmonyOS主力应用开发语言,它在TypeScript(简称TS)
的基础上,匹配ArkUI
框架,扩展了声明式UI、状态管理等相应的能力,让开发者以更简洁、更自然的方式开发跨端应用,如果你之前接触过Flutter
的Dart
、Kotlin
的Compose
,那么你对于这个ArkTS
的使用应该问题不大。
ArkTS的构成如下图所示
JavaScript
、TypeScript
和 ArkTS
的关系:
JavaScript
是一种属于网络的高级脚本语言,已经被广泛用于Web
应用开发,常用来为网页添加各式各样的动态功能,为用户提供更流畅美观的浏览效果。TypeScript
是JavaScript
的一个超集,它扩展了JavaScript
的语法,通过在JavaScript的基础上添加静态类型定义构建而成,是一个开源的编程语言。ArkTS
基于TypeScript
语言,拓展了声明式UI、状态管理、并发任务等能力。
一、声明式UI
声明式UI有两个特征分别是:声明式描述
和状态驱动视图更新
,那么怎么体现这一点呢?我们结合代码来说明,首先打开DevEco Studio,这里我使用的版本是DevEco Studio 3.1.1 Release
,创建工程。
点击Next。
输入项目名称和包名,这里会说明使用的方式和语言及SDK版本和使用的设备类型,修改好之后点击Finish
,完成项目的创建。
完成创建之后我们就可以看到index.ets的代码如图所示,下面我们对这段代码进行一个解析:
装饰器
,用来装饰类、结构体、方法以及变量,赋予其特殊的含义,如上述示例中@Entry
、@Component
、@State
都是装饰器。具体而言,@Component
表示这是个自定义组件;@Entry
则表示这是个入口组件;@State
表示组件中的状态变量,此状态变化会引起 UI 变更。自定义组件
,可复用的 UI 单元,可组合其它组件,如上述被@Component
装饰的struct Index
。UI 描述
,声明式的方式来描述 UI 的结构,如上述 build() 方法内部的代码块。内置组件
,框架中默认内置的基础和布局组件,可直接被开发者调用,比如示例中的Row、Column、Text
。事件方法
,用于添加组件对事件的响应逻辑,统一通过事件方法进行设置,比如为组件添加onClick()
。属性方法
,用于组件属性的配置,统一通过属性方法进行设置,如fontSize()、width()、height()、color()
等,可通过链式调用的方式设置多项属性。
下面我们预览看一下,点击右侧边栏的Previewer
,等待一小会。
下面我们修改index.ets中的代码,然后Ctrl + S保存一下,右侧的预览画面就会更新。
预览更新后
点击Test按钮之后
这里我们添加了一个按钮,同时添加了点击事件,事件中修改了message的值,而message是由@State
修饰的,那么就会出发UI刷新,刷新后,Text组件所显示的内容就会从Hello World变成Hello ArkTS,这就是声明式UI的另一特征:状态驱动视图更新
。
二、数据列表
上面的示例比较简单,下面我们做一个有点难度的示例,该示例源于鸿蒙学堂官网,感兴趣的可以去学习。
① 创建ArkTS文件
首先我们在ets
目录下创建一个model
目录,model
目录下创建一个RankData.ets
文件,代码如下所示:
export class RankData { id: number | string name: Resource vote: string constructor(id: number | string, name: Resource, vote: string) { this.id = id; this.name = name; this.vote = vote; } }
这里的代码简单说明一下,export
表示可以在其他模块中使用,这里的含义就在于我们将RankData反正model目录下,如果我们pages下要使用这个RankData,则RankData本身就需要支持调用才行,因此需要export
进行修饰,调用的地方则使用import
作为插入。
而id属性的定义是一个联合类型,这属于TypeScript的基础数据类型,表示取值可以为多种类型中的一种。number表示数字,string就是字符串,Resource就表示资源,比如string.app_name这种方式。构造函数就没有什么好说的了,就是属性赋值而已。
② 添加资源
我们看到和ets
目录平级的是resources
,该目录下毫无疑问就是资源目录,目录下有三个文件夹,base属于基础资源目录里面可以放置文字、颜色、音频、配置文件等,en_US就是英文下的文字资源,zh_CN就是中文下的文字资源,三个目录下的文字资源文件都是json格式的,下面我们修改base/element/string.json
和en_US/element/string.json
中的代码:
{ "string": [ { "name": "module_desc", "value": "module description" }, { "name": "EntryAbility_desc", "value": "description" }, { "name": "EntryAbility_label", "value": "label" }, { "name": "page_type", "value": "variety" }, { "name": "page_number", "value": "ranking" }, { "name": "page_vote", "value": "vote" }, { "name": "prompt_text", "value": "Press again to exit the app" }, { "name": "title", "value": "Ranking List" }, { "name": "title_default", "value": " " }, { "name": "fruit_watermelon", "value": "watermelon" }, { "name": "fruit_apple", "value": "apple" }, { "name": "fruit_banana", "value": "banana" }, { "name": "fruit_grapes", "value": "grapes" }, { "name": "fruit_red_grape", "value": "grape" }, { "name": "fruit_pears", "value": "pears" }, { "name": "fruit_pineapple", "value": "pineapple" }, { "name": "fruit_durian", "value": "durian" }, { "name": "fruit_guava", "value": "guava" }, { "name": "fruit_carambola", "value": "carambola" } ] }
再修改zh_CN/element/string.json
中的代码:
{ "string": [ { "name": "module_desc", "value": "模块描述" }, { "name": "EntryAbility_desc", "value": "description" }, { "name": "EntryAbility_label", "value": "label" }, { "name": "page_type", "value": "种类" }, { "name": "page_number", "value": "排名" }, { "name": "page_vote", "value": "得票数" }, { "name": "prompt_text", "value": "再按一次退出程序" }, { "name": "title", "value": "排行榜" }, { "name": "title_default", "value": " " }, { "name": "fruit_watermelon", "value": "西瓜" }, { "name": "fruit_apple", "value": "苹果" }, { "name": "fruit_banana", "value": "香蕉" }, { "name": "fruit_grapes", "value": "葡萄" }, { "name": "fruit_red_grape", "value": "红提" }, { "name": "fruit_pears", "value": "梨子" }, { "name": "fruit_pineapple", "value": "菠萝" }, { "name": "fruit_durian", "value": "榴莲" }, { "name": "fruit_guava", "value": "番石榴" }, { "name": "fruit_carambola", "value": "杨桃" } ] }
下面我们制造一些假数据,在model
包下新建一个DataModel.ets
文件,代码如下所示:
import { RankData } from './RankData' export {rankData1, rankData2} const rankData1: RankData[] = [ new RankData(1, $r('app.string.fruit_apple'), "10000"), new RankData(2, $r('app.string.fruit_grapes'), '10320'), new RankData(3, $r('app.string.fruit_watermelon'), '9801'), new RankData(4, $r('app.string.fruit_banana'), '8431'), new RankData(5, $r('app.string.fruit_pineapple'), '7546'), new RankData(6, $r('app.string.fruit_durian'), '7431'), new RankData(7, $r('app.string.fruit_red_grape'), '7187'), new RankData(8, $r('app.string.fruit_pears'), '7003'), new RankData(9, $r('app.string.fruit_carambola'), '6794'), new RankData(10, $r('app.string.fruit_guava'), '6721') ] const rankData2: RankData[] = [ new RankData('11', $r('app.string.fruit_watermelon'), '8836'), new RankData('12', $r('app.string.fruit_apple'), '8521'), new RankData('13', $r('app.string.fruit_banana'), '8431'), new RankData('14', $r('app.string.fruit_grapes'), '7909'), new RankData('15', $r('app.string.fruit_red_grape'), '7547'), new RankData('16', $r('app.string.fruit_pears'), '7433'), new RankData('17', $r('app.string.fruit_pineapple'), '7186'), new RankData('18', $r('app.string.fruit_durian'), '7023'), new RankData('19', $r('app.string.fruit_guava'), '6794'), new RankData('20', $r('app.string.fruit_carambola'), '6721') ];
这里我们首先导入RankData
,然后创建了两个数组,数组中通过RankData
构建函数进行bean的构建,注意这里的id,我可以使用number也可以使用string,同时资源的引用是 $r
,r
就表示resource
,使用app.string
引用文字资源,你还可以app.color等一些方式引用其他类型资源,构建了两个数组,然后导出这两个数组在其他文件中使用。
现在有了模拟数据之后,我们可以再创建一个类去提供模拟数据,在model
包下新建一个RankViewModel.ets
文件,代码如下所示:
import { RankData } from './RankData'; import { rankData1, rankData2 } from './DataModel'; export class RankViewModel { loadRankDataSource1(): RankData[] { return rankData1; } loadRankDataSource2(): RankData[] { return rankData2; } }
这里导入了RankData和DataModel,通过在RankViewModel中进行返回数据得到具体的数据数组。这个其实和Android的MVI架构差不多,下面我们再添加一些colors资源,在后面的样式上会用到,修改base/element/color.json
文件,代码如下所示:
{ "color": [ { "name": "start_window_background", "value": "#FFFFFF" }, { "name": "white", "value": "#FFFFFF" }, { "name": "rank_first_gradient_start", "value": "#FFFF9A" }, { "name": "rank_first_gradient_end", "value": "#CCA538" }, { "name": "rank_first_border", "value": "#9E8A24" }, { "name": "rank_first_text", "value": "#9E8A24" }, { "name": "rank_secondary_gradient_start", "value": "#B8B8B8" }, { "name": "rank_secondary_gradient_end", "value": "#9C9C9C" }, { "name": "rank_secondary_border", "value": "#7E7E7E" }, { "name": "rank_secondary_text", "value": "#FFFFFF" }, { "name": "rank_third_gradient_start", "value": "#B9A185" }, { "name": "rank_third_gradient_end", "value": "#AE8659" }, { "name": "rank_third_border", "value": "#775C3E" }, { "name": "rank_third_text", "value": "#FFFFFF" }, { "name": "rank_view_color_holder", "value": "#FFFFFF" }, { "name": "item_color", "value": "#007DFF" }, { "name": "item_color_black", "value": "#182431" }, { "name": "background", "value": "#F1F3F5" }, { "name": "font_description", "value": "#989A9C" }, { "name": "circle_text_background", "value": "#007dff" } ] }
除此之外还有三个图标,你可以在我的源码中获取,放在resources/base/media
下
其中icon.png是创建工程时自带的图标,如果你觉得Project
模式下文件过多,你可以切换为Ohos
模式。
这样看起来比较简洁,只不过你需要熟悉文件结构才行。
③ 样式
在进行鸿蒙应用开发时,通常会将样式和代码进行分离,这一点是很常见了,我们在ets
目录下新建一个constants
文件夹,该目录下新建一个Constants.ets
文件,代码如下:
/** * 字体大小 */ export enum FontSize { SMALL = 14, MIDDLE = 16, LARGE = 20, }; /** * 字体粗细 */ export enum FontWeight { BOLD = '400', BOLDER = '500', }; /** * 权重是组件大小的全局默认值。 */ export const WEIGHT = '100%'; /** * Toast 出现的时间 */ export const TIME = 1000; /** * App退出的间隔时间 */ export const APP_EXIT_INTERVAL: number = 4500; /** * 页面TAG */ export const TAG: string = 'Index'; /** * 标题内容 */ export const TITLE: Resource = $r('app.string.title'); class style { RANK_PADDING: number = 15; // 排名填充 CONTENT_WIDTH: string = '90%'; // 内容的宽度 BORDER_RADIUS: number = 20; // 边界半径 STROKE_WIDTH: number = 1; // 描边宽度 HEADER_MARGIN_TOP: number = 20; // 距离上边距 HEADER_MARGIN_BOTTOM: number = 15;// 距离下边距 LIST_HEIGHT: string = '65%'; // List高度 } /** * 页面样式 */ export const Style: style = { RANK_PADDING: 15, CONTENT_WIDTH: '90%', BORDER_RADIUS: 20, STROKE_WIDTH: 1, HEADER_MARGIN_TOP: 20, HEADER_MARGIN_BOTTOM: 15, LIST_HEIGHT: '65%' }; class listHeaderStyle { FONT_WEIGHT: number = 400; // 字体粗细 LAYOUT_WEIGHT_LEFT: string = '30%'; // 左边的布局权重 LAYOUT_WEIGHT_CENTER: string = '50%'; // 中间的布局权重 LAYOUT_WEIGHT_RIGHT: string = '20%'; // 右边的布局权重 } /** * 列表标题样式 */ export const ListHeaderStyle: listHeaderStyle = { FONT_WEIGHT: 400, LAYOUT_WEIGHT_LEFT: '30%', LAYOUT_WEIGHT_CENTER: '50%', LAYOUT_WEIGHT_RIGHT: '20%', }; class itemStyle { TEXT_LAYOUT_SIZE: number = 24; // 文本的行高 CIRCLE_TEXT_BORDER_RADIUS: number = 24; // 圆形文本的边框半径 CIRCLE_TEXT_SIZE: number = 24; // 圆圈文本的大小 CIRCLE_TEXT_COLOR_STOP_1: number = 0.5; // 渐变色比例1 CIRCLE_TEXT_COLOR_STOP_2: number = 1.0; // 渐变色比例2 BAR_HEIGHT: number = 48; // item高度 LAYOUT_WEIGHT_LEFT: string = '30%'; // 左边的布局权重 LAYOUT_WEIGHT_CENTER: string = '50%'; // 中间的布局权重 LAYOUT_WEIGHT_RIGHT: string = '20%'; // 右边的布局权重 BORDER_WIDTH: number = 1; // 边框宽度 COLOR_BLUE: Resource = $r('app.color.item_color'); // 文字蓝色 COLOR_BLACK: Resource = $r('app.color.item_color_black'); // 文字黑色 } /** * 列表Item样式 */ export const ItemStyle: itemStyle = { TEXT_LAYOUT_SIZE: 24, CIRCLE_TEXT_BORDER_RADIUS: 24, CIRCLE_TEXT_SIZE: 24, CIRCLE_TEXT_COLOR_STOP_1: 0.5, CIRCLE_TEXT_COLOR_STOP_2: 1.0, BAR_HEIGHT: 48, LAYOUT_WEIGHT_LEFT: '30%', LAYOUT_WEIGHT_CENTER: '50%', LAYOUT_WEIGHT_RIGHT: '20%', BORDER_WIDTH: 1, COLOR_BLUE: $r('app.color.item_color'), COLOR_BLACK: $r('app.color.item_color_black') }; class titleBarStyle { IMAGE_BACK_SIZE: number = 21; // 后退按钮的图像大小 IMAGE_BACK_MARGIN_RIGHT: number = 18; // 后退按钮的右边距 IMAGE_LOADING_SIZE: number = 22; // 刷新按钮的图像大小 BAR_HEIGHT: number = 47; // 标题栏的高度 BAR_MARGIN_HORIZONTAL: number = 26; // 标题组件的水平边距 BAR_MARGIN_TOP: number = 10; // 标题组件的上边距 WEIGHT: string = '50%'; // 行布局的权重 } /** * 标题栏样式 */ export const TitleBarStyle: titleBarStyle = { IMAGE_BACK_SIZE: 21, IMAGE_BACK_MARGIN_RIGHT: 18, IMAGE_LOADING_SIZE: 22, BAR_HEIGHT: 47, BAR_MARGIN_HORIZONTAL: 26, BAR_MARGIN_TOP: 10, WEIGHT: '50%', };
这里的代码乍一看很多,不好理解,其实我们分析一下就知道是写什么属性,首先我们定义了页面字体大小和粗细的枚举类型,用于设置标题文字和其他文字,然后就是页面的权重、退出App的提示时间等、接着就是定义页面样式、标题栏样式、列表头样式、列表Item样式,通过注释你可以你知道每一个样式是什么意思,熟能生巧,你现在觉得不适应是因为不熟悉的缘故。
④ 组件
在ArkTS中组件是一个比较重要的知识点,组件也分为三个类型,基础组件、容器组件和自定义组件。
- 基础组件,比如Text、Button、Image、TextInput等。
- 容器组件,比如Column、Row、Stack、List等。
- 自定义组件,则是根据实际的功能需求,由开发者自己组合使用基础组件和容器组件变成新的功能组件。比如页面的标题栏,左侧是返回按钮,中间是标题文字,可能还会有副标题,右侧是功能按钮,这种就是自定义组件。
Harmony ArkTS语言(下)https://developer.aliyun.com/article/1407895