【鸿蒙学习笔记】鸿蒙开发探秘:深入解析ArkTS语言与应用开发

文章目录

    • [一、鸿蒙开发与 ArkTS 语言概述](#一、鸿蒙开发与 ArkTS 语言概述)
      • [1.1 鸿蒙系统简介](#1.1 鸿蒙系统简介)
      • [1.2 ArkTS 语言的地位与特点](#1.2 ArkTS 语言的地位与特点)
    • [二、ArkTS 基础语法详解](#二、ArkTS 基础语法详解)
      • [2.1 变量与数据类型](#2.1 变量与数据类型)
      • [2.2 函数定义与调用](#2.2 函数定义与调用)
      • [2.3 控制流语句](#2.3 控制流语句)
      • [2.4 类与面向对象编程](#2.4 类与面向对象编程)
    • [三、基于 ArkTS 的 UI 开发](#三、基于 ArkTS 的 UI 开发)
      • [3.1 声明式 UI 概念与优势](#3.1 声明式 UI 概念与优势)
      • [3.2 常用 UI 组件](#3.2 常用 UI 组件)
      • [3.3 布局容器的使用](#3.3 布局容器的使用)
    • 四、状态管理在鸿蒙应用中的实践
      • [4.1 状态管理概念](#4.1 状态管理概念)
      • [4.2 常用状态装饰器](#4.2 常用状态装饰器)
      • [4.3 状态管理最佳实践](#4.3 状态管理最佳实践)
    • 五、鸿蒙应用开发实战:构建简单应用
      • [5.1 开发环境搭建](#5.1 开发环境搭建)
      • [5.2 项目结构解析](#5.2 项目结构解析)
      • [5.3 资源管理基础](#5.3 资源管理基础)
      • [5.4 应用开发步骤与代码实现](#5.4 应用开发步骤与代码实现)

一、鸿蒙开发与 ArkTS 语言概述

1.1 鸿蒙系统简介

鸿蒙系统(HarmonyOS)是华为公司自主研发的一款面向全场景的分布式操作系统,其设计初衷是为了解决多设备之间的互联互通和协同工作问题,旨在为用户提供更加智能、便捷的全场景体验。

鸿蒙系统的分布式特性是其核心优势之一。它通过分布式软总线、分布式数据管理、分布式任务调度等技术,实现了不同设备之间的无缝连接和协同工作,打破了设备之间的界限,让多个设备可以像一个设备一样协同工作,形成一个超级终端。例如,在智能家居场景中,用户可以通过手机控制智能音箱播放音乐,同时将手机上的视频投屏到智能电视上播放,实现多设备之间的协同工作。

鸿蒙系统的应用场景非常广泛,涵盖了智能家居、智慧办公、智慧出行、影音娱乐、运动健康等多个领域。在智能家居领域,鸿蒙系统可以实现智能家电之间的互联互通,用户可以通过手机或智能音箱等设备控制家电的开关、调节温度等;在智慧办公领域,鸿蒙系统可以实现多设备之间的文件共享、协同编辑等功能,提高办公效率;在智慧出行领域,鸿蒙系统可以与车载系统集成,实现车辆的智能控制和信息交互;在影音娱乐领域,鸿蒙系统可以提供更加流畅的视频播放和音乐播放体验;在运动健康领域,鸿蒙系统可以与智能穿戴设备集成,实现运动数据的实时监测和分析。

1.2 ArkTS 语言的地位与特点

ArkTS(Ark TypeScript)是鸿蒙开发的主力编程语言,它是在 TypeScript 的基础上扩展而来,专为鸿蒙系统应用开发而设计。ArkTS 在鸿蒙开发中占据着举足轻重的地位,它为开发者提供了一种高效、简洁的开发方式,能够帮助开发者快速构建出高性能、高质量的鸿蒙应用。

ArkTS 具有以下几个特点:

  • 静态类型:ArkTS 继承了 TypeScript 的静态类型特性,在编译时进行严格的类型检查,可以提前发现类型错误,提高代码的稳定性和可维护性。例如:
typescript 复制代码
let num: number = 10;
num = "hello"; // 编译时会报错,因为类型不匹配
  • 声明式 UI:ArkTS 采用声明式的 UI 开发范式,开发者只需描述 UI 的最终状态,而无需关注 UI 的构建过程,大大提高了开发效率。例如,使用 ArkTS 创建一个简单的按钮:
typescript 复制代码
@Entry
@Component
struct ButtonExample {
  build() {
    Column() {
      Button("Click Me")
      .onClick(() => {
          console.log("Button clicked");
        })
    }
  }
}
  • 响应式编程:ArkTS 支持响应式编程,通过状态管理机制,当数据状态发生变化时,UI 会自动更新,实现数据与 UI 的双向绑定,简化了开发过程。例如:
typescript 复制代码
@Entry
@Component
struct Counter {
  @State count: number = 0;

  build() {
    Column() {
      Text(`Count: ${this.count}`);
      Button("Increment")
      .onClick(() => {
          this.count++;
        });
    }
  }
}
  • 跨平台:ArkTS 编写的代码可以在多种鸿蒙设备上运行,实现一次开发,多端部署,降低了开发成本,提高了开发效率 。这使得开发者能够为不同类型的设备(如手机、平板、智能手表、智能音箱等)提供统一的应用体验,无需为每个设备单独开发一套代码。例如,一个基于 ArkTS 开发的新闻阅读应用,可以在手机上以简洁的列表形式展示新闻内容,在平板上以分栏布局展示新闻列表和详细内容,在智能手表上则以简洁的通知形式提醒用户有新的新闻。

二、ArkTS 基础语法详解

2.1 变量与数据类型

在 ArkTS 中,变量用于存储数据,数据类型则决定了变量能够存储的数据种类和可以执行的操作。

变量声明使用letconst关键字。let声明的变量可以重新赋值,而const声明的常量一旦赋值就不能再改变。例如:

typescript 复制代码
let count: number = 0;
count = 1; // 合法,count可以重新赋值

const PI: number = 3.14;
// PI = 3.14159; // 非法,PI是常量,不能重新赋值

ArkTS 支持多种基本数据类型:

  • number :表示数字,包括整数和浮点数,例如103.14

  • string :表示字符串,用于存储文本数据,例如"Hello, ArkTS"

  • boolean :表示布尔值,只有truefalse两个值,常用于条件判断 。

  • null:表示空值,代表一个空对象指针。

  • undefined :表示未定义,当一个变量声明但未初始化时,其值为undefined

除了基本数据类型,ArkTS 还支持一些复杂数据类型:

  • 数组(Array):用于存储一组相同类型的数据,可以通过索引访问数组元素。例如:
typescript 复制代码
let numbers: number[] = [1, 2, 3, 4, 5];
console.log(numbers[0]); // 输出 1
  • 对象(Object):由一组键值对组成,用于存储和管理相关的数据。例如:
typescript 复制代码
let person = {
  name: "John",
  age: 30,
  profession: "Engineer"
};
console.log(person.name); // 输出 John
  • 枚举(Enum):用于定义一组命名的常量。例如:
typescript 复制代码
enum Color {
  Red,
  Green,
  Blue
}
let myColor: Color = Color.Green;
console.log(myColor); // 输出 1,枚举值默认从0开始编号

2.2 函数定义与调用

函数是一段可重复使用的代码块,用于执行特定的任务。在 ArkTS 中,函数定义包括函数名、参数列表、返回值类型和函数体。

函数声明的基本语法如下:

typescript 复制代码
function functionName(parameter1: type1, parameter2: type2): returnType {
  // 函数体
  return result;
}

例如,定义一个加法函数:

typescript 复制代码
function add(a: number, b: number): number {
  return a + b;
}

调用函数时,使用函数名并传入相应的参数:

typescript 复制代码
let sum: number = add(3, 5);
console.log(sum); // 输出 8

在函数定义中,可以使用箭头函数来简化函数的写法,箭头函数通常用于定义简短的匿名函数。例如,上述加法函数可以用箭头函数表示为:

typescript 复制代码
const add = (a: number, b: number): number => a + b;

ArkTS 还支持函数的可选参数和默认参数。可选参数在参数名后加?,表示该参数可以不传;默认参数为参数提供一个默认值,当调用函数时未传入该参数时,将使用默认值。例如:

typescript 复制代码
function greet(name?: string, message: string = "Hello"): void {
  if (name) {
    console.log(`${message}, ${name}`);
  } else {
    console.log(message);
  }
}
greet(); // 输出 Hello
greet("John"); // 输出 Hello, John
greet("John", "Hi"); // 输出 Hi, John

2.3 控制流语句

控制流语句用于控制程序的执行流程,根据不同的条件执行不同的代码块。

条件语句(if - else):根据条件的真假来决定执行哪个代码块。例如:

typescript 复制代码
let score: number = 85;
if (score >= 90) {
  console.log("优秀");
} else if (score >= 60) {
  console.log("及格");
} else {
  console.log("不及格");
}

循环语句(for、while) :用于重复执行一段代码。for循环通常用于已知循环次数的情况,while循环则用于在条件为真时持续循环。例如:

typescript 复制代码
// for循环
for (let i: number = 0; i < 5; i++) {
  console.log(i);
}
// while循环
let count: number = 0;
while (count < 5) {
  console.log(count);
  count++;
}

分支语句(switch - case):根据一个表达式的值来选择执行不同的代码块,适用于多条件判断的场景。例如:

typescript 复制代码
let day: number = 3;
switch (day) {
  case 1:
    console.log("Monday");
    break;
  case 2:
    console.log("Tuesday");
    break;
  case 3:
    console.log("Wednesday");
    break;
  default:
    console.log("Other day");
}

2.4 类与面向对象编程

类是面向对象编程的核心概念,它是一种抽象的数据类型,用于创建对象。在 ArkTS 中,类定义包括属性、方法和构造函数。

定义一个简单的类:

typescript 复制代码
class Person {
  // 属性
  name: string;
  age: number;

  // 构造函数,用于初始化对象
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  // 方法
  greet(): void {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
}

创建类的实例并调用方法:

typescript 复制代码
let person: Person = new Person("Alice", 25);
person.greet(); // 输出 Hello, my name is Alice and I am 25 years old.

类的属性可以分为私有属性和公有属性。私有属性在属性名前加private关键字,只能在类内部访问;公有属性可以在类外部访问。例如:

typescript 复制代码
class Rectangle {
  private width: number;
  private height: number;

  constructor(width: number, height: number) {
    this.width = width;
    this.height = height;
  }

  getArea(): number {
    return this.width * this.height;
  }
}
let rect: Rectangle = new Rectangle(5, 3);
// console.log(rect.width); // 非法,width是私有属性,不能在类外部访问
console.log(rect.getArea()); // 输出 15

类还支持继承,通过继承可以创建一个新类,新类继承了父类的属性和方法,并且可以添加自己的属性和方法。例如:

typescript 复制代码
class Animal {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  speak(): void {
    console.log(`${this.name} makes a sound.`);
  }
}
class Dog extends Animal {
  breed: string;

  constructor(name: string, breed: string) {
    super(name);
    this.breed = breed;
  }

  speak(): void {
    console.log(`${this.name} barks.`);
  }
}
let dog: Dog = new Dog("Buddy", "Golden Retriever");
dog.speak(); // 输出 Buddy barks.

在上述例子中,Dog类继承自Animal类,并重写了speak方法,体现了多态性。多态性使得不同的对象可以对相同的方法调用做出不同的响应 。

三、基于 ArkTS 的 UI 开发

3.1 声明式 UI 概念与优势

在传统的命令式 UI 开发中,开发者需要详细地描述如何构建和更新 UI,包括创建 UI 元素、设置属性、处理事件等一系列具体的操作步骤 。以创建一个简单的按钮为例,在命令式 UI 中,可能需要以下步骤:

typescript 复制代码
// 创建按钮对象
let button = document.createElement('button');
// 设置按钮文本
button.textContent = 'Click Me';
// 添加点击事件处理函数
button.addEventListener('click', function() {
  console.log('Button clicked');
});
// 将按钮添加到页面中
document.body.appendChild(button);

这种方式虽然灵活,但代码较为繁琐,尤其是在处理复杂 UI 时,开发者需要花费大量精力来管理 UI 的状态和更新过程。

而声明式 UI 则采用了一种不同的思路,开发者只需描述 UI 的最终状态,即 "是什么",而无需关心 UI 的构建过程。框架会根据开发者的描述自动生成和更新 UI。在 ArkTS 中,使用声明式 UI 创建相同按钮的代码如下:

typescript 复制代码
@Entry
@Component
struct ButtonExample {
  build() {
    Column() {
      Button("Click Me")
      .onClick(() => {
          console.log("Button clicked");
        })
    }
  }
}

声明式 UI 具有以下优势:

  • 代码简洁:无需编写大量的底层操作代码,减少了代码量,提高了开发效率。例如,在构建一个复杂的表单时,声明式 UI 可以通过简洁的代码描述表单的结构和样式,而命令式 UI 则需要逐个创建表单元素并设置其属性。

  • 可读性好:代码结构与 UI 的层次结构更加相似,易于理解和维护。其他开发者可以快速了解 UI 的布局和功能。例如,在一个包含多个组件的页面中,声明式 UI 的代码可以清晰地展示各个组件的嵌套关系和属性设置。

  • 维护方便:当 UI 需求发生变化时,只需修改声明式代码中的描述部分,框架会自动更新 UI,而无需手动修改大量的操作代码。例如,如果需要修改按钮的文本和样式,在声明式 UI 中,只需在相应的组件中修改属性值即可。

  • 开发效率高:声明式 UI 通常结合响应式编程,能够自动处理数据变化对 UI 的影响,减少了手动更新 UI 的工作量。例如,当数据模型中的某个值发生变化时,与之绑定的 UI 元素会自动更新显示。

3.2 常用 UI 组件

Text 组件:用于显示文本内容,支持设置文本的字体、大小、颜色、对齐方式等属性。例如:

typescript 复制代码
Text("Hello, ArkTS")
.fontSize(20)
.fontColor(Color.Blue)
.textAlign(TextAlign.Center)

Button 组件:用于创建按钮,支持设置按钮的文本、样式、点击事件等。按钮类型包括胶囊按钮、圆形按钮、普通按钮。Button 当做为容器使用时可以通过添加子组件实现包含文字、图片等元素的按钮。例如:

typescript 复制代码
// 创建不包含子组件的普通按钮
Button('Ok', { type: ButtonType.Normal, stateEffect: true })
.borderRadius(8)
.backgroundColor(0x317aff)
.width(90)
.height(40)
.onClick(() => {
  console.log('Button clicked');
})
// 创建包含子组件的按钮
Button({ type: ButtonType.Normal, stateEffect: true }) {
  Row() {
    Image($r('app.media.loading')).width(20).height(40).margin({ left:12 })
    Text('loading').fontSize(12).fontColor(0xffffff).margin({ left:5, right: 12 })
  }.alignItems(VerticalAlign.Center)
}.borderRadius(8).backgroundColor(0x317aff).width(90).height(40)

Image 组件:用于显示图片,支持设置图片的源路径、大小、缩放模式等属性。例如:

typescript 复制代码
Image($r('app.media.icon'))
.width(100)
.height(100)
.objectFit(ImageFit.Cover)

TextInput 组件:用于接收用户的文本输入,支持设置输入框的提示文本、输入类型(如数字、密码等)、输入事件等。例如:

typescript 复制代码
TextInput({
  placeholder: '请输入用户名',
  type: InputType.Text
})
.width('100%')
.height(40)
.onChange((value) => {
  console.log('输入内容:', value);
})

3.3 布局容器的使用

Column 组件 :沿垂直方向布局的容器,其子元素按照垂直方向依次排列。可以通过space属性设置子元素垂直方向间距,通过alignItems属性设置子元素在水平方向上的对齐格式,通过justifyContent属性设置子元素在垂直方向上的对齐格式 。例如:

typescript 复制代码
Column({ space: 10 })
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Start)
{
  Text('Item 1')
  Text('Item 2')
  Text('Item 3')
}

Row 组件 :沿水平方向布局的容器,其子元素按照水平方向依次排列。同样可以设置spacealignItems(此时设置子元素在垂直方向上的对齐格式)和justifyContent(设置子元素在水平方向上的对齐格式)等属性。例如:

typescript 复制代码
Row({ space: 15 })
.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.SpaceBetween)
{
  Text('Left')
  Text('Center')
  Text('Right')
}

Stack 组件 :层叠布局容器,其子元素会按照添加的顺序进行层叠,后一个子元素会覆盖在前一个子元素之上。可以通过alignContent参数实现子元素位置的相对移动,支持九种对齐方式 。通过zIndex属性改变兄弟组件显示层级关系,zIndex值越大,显示层级越高。例如:

typescript 复制代码
Stack({ alignContent: Alignment.TopStart })
{
  Text('Bottom')
  Text('Middle').zIndex(1)
  Text('Top').zIndex(2)
}

List 组件 :用于展示列表数据,支持设置列表的方向(垂直或水平)、列表项之间的间距等属性。通过ForEach循环遍历数据数组来生成列表项。例如:

typescript 复制代码
let data = ['Apple', 'Banana', 'Cherry'];
List({ space: 8, listDirection: Axis.Vertical })
{
  ForEach(data, (item) => {
    ListItem() {
      Text(item)
    }
  }, item => item)
}

四、状态管理在鸿蒙应用中的实践

4.1 状态管理概念

在鸿蒙应用开发中,状态管理是构建响应式界面的核心支柱,而单向数据流(Unidirectional Data Flow, UDF)作为鸿蒙架构的重要设计原则,贯穿于组件通信、状态更新和界面渲染的全流程。

状态管理,简单来说,就是对应用程序中数据状态的管理和控制,以确保应用在不同场景下的正确行为和良好用户体验。它主要负责维护应用的数据模型,并确保数据的变化能够及时、准确地反映在用户界面(UI)上,实现数据与 UI 展示之间的同步。

以一个简单的计数器应用为例,计数器的当前数值就是一个状态。当用户点击 "增加" 或 "减少" 按钮时,这个状态会发生变化,而 UI 界面上显示的计数器数值也应该随之更新 。在这个过程中,状态的变化驱动了 UI 的更新,这就是状态驱动 UI 更新的概念。

单向数据流则是指数据在应用中按照固定方向流动:状态(State)驱动界面渲染,用户交互(Event)触发状态更新,形成「状态→视图→交互→新状态」的闭环。在鸿蒙中,这一过程通过 @State、@Prop 、@Link 等装饰器实现精准的状态绑定与更新,确保数据变化可追踪、可预测。其具有以下核心特征:

  • 单一数据源:每个组件状态由唯一源头管理(如组件内 @State、父组件传递的 @Prop),避免数据不一致。

  • 单向传递:状态从父组件到子组件单向流动,子组件通过回调通知父组件更新,杜绝循环依赖。

  • 声明式更新:只需修改状态值,框架自动完成界面重渲染,无需手动操作 DOM。

状态容器则是存储和管理状态的机制,可以将状态管理比喻为 "数据盒子":当 "盒子" 里的数据变化时,使用这些数据的 UI 会自动更新,且只能通过特定方式修改 "盒子" 里的数据,确保可预测性。

4.2 常用状态装饰器

@State 装饰器:用于声明组件的内部状态变量。被 @State 修饰的变量,当它的值发生变化时,会自动触发组件的重新渲染,从而实现 UI 的更新。它是最基本的状态管理装饰器,主要用于管理组件自身的状态。例如在一个计数器组件中:

typescript 复制代码
@Entry
@Component
struct Counter {
  @State count: number = 0;
  build() {
    Column() {
      Text(`${this.count}`)
      .fontSize(30)
      .fontWeight(FontWeight.Bold);
      Button("增加")
      .onClick(() => {
          this.count++;
        });
      Button("减少")
      .onClick(() => {
          this.count--;
        });
    }
  }
}

在上述代码中,count 变量被 @State 修饰,当用户点击 "增加" 或 "减少" 按钮,count 的值发生变化时,Text 组件会自动更新显示最新的 count 值。

@Link 装饰器:用于建立父子组件之间的双向数据绑定,实现父子组件之间的数据双向同步。例如,在一个包含输入框和显示文本的父子组件结构中:

typescript 复制代码
// 子组件
@Component
struct TextInput {
  @Link textValue: string;
  label: string = "输入内容";
  build() {
    Column() {
      Text(this.label);
      TextInput({ text: this.textValue })
     .onChange((value: string) => {
          this.textValue = value;
        });
    }
  }
}
// 父组件
@Component
struct HomePage {
  @State message: string = "你好";
  build() {
    Column({ space: 20 }) {
      Text(`父组件显示的消息: ${this.message}`);
      TextInput({ textValue: $message, label: '编辑消息' });
    }
  }
}

在上述代码中,父组件 HomePagemessage 状态变量通过 @Link 与子组件 TextInputtextValue 建立双向绑定。在 TextInput 子组件中输入内容,textValue 变化会同步到父组件的 message 上;父组件中 message 变化也会同步到子组件的 textValue 上 。

4.3 状态管理最佳实践

最小状态原则:只将需要驱动 UI 的数据声明为状态变量,避免不必要的状态声明,减少状态管理的复杂性。例如,在一个展示用户信息的组件中,如果用户的头像图片地址不会在组件内发生变化,就不需要将其声明为状态变量。

typescript 复制代码
// 正确做法,只将需要响应式更新的用户名声明为状态
@Entry
@Component
struct UserProfile {
  @State username: string = "John";
  build() {
    Column() {
      Image($r('app.media.avatar'))
      .width(100)
      .height(100);
      Text(this.username);
    }
  }
}

状态提升 :当多个组件需要共享状态时,将状态提升到共同的父组件。这样可以确保状态的唯一性和一致性,避免数据不一致的问题。例如,有两个子组件 Child1Child2 都需要使用某个状态 sharedState

typescript 复制代码
// 父组件
@Entry
@Component
struct Parent {
  @State sharedState: string = "初始值";
  build() {
    Column() {
      Child1({ state: this.sharedState });
      Child2({ state: this.sharedState });
    }
  }
}
// 子组件1
@Component
struct Child1 {
  @Prop state: string;
  build() {
    Text(this.state);
  }
}
// 子组件2
@Component
struct Child2 {
  @Prop state: string;
  build() {
    Text(this.state);
  }
}

不可变性 :尽量避免直接修改复杂对象,而是创建新对象。直接修改复杂对象可能会导致状态变化无法被正确检测,从而影响 UI 的更新。例如,对于一个包含用户信息的对象 user

typescript 复制代码
// 错误做法,直接修改对象属性,可能无法触发UI更新
@Entry
@Component
struct UserComponent {
  @State user = { name: "John", age: 30 };
  updateUser() {
    this.user.age = 31;
  }
  build() {
    Column() {
      Text(`Name: ${this.user.name}, Age: ${this.user.age}`);
      Button("Update Age")
      .onClick(() => {
          this.updateUser();
        });
    }
  }
}
// 正确做法,创建新对象,确保状态变化被检测到
@Entry
@Component
struct UserComponent {
  @State user = { name: "John", age: 30 };
  updateUser() {
    this.user = { ...this.user, age: 31 };
  }
  build() {
    Column() {
      Text(`Name: ${this.user.name}, Age: ${this.user.age}`);
      Button("Update Age")
      .onClick(() => {
          this.updateUser();
        });
    }
  }
}

状态分层 :区分局部状态(@State)和全局状态(AppStorage)。局部状态只在组件内部使用,全局状态则可以在整个应用中共享。例如,用户的登录状态可以作为全局状态存储在 AppStorage 中,而某个组件内的临时数据则作为局部状态使用 @State 管理。

typescript 复制代码
// 使用AppStorage管理全局状态
import AppStorage from '@ohos.appstorage';
// 存储全局状态
AppStorage.SetOrCreate('isLoggedIn', true);
// 获取全局状态
let isLoggedIn = AppStorage.Get('isLoggedIn');

避免过度状态:不要将所有数据都声明为状态,只关注影响 UI 的数据。过度的状态声明会增加应用的内存消耗和状态管理的难度。例如,一些只在组件内部计算使用,且不影响 UI 显示的数据,就不需要声明为状态变量。

五、鸿蒙应用开发实战:构建简单应用

5.1 开发环境搭建

要进行鸿蒙应用开发,首先需要搭建开发环境,主要工具是 DevEco Studio。以下是搭建步骤:

  1. 下载 DevEco Studio :访问华为开发者联盟官网(https://developer.huawei.com/consumer/cn/),在下载中心找到 DevEco Studio,根据你的操作系统(Windows 或 macOS)选择对应的安装包进行下载。

  2. 安装 DevEco Studio:下载完成后,双击安装包进行安装。以 Windows 系统为例,在安装向导中,选择安装路径(建议安装在非系统盘,如 D 盘),勾选 "创建桌面快捷方式" 和 "添加到环境变量" 选项,然后按照提示完成安装过程。安装过程中会自动下载 OpenJDK、Gradle 等工具 。

  3. 配置 Node.js 环境 :DevEco Studio 依赖 Node.js 环境,若本地未安装 Node.js,首次启动 DevEco Studio 时,IDE 会提示自动安装(安装路径不能含空格)。也可以自行前往 Node.js 官网(https://nodejs.org/en/download/)下载并安装最新版本的 Node.js。安装完成后,在命令行中输入node -v,检查 Node.js 是否安装成功。

  4. 配置 SDK:打开 DevEco Studio,进入 "Settings"(Windows 系统下为 "File"->"Settings"),在搜索框中输入 "HarmonyOS SDK",选择 "HarmonyOS SDK" 选项,设置 SDK 的存储路径(如 D:\Huawei\SDK),勾选 "Accept" 协议,等待 SDK 自动下载完成。此过程可能需要一些时间,请耐心等待。

  5. 安装模拟器:在 DevEco Studio 的欢迎界面,点击 "Configure"->"AVD Manager",在弹出的窗口中点击 "Create Device",选择合适的模拟器设备(如 Phone、Tablet 等),然后点击 "Next",选择系统镜像(如 API 12 及以上版本),最后点击 "Finish" 完成模拟器的创建。创建完成后,在 AVD Manager 中点击模拟器的 "Play" 按钮启动模拟器。

5.2 项目结构解析

以一个简单的鸿蒙应用项目为例,其目录结构如下:

Plain 复制代码
TodoApp/
├── AppScope/                 # 应用全局配置
│   └── resources/            # 应用全局资源
│       └── base
│           ├── element
│           │   └── string.json
│           └── media
│               └── app_icon.png
│   └── app.json5             # 应用全局配置文件
├── entry/                    # 主应用模块
│   ├── src/main/ets/          # 源代码目录
│   │   ├── entryability/     # 应用入口
│   │   │   └── EntryAbility.ts # 应用生命周期管理
│   │   ├── pages/            # 页面目录
│   │   │   ├── Index.ets     # 应用首页
│   │   │   ├── AddTodo.ets   # 添加待办页面
│   │   │   └── TodoDetail.ets# 待办详情页面
│   │   ├── components/       # 自定义组件目录
│   │   ├── models/           # 数据模型目录
│   │   └── services/         # 业务服务目录
│   ├── src/main/resources/   # 模块资源目录
│   │   ├── base
│   │   │   ├── element
│   │   │   ├── media
│   │   │   └── profile
│   │   └── rawfile
│   └── module.json5          # 模块配置文件
├── build-profile.json5       # 项目构建配置
└── oh_modules                # 用于存放三方库依赖信息
  • AppScope 目录:应用的全局配置和资源文件夹,通常包含一些全局资源和配置,供应用的各个模块共享。

    • resources:用于存放应用的资源文件,可以细分为多个子目录。

      • base:存放应用的基础资源,包括语言资源、样式资源等。

      • element:定义了应用中的基础元素,如字符串、数字、颜色、样式等资源。常见的如 string.json,其中存放了应用中会用到的字符串文本内容。

      • media:存放应用中的多媒体资源,如图标、背景图、音频等。

    • app.json5:此文件包含了应用的全局配置信息,通常包括应用名称、版本号、设备配置、权限声明等。

  • entry 目录:开发的核心部分,包含了应用模块的源代码、资源、配置以及构建任务。

    • src:存放应用的源代码,主要是 ArkTS 的代码部分,分为以下几个子目录。

      • ets:存放 ArkTS 源码,包括应用的入口、扩展能力以及页面定义。

        • entryability:定义应用的入口能力,通常会包含应用启动后的初始化逻辑。

        • pages:应用中的各个页面模块,通常是应用的各个视图层或 UI 层。每个页面对应一个.ets 文件,包含该页面的 UI 布局、业务逻辑、事件处理等内容。

        • components:存放可复用的自定义组件。

        • models:存放数据模型定义。

        • services:存放业务逻辑和服务。

      • resources:存放应用使用到的资源文件,类似 AppScope/resources,但一般这是局部资源,供当前模块使用。

        • base、media、profile:资源文件的具体分类。base 可能包括一些通用的样式文件,media 包括图片、音频等,profile 则是配置信息。

        • rawfile:原始资源文件,通常是应用中不需要设备适配的静态文件,路径和名称需要明确指定。

    • module.json5:模块的配置文件,包含了当前模块的基本配置信息,包括模块名称、版本、依赖关系等,HAP 包的配置信息,描述了应用如何在目标设备上运行,以及不同设备之间的适配,应用 / 服务的全局配置信息。

  • build-profile.json5:该文件存储模块级的编译配置项,包括编译选项、目标设备或平台等。

  • oh_modules 目录:用于存放应用的第三方库依赖,类似于 Node.js 中的 node_modules。

5.3 资源管理基础

在鸿蒙应用开发中,资源管理是一个重要的环节,它可以帮助我们更好地组织和管理应用中的各种资源,如颜色、字符串、尺寸、媒体资源等。

  • 资源分类

    • 颜色资源 :用于定义应用中的各种颜色,通常存储在resources/base/element/color.json文件中。例如:
json 复制代码
{
  "primary_color": "#007DFF",
  "secondary_color": "#F5F5F5"
}

在代码中引用颜色资源:

typescript 复制代码
Text("Hello")
.fontColor($r('app.color.primary_color'))
  • 字符串资源 :用于存储应用中的文本内容,方便进行国际化和本地化。字符串资源存储在resources/base/element/string.json文件中。例如:
json 复制代码
{
  "app_name": "My Todo App",
  "welcome_message": "Welcome to your todo list"
}

在代码中引用字符串资源:

typescript 复制代码
Text($r('app.string.app_name'))
  • 尺寸资源 :用于定义应用中的各种尺寸,如字体大小、边距、间距等,存储在resources/base/element/float.json文件中。例如:
json 复制代码
{
  "font_size_large": 20,
  "margin_small": 5
}

在代码中引用尺寸资源:

typescript 复制代码
Text("Hello")
.fontSize($r('app.float.font_size_large'))
.margin({ top: $r('app.float.margin_small') })
  • 媒体资源 :包括图片、音频、视频等文件,通常存储在resources/base/media目录下。例如,将一张图片icon.png放在该目录下,在代码中引用图片资源:
typescript 复制代码
Image($r('app.media.icon'))
.width(50)
.height(50)
  • 资源目录结构 :应用的资源文件统一存放于resources目录下,包括base目录与限定词目录。

    • base 目录:默认存在,当没有匹配的限定词目录时自动引用,其中的资源文件会被编译成二进制文件,并赋予资源文件 ID,通过指定资源类型(type)和资源名称(name)引用。

    • 限定词目录 :可以由一个或多个表征应用场景或设备特征的限定词组合而成,包括移动国家码和移动网络码、语言、文字、国家或地区、横竖屏、设备类型、颜色模式和屏幕密度等维度,限定词之间通过下划线(_)或者中划线(-)连接。例如,en_US表示美式英语的限定词目录,zh_CN-car-ldpi表示适用于中国地区、简体中文、车机设备且屏幕密度为 ldpi 的限定词目录 。限定词目录中的资源文件同样会被编译成二进制文件,并赋予资源文件 ID,通过指定资源类型(type)和资源名称(name)来引用。

    • rawfile 目录 :支持创建多层子目录,子目录名称可以自定义,文件夹内可以自由放置各类资源文件。目录中的资源文件会被直接打包进应用,不经过编译,也不会被赋予资源文件 ID,通过指定文件路径和文件名引用。例如,访问rawfile目录下的fonts/MyFont.ttf文件:

typescript 复制代码
let fontPath = $rawfile('fonts/MyFont.ttf');

5.4 应用开发步骤与代码实现

下面以构建一个简单的待办事项应用为例,讲解鸿蒙应用的开发步骤与代码实现:

  1. 创建项目:打开 DevEco Studio,点击 "Create Project",选择 "Empty Ability" 模板,点击 "Next"。在项目配置界面,填写项目名称(如 TodoApp)、包名(如 com.example.todo)、保存路径,选择编译 SDK 版本和设备类型(如 Phone),语言选择 ArkTS,然后点击 "Finish",等待项目创建完成。

  2. 设计界面

    • 首页(Index.ets):展示待办事项列表,包含页面标题、添加按钮和待办事项列表。当没有待办事项时,显示空状态提示。
typescript 复制代码
@Entry
@Component
struct TodoListPage {
  @State todos: TodoItem[] = [];
  @State isLoading: boolean = true;

  build() {
    Column() {
      // 页面标题栏
      Row() {
        Text("我的待办事项")
       .fontSize(22)
       .fontWeight(FontWeight.Bold);
        // 添加按钮
        Button() {
          Image($r("app.media.ic_add"))
         .width(24)
         .height(24);
        }
       .width(40)
       .height(40)
       .backgroundColor("#007AFF")
       .borderRadius(20)
       .onClick(() => {
          // 跳转到添加页面
          router.pushUrl({ url: "pages/AddTodo" });
        });
      }
     .width("100%")
     .padding(15)
     .justifyContent(FlexAlign.SpaceBetween);
      // 待办事项列表
      if (this.isLoading) {
        // 加载状态
        Progress({ type: ProgressType.Circular })
       .width(40)
       .height(40)
       .margin(20);
      } else if (this.todos.length === 0) {
        // 空状态
        Column() {
          Image($r("app.media.ic_empty"))
         .width(120)
         .height(120);
          Text("暂无待办事项")
         .fontSize(16)
         .color("#999")
         .margin(10);
        }
       .flexGrow(1)
       .justifyContent(FlexAlign.Center);
      } else {
        // 待办列表
        List({ space: 8 }) {
          ForEach(this.todos, (item) => {
            ListItem() {
              TodoItemComponent({
                todo: item,
                onStatusChange: (isCompleted) => {
                  // 状态变化回调
                  this.updateTodoStatus(item.id, isCompleted);
                },
                onDelete: () => {
                  // 删除回调
                  this.deleteTodo(item.id);
                }
              });
            };
          }, item => item.id);
        }
       .padding(10)
       .flexGrow(1);
      }
    }
   .width("100%")
   .height("100%")
   .backgroundColor("#F5F5F5");
  }

  // 页面加载时获取数据
  aboutToAppear() {
    this.loadTodos();
  }

  // 从本地存储加载待办事项
  async loadTodos() {
    // 模拟加载延迟
    setTimeout(async () => {
      this.isLoading = false;
      // 实际应用中从本地存储读取数据
      // this.todos = await StorageService.getTodos();
    }, 1000);
  }

  // 更新待办事项状态
  async updateTodoStatus(id: string, isCompleted: boolean) {
    // 实际应用中更新本地存储
    // await StorageService.updateTodoStatus(id, isCompleted);
    this.todos = this.todos.map(todo => {
      if (todo.id === id) {
        todo.completed = isCompleted;
      }
      return todo;
    });
  }

  // 删除待办事项
  async deleteTodo(id: string) {
    // 实际应用中从本地存储删除数据
    // await StorageService.deleteTodo(id);
    this.todos = this.todos.filter(todo => todo.id!== id);
  }
}
  • 添加页(AddTodo.ets):提供一个表单,用于添加新的待办事项,包含标题输入框、内容输入框和提交按钮。
typescript 复制代码
@Entry
@Component
struct AddTodoPage {
  @State title: string = "";
  @State content: string = "";

  build() {
    Column() {
      TextInput({
        placeholder: "请输入标题",
        text: this.title
      })
     .width("100%")
     .height(40)
     .margin({ top: 20 });
      TextInput({
        placeholder: "请输入内容",
        text: this.content,
        type: InputType.Multiline
      })
     .width("100%")
     .height(150)
     .margin({ top: 20 });
      Button("提交")
     .width("100%")
     .height(40)
     .backgroundColor("#007AFF")
     .fontColor(Color.White)
     .margin({ top: 20 })
     .onClick(() => {
        if (this.title && this.content) {
          let newTodo: TodoItem = {
            id: Date.now().toString(),
            title: this.title,
            content: this.content,
            completed: false,
            createTime: Date.now()
          };
          // 实际应用中保存到本地存储
          // await StorageService.addTodo(newTodo);
          // 刷新首页数据
          router.back();
        }
      });
    }
   .width("100%")
   .height("100%")
   .padding(20);
  }
}
  1. 编写逻辑

    • 数据模型(TodoModel.ets):定义待办事项的数据结构。
typescript 复制代码
export interface TodoItem {
  id: string;
  title: string;
  content: string;
  completed: boolean;
  createTime: number;
}
  • 存储服务(StorageService.ets) :负责与本地存储进行交互,实现待办事项的增、删、改、查功能(这里仅为示例,实际实现需要使用@ohos.data.preferences等相关模块)。
typescript 复制代码
import preferences from '@ohos.data.preferences';

export class StorageService {
  private static instance: StorageService;
  private pref: preferences.Preferences | null = null;
  private readonly STORAGE_KEY = 'todo_items';
  private readonly PREF_NAME = 'todo_storage';

  private constructor() {
    this.init();
  }

  static getInstance(): StorageService {
    if (!StorageService.instance) {
      StorageService.instance = new StorageService();
    }
    return StorageService.instance;
  }

  async init() {
    try {
      this.pref = await preferences.getPreferences(this.PREF_NAME);
    } catch (error) {
      console.error('Failed to init storage:', error);
    }
  }

  async addTodo(todo: TodoItem) {
    if (!this.pref) return;
    let todos = await this.getTodos();
    todos.push(todo);
    await this.pref.putJson(this.STORAGE_KEY, todos);
    await this.pref.flush();
  }

  async getTodos(): Promise<TodoItem[]> {
    if (!this.pref) return [];
    let todos = await this.pref.getJson(this.STORAGE_KEY, []);
    return todos.map((todo: any) => ({
     ...todo,
      createTime: typeof todo.create
相关推荐
伯明翰java9 小时前
Redis学习笔记-List列表(2)
redis·笔记·学习
云帆小二10 小时前
从开发语言出发如何选择学习考试系统
开发语言·学习
Elias不吃糖10 小时前
总结我的小项目里现在用到的Redis
c++·redis·学习
BullSmall10 小时前
《道德经》第六十三章
学习
AA陈超11 小时前
使用UnrealEngine引擎,实现鼠标点击移动
c++·笔记·学习·ue5·虚幻引擎
BullSmall11 小时前
《道德经》第六十二章
学习
爱笑的眼睛1111 小时前
ArkTS接口与泛型在HarmonyOS应用开发中的深度应用
华为·harmonyos
Knox_Lai11 小时前
数据结构与算法学习(0)-常见数据结构和算法
c语言·数据结构·学习·算法
IMPYLH12 小时前
Lua 的 assert 函数
开发语言·笔记·junit·单元测试·lua
离离茶12 小时前
【笔记1-8】Qt bug记录:QListWidget窗口的浏览模式切换为ListMode后,滚轮滚动速度变慢
笔记·qt·bug