HarmonyOS开发学习(1)–目录认识与基本开发

目录认识

项目目录大致如下:

其中详细如下:

  • AppScope中存放应用全局所需要的资源文件。
  • entry是应用的主模块,存放HarmonyOS应用的代码、资源等。
  • oh_modules是工程的依赖包,存放工程依赖的源文件。
  • build-profile.json5是工程级配置信息,包括签名、产品配置等。
  • hvigorfile.ts是工程级编译构建任务脚本,hvigor是基于任务管理机制实现的一款全新的自动化构建工具,主要提供任务注册编排,工程模型管理、配置管理等核心能力。
  • oh-package.json5是工程级依赖配置文件,用于记录引入包的配置信息。

在AppScope,其中有resources文件夹和配置文件app.json5。AppScope>resources>base中包含element和media两个文件夹,

  • 其中element文件夹主要存放公共的字符串、布局文件等资源。
  • media存放全局公共的多媒体资源文件。

AppScope>app.json5是应用的全局的配置文件,用于存放应用公共的配置信息。

其中配置信息如下:

  • bundleName是包名。
  • vendor是应用程序供应商。
  • versionCode是用于区分应用版本。
  • versionName是版本号。
  • icon对应于应用的显示图标。
  • label是应用名。

模块目录

entry>src目录中主要包含总的main文件夹,单元测试目录ohosTest,以及模块级的配置文件。

  • main文件夹中,ets文件夹用于存放ets代码,resources文件存放模块内的多媒体及布局文件等,module.json5文件为模块的配置文件。
  • ohosTest是单元测试目录。
  • build-profile.json5是模块级配置信息,包括编译构建配置项。
  • hvigorfile.ts文件是模块级构建脚本。
  • oh-package.json5是模块级依赖配置信息文件。

进入src>main>ets目录中,其分为entryability、pages两个文件夹。

  • entryability存放ability文件,用于当前ability应用逻辑和生命周期管理。
  • pages存放UI界面相关代码文件,初始会生成一个Index页面。

个人理解:entryability内部从存放ability文件,其表示应用程序页面容器,被module.json5注册,可以在内部引入pages
(后记:ability就是应用组件,每个ability都会在任务栏里添加一个新的任务,个人认为一般项目只需要一个ability即可)
pages内部存放页面文件,即页面显示的具体代码,被ability引用
除此之外可能存在
common:存放常量
view:用于存放自定义组件
model:用于存放数据
viewmodel:用于存放实体类、业务逻辑类等

resources目录下存放模块公共的多媒体、字符串及布局文件等资源,分别存放在element、media文件夹中。

entry>src>main>module.json5是模块的配置文件,包含当前模块的配置信息。

其中module对应的是模块的配置信息,一个模块对应一个打包后的hap包,hap包全称是HarmonyOS Ability Package,其中包含了ability、第三方库、资源和配置文件。其具体属性及其描述可以参照下表1。

属性描述
name该标签标识当前module的名字,module打包成hap后,表示hap的名称,标签值采用字符串表示(最大长度31个字节),该名称在整个应用要唯一。
type表示模块的类型,类型有三种,分别是entry、feature和har。
srcEntry当前模块的入口文件路径。
description当前模块的描述信息。
mainElement该标签标识hap的入口ability名称或者extension名称。只有配置为mainElement的ability或者extension才允许在服务中心露出。
deviceTypes该标签标识hap可以运行在哪类设备上,标签值采用字符串数组的表示。
deliveryWithInstall标识当前Module是否在用户主动安装的时候安装,表示该Module对应的HAP是否跟随应用一起安装。- true:主动安装时安装。- false:主动安装时不安装。
installationFree标识当前Module是否支持免安装特性。- true:表示支持免安装特性,且符合免安装约束。- false:表示不支持免安装特性。
pages对应的是main_pages.json文件,用于配置ability中用到的page信息。
abilities是一个数组,存放当前模块中所有的ability元能力的配置信息,其中可以有多个ability。
表1 module.json5默认配置属性及描述

对于abilities中每一个ability的属性项,其描述信息如下表2。

属性描述
name该标签标识当前ability的逻辑名,该名称在整个应用要唯一,标签值采用字符串表示(最大长度127个字节)。
srcEntryability的入口代码路径。
descriptionability的描述信息。
iconability的图标。该标签标识ability图标,标签值为资源文件的索引。该标签可缺省,缺省值为空。如果ability被配置为MainElement,该标签必须配置。
labelability的标签名。
startWindowIcon启动页面的图标。
startWindowBackground启动页面的背景色。
visibleability是否可以被其他应用程序调用,true表示可以被其它应用调用, false表示不可以被其它应用调用。
skills标识能够接收的意图的action值的集合,取值通常为系统预定义的action值,也允许自定义。
entities标识能够接收Want的Entity值的集合。
actions标识能够接收的Want的Action值的集合,取值通常为系统预定义的action值,也允许自定义。
表2 abilities中对象的默认配置属性及描述

基本语法

Harmony使用的ArkTS语言,其作为TS的超集,兼容TypeScript的语法,因此基本语法也与此相同:

基本数据类型:

TypeScript支持一些基础的数据类型,如布尔型、数组、字符串等,下文举例几个较为常用的数据类型,我们来了解下他们的基本使用。

布尔值

TypeScript中可以使用boolean来表示这个变量是布尔值,可以赋值为true或者false。

let isDone: boolean = false;

数字

TypeScript里的所有数字都是浮点数,这些浮点数的类型是 number。除了支持十进制,还支持二进制、八进制、十六进制。

let decLiteral: number = 2023;
let binaryLiteral: number = 0b11111100111;
let octalLiteral: number = 0o3747;
let hexLiteral: number = 0x7e7;

字符串

TypeScript里使用 string表示文本数据类型, 可以使用双引号( “)或单引号(’)表示字符串。

let name: string = "Jacky";
name = "Tom";name = 'Mick';

数组

TypeScrip有两种方式可以定义数组。 第一种,可以在元素类型后面接上 [],表示由此类型元素组成的一个数组。

let list: number[] = [1, 2, 3];

第二种方式是使用数组泛型,Array<元素类型>。

let list: Array<number> = [1, 2, 3];

元组

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。 比如,你可以定义一对值分别为 string和number类型的元组。

let x: [string, number];
x = ['hello', 10]; // OK
x = [10, 'hello']; // Error

枚举

enum类型是对JavaScript标准数据类型的一个补充,使用枚举类型可以为一组数值赋予友好的名字。

enum Color {Red, Green, Blue};
let c: Color = Color.Green;

Unknown

有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。那么我们可以使用unknown类型来标记这些变量。

let notSure: unknown = 4;
notSure = 'maybe a string instead';
notSure = false;

Void

当一个函数没有返回值时,你通常会见到其返回值类型是 void。

function test(): void {
   console.log('This is function is void');
}

Null 和 Undefined

TypeScript里,undefined和null两者各自有自己的类型分别叫做undefined和null。

let u: undefined = undefined;
let n: null = null;

联合类型

联合类型(Union Types)表示取值可以为多种类型中的一种。

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

条件语句:

TypeScript使用的条件语句与一般语句相同

函数:

TypeScript函数有两种

// 有名函数
function add(x, y) {
  return x + y;
}

// 匿名函数
let myAdd = function (x, y) {
  return x + y;
};

在TypeScript里我们可以在参数名旁使用 ?实现可选参数的功能。 比如,我们想让lastName是可选的:

function buildName(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + ' ' + lastName;
    else
        return firstName;
}
let result1 = buildName('Bob');
let result2 = buildName('Bob', 'Adams'); 

剩余参数

剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。 可以使用省略号( …)进行定义:

function getEmployeeName(firstName: string, ...restOfName: string[]) {
  return firstName + ' ' + restOfName.join(' ');
}

let employeeName = getEmployeeName('Joseph', 'Samuel', 'Lucas', 'MacKinzie');

迭代器:

for..of 语句

for..of会遍历可迭代的对象,调用对象上的Symbol.iterator方法。 下面是在数组上使用for..of的简单例子:

let someArray = [1, "string", false];
for (let entry of someArray) {
    console.log(entry); // 1, "string", false
}

for..of vs. for..in 语句

for..of和for..in均可迭代一个列表,但是用于迭代的值却不同:for..in迭代的是对象的键,而for..of则迭代的是对象的值。

let list = [4, 5, 6];
for (let i in list) {
    console.log(i); // "0", "1", "2",
}
for (let i of list) {
    console.log(i); // "4", "5", "6"
}

ArkTs

上面的是我对TS部分语法重点的记录,接下来则是ArkTs部分的记录:

ArkUI开发框架:

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() 等,可通过链式调用的方式设置多项属性。

ArkUI完整的开发范式可参考这里:

https://developer.harmonyos.com/cn/docs/documentation/doc-guides/arkui-overview-0000001281480754

1.在开发中我们不可避免需要去循环数组生成组件,ArkTS就提供了forEach组件:

ForEach(
  arr: any[], // 用于迭代的数组
  itemGenerator: (item: any, index?: number) => void, // 生成子组件的lambda函数
  keyGenerator?: (item: any, index?: number) => string // 用于给定数组项生成唯一且稳定的键值
)

2.组件状态管理装饰器和@Builder装饰器:

组件状态管理装饰器用来管理组件中的状态,它们分别是:@State、@Prop、@Link。

  • @State装饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的build方法进行UI刷新。
  • @Prop与@State有相同的语义,但初始化方式不同。@Prop装饰的变量必须使用其父组件提供的@State变量进行初始化,允许组件内部修改@Prop变量,但更改不会通知给父组件,即@Prop属于单向数据绑定。
  • @Link装饰的变量可以和父组件的@State变量建立双向数据绑定,需要注意的是:@Link变量不能在组件内部进行初始化。
  • @Builder装饰的方法用于定义组件的声明式UI描述,在一个自定义组件内快速生成多个布局内容。

@State、@Prop、@Link三者关系如图所示:

3.组件生命周期函数

自定义组件的组成

ArkTS通过struct声明组件名,并通过@Component和@Entry装饰器,来构成一个自定义组件。

使用@Entry和@Component装饰的自定义组件作为页面的入口,会在页面加载时首先进行渲染。

@Entry
@Component
struct ToDoList {...}

在自定义组件内需要使用build方法来进行UI描述。build方法内可以容纳内置组件和其他自定义组件,如Column和Text都是内置组件,由ArkUI框架提供,ToDoItem为自定义组件,需要开发者使用ArkTS自行声明。

@Entry
@Component
struct ToDoList {
  ...
  build() {
    Column(...) {
      Text(...)
        ...
      ForEach(...{
        TodoItem(...)
      },...)
    }
  ...
  }
}

配置属性与布局

  • 常量传递
    例如使用fontSize(50)来配置字体大小。
Text('Hello World')
  .fontSize(50)
  • 变量传递
    在组件内定义了相应的变量后,例如组件内部成员变量size,就可以使用this.size方式使用该变量。
Text('Hello World')
  .fontSize(this.size)
  • 链式调用
    在配置多个属性时,ArkTS提供了链式调用的方式,通过’.’方式连续配置。
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)
  • 内置枚举类型
    除此之外,ArkTS中还提供了内置枚举类型,如Color,FontWeight等,例如设置fontColor改变字体颜色为红色,并私有fontWeight为加粗。
Text('Hello World')
  .fontSize(this.size)
  .width(this.count + 100)
  .height(this.count % 2 === 0 ? 100 : 200)
  .fontColor(Color.Red)
  .fontWeight(FontWeight.Bold)

布局容器

ArkTS提供了许多布局容器用于实现页面布局,其中最基本的是列布局和行布局:

列布局Row

Row() {
  Image($r('app.media.ic_default'))
    ...
  Text(this.content) 
    ...
}
...

列布局是从左往右横向排序

行布局Column

Column() {
   Text($r('app.string.page_title'))
     ...

   ForEach(this.totalTasks,(item) => {
     TodoItem({content:item})
   },...)
 }

行布局是从上往下纵向排序

改变组件状态

上面我们说过,被@State修饰的变量进行改变的时候,会更新对应组件,因此,我们可以使用这个特性来实现组件状态的改变。

如同一般策略,我们可以定义boolean类型的变量来实现:

export struct todoItem{
  private  content:string
  @State isClick:boolean=false
  build(){
    Row(){
      if (this.isClick){
        Text("adasd")
      }else{
        Text("vvv")
      }
      Text(this.content)
        .opacity(this.isClick ? CommonConstants.OPACITY_COMPLETED : CommonConstants.OPACITY_DEFAULT)
        .decoration({type:this.isClick?TextDecorationType.LineThrough:TextDecorationType.None})
    }
    .borderRadius(24)
    .backgroundColor("#FFFFFF")
    .width('93.3%')
    .height('64vp')
    .onClick(()=>{
      this.isClick=!this.isClick
    })
  }
}

这里我们使用onClick方法来实现变量值的改变,并通过ifelse和三目运算符来实现组件改变。