从零开始的鸿蒙开发
是由华为推出的操作系统,面向手机、平板、智能穿戴、智慧屏等多种设备。它的特点是:支持多设备形态统一开发框架声明式 UI 编程兼容主流应用开发习惯只需要会基本的编程操作,一步一步完成。便可在最短时间内完成一个可运行的鸿蒙应用。
鸿蒙(HarmonyOS)是由华为推出的操作系统,面向手机、平板、智能穿戴、智慧屏等多种设备。它的特点是:
支持多设备形态
统一开发框架
声明式 UI 编程
兼容主流应用开发习惯

只需要会基本的编程操作,一步一步完成环境安装、项目创建与运行。便可在最短时间内完成一个可运行的鸿蒙应用。
一、环境准备
鸿蒙应用开发使用官方 IDE:DevEco Studio。它类似于 Android Studio 或 Xcode。
打开官网:
https://developer.huawei.com/consumer/cn/deveco-studio/
进入下载页面,选择最新版 DevEco Studio。

下载完成后直接安装。

打开DevEco,新建项目,可以选择 APP 还是元服务,元服务不需要下载安装,点击即用

选择 application,默认配置,点击finish创建项目

项目创建完成后,会看到下面的目录:
entry/
├── src/main/
│ ├── ets/
│ │ ├── entryability/
│ │ └── pages/
│ │ └── Index.ets
entry是主模块根目录;entryability是应用入口 Ability 的代码;pages/Index.ets为默认首页文件
右上角选择设备为previewer预览器,并点击运行

可以看到在手机上的运行效果

点击device-设备管理器,我们可以下载多种鸿蒙设备的模拟器,下载一个手机镜像


下载完成后,以mate80为例,新建设备,点击绿色三角运行


弹出模拟器运行窗口,手机旁菜单栏中可进行摇一摇、截屏等一系列手机操作

在右上角设备中选择新创建的mate80,运行。在模拟器窗口中看到运行效果

二、Arkts 基础
ArkTS在 TS 的基础上演化而来,而TypeScript (TS)源于JavaScript (JS)。ArkTS的特点是引入了声明式 UI 描述和状态管理机制;
2.1 变量与数据类型
在 ArkTS 中对变量的要求比较严格,必须明确告诉程序变量是什么类型,禁止使用 any 类型
常见的变量类型有string (字符串),必须用引号包起来;number (数字):装整数或小数;boolean (布尔值);Array (数组)等
定义变量时需要明确数据类型,同时使用let或const等关键字定义变量的属性
let title: string = '数据类型'; // let 定义可以修改的变量
const maxCount: number = 10; // const 定义不能修改的常量
let isDone: boolean = false;
2.2 interface
interface定义了一个对象(Object)应该长什么样
例如描述一个待办任务可能要写三个独立的变量:
let todoId: number = 1;
let todoTitle: string = '去买菜';
let todoIsDone: boolean = false;
interface中把这三个属性打包,定义一个名为 Todo 的接口
interface Todo {
id: number; // 每一个 Todo 必须有一个数字类型的 id
title: string; // 每一个 Todo 必须有一个字符串类型的标题
done: boolean; // 每一个 Todo 必须有一个布尔类型的完成状态
}
定义好接口后,我们就可以根据它来创建真正的对象了。
// 声明一个变量 newTask,它的类型必须符合 Todo 接口
let newTask: Todo = {
id: 1,
title: '安装 DevEco Studio',
done: true
};
Arkts中,接口也是非常严格的,如果在 newTask 里漏掉了某个属性,或添加了不存在的属性,编译器都会报错,且数据类型严格一致
若要添加可选属性,用 ? 标识
memo?: string; // 备注是“可选”的,不填也不会报错。
只读属性用 readonly 标识,一旦确定就不允许再修改
readonly id: number;
2.3 函数与方法
在 ArkTS 中,函数是执行特定任务的代码块。方法是定义在类(Class)或结构体(Struct)内部的函数。在鸿蒙开发中,写在组件里的逻辑代码(toggleDone)通常被称为方法。
// 这是一个计算两个数字之和的函数
function add(a: number, b: number): number {
let sum = a + b;
return sum; // 返回计算结果
}
与很多语言相同,函数包含输入输出与函数体。括号中的(a: number, b: number)是函数的输入参数,必须标注类型;: number是函数的返回类型,如果函数不返回任何东西,类型写 : void;被花括号 { }围着的是函数体
ArkTS 的 UI 中有一种长得很像“箭头”的简写方式,是箭头函数。它非常适合作为按钮点击等事件的回调。
// (参数) => { 逻辑 }
.onClick(() => {
console.log('点击了按钮');
})
2.4 声明式 UI 布局
在原生 Android或老iOS里,代码的是界面怎么一步步变化,状态变了,需要自己找到某个按钮,更改它的属性/结构。而在声明式UI 中,只需要描述最终状态,系统会自动把对应的组件挪到正确的位置。
每一个页面或组件都遵循固定的模板
@Entry // 页面的入口,从这里开始构建 UI
@Component // 声明这是一个 UI 组件
struct Index { // struct:结构体,定义组件
// 数据/状态,比如 @State count: number = 0;
build() { // UI 描述
// 所有的 UI 代码必须写在这里
}
}
@Component表示下面的 struct 不是普通的数据结构,而是一个可参与 UI 构建、可被渲染、可被重组的组件
组件里通常分两块:
状态/数据区用来声明数据来源和数据流关系。如@State、@Prop、@Link、@ObjectLink 之类的装饰器字段。@State是组件内部自有状态。更改它会触发该组件及相关子树的重新构建,从而刷新界面。@Prop是父组件传进来的只读参数;@Link和父组件做双向绑定……负责更新保存数据
build() 区域是UI 描述区,写法是嵌套组件树,每当状态变化时,build() 会被重新执行,把变化反映到屏幕上。
要把组件整齐地摆放,需要容器。最常用的是Column和Row
Column让里面的内容从上到下垂直排列。Row从左到右水平排列
Column({ space: 20 }) { // 容器内组件间距为 20vp
Text('我的待办清单')
.fontSize(24)
Row() {
Text('任务 1:学习 ArkTS')
Blank() // 空白组件,把后面的组件推到最右边
Toggle({ type: ToggleType.Checkbox }) // 一个开关
}
.width('100%')
}

改变组件的外观只需要在组件后面像接龙一样,点(.)出需要的属性。被称为链式调用。如:
Text('我的待办清单')
.fontSize(24) // 字号
.fontColor(Color.Black) // 颜色
.fontWeight(700) // 加粗
.backgroundColor('#007DFF') // 背景色(蓝色)
.padding(10) // 内边距
.borderRadius(8) // 圆角

完成布局后,需要实现对用户操作的响应,在组件后面链式调用事件方法即可,通常配合箭头函数。如在页面中加一个按钮
Button('点我')
.onClick(() => {
console.info('用户点了我一下');
})

若需要自定义的响应函数,通常在组件中编写方法,build中使用this调用组件内的方法,如
@Component
struct TodoList {
// 定义一个删除方法,参数是任务的 ID
private removeTodo(id: number): void {
console.log('正在删除编号为' + id + '的任务');
// 删除逻辑
}
build() {
Button('删除任务')
.onClick(() => {
this.removeTodo(101); // 通过 this 调用组件内的方法
})
}
}
三、ToDo Lite 待办应用
接下来我们打造一个最简化功能的ToDo Lite 应用。
新建项目,打开entry/src/main/ets/pages/index.ets文件
首先定义 Todo这个接口,包含三个属性
interface Todo {
id: number; // id
title: string; // 待办的内容
done: boolean; // 是否完成
}
第二步写出基础布局
在 struct Index 中,给列表塞两个假数据,并在build中画出ui
在把 this.todos 这个数组,一条一条地渲染成列表项时,使用循环函数
ForEach(this.todos, (item: Todo) => { ... }, key)
ForEach 对数组 this.todos 里的每一个元素执行一遍回调函数,然后生成对应的 UI。
等价于伪代码:
for (let item of this.todos) {
生成一个 ListItem
}
(item: Todo) => item.id.toString()是key参数,UI 框架需要知道,当数组发生变化时,哪一项是“同一个元素”,哪一项是新加的。item.id 是每个 Todo 的唯一身份。
List函数的作用是把 todos 数组里的每个 Todo 渲染成一条带标题和删除按钮的列表项,并用 id 作为唯一标识
List() {
ForEach(this.todos, (item: Todo) => {
ListItem() {
Row() {
Text(item.title).fontSize(18).layoutWeight(1)
Button('删除').type(ButtonType.Capsule)
}.width('100%').padding(16)
}
}, (item: Todo) => item.id.toString()) // 唯一标识符
}
完整代码为
interface Todo {
id: number; // id
title: string; // 待办的内容
done: boolean; // 是否完成
}
@Entry
@Component
struct Index {
@State todos: Todo[] = [
{ id: 1, title: '安装 DevEco Studio', done: true },
{ id: 2, title: '新建一个项目', done: false }
];
build() {
Column({ space: 12 }) {
// 标题行
Row() {
Text('ToDo Lite').fontSize(24).fontWeight(FontWeight.Bold)
Blank()
Button('新增').type(ButtonType.Capsule)
}.width('100%').padding(16)
// 列表
List() {
ForEach(this.todos, (item: Todo) => {
ListItem() {
Row() {
Text(item.title).fontSize(18).layoutWeight(1)
Button('删除').type(ButtonType.Capsule)
}.width('100%').padding(16)
}
}, (item: Todo) => item.id.toString()+ item.done) // 唯一标识符
}
}
}
}

这样只实现了界面,点哪里都没反应。我们要加入切换状态和删除的逻辑。
toggleDone 切换完成状态。生成一个新数组;找到那条 id 对应的 todo,复制一份并把 done 取反;最后整体替换 this.todos。
private toggleDone(id: number) {
this.todos = this.todos.map(t => {
if (t.id === id) {
return { id: t.id, title: t.title, done: !t.done } as Todo;
}
return t;
});
}
removeTodo删除任务
private removeTodo(id: number) {
// 只保留那些 ID 不匹配的任务
this.todos = this.todos.filter(t => t.id !== id);
}
两个方法定义完成后,将它们绑定到 UI 上。给任务行添加
.onClick(() => this.toggleDone(item.id))
给删除按钮添加
.onClick(() => this.removeTodo(item.id))
同时需要给切换状态加上待办完成效果
Text(item.title)
.fontSize(18)
.decoration({
type: item.done ? TextDecorationType.LineThrough : TextDecorationType.None
})
.opacity(item.done ? 0.5 : 1.0)
.layoutWeight(1)
.decoration是一种三元表达式。系统会检查 item.done 是 true 还是 false。如果是 true,就应用 LineThrough(删除线),同时将已完成字体变淡一点
.opacity(item.done ? 0.5 : 1.0)

此时,待办完成功能可以正常使用,接下来完成新建待办的功能
用户点击新建时,要去往“新增页面”,在 pages 文件夹下新建 AddTodo.ets
首先完成ui部分
@Entry
@Component
struct AddTodo {
@State title: string = ''; // 输入的文字
build() {
Column({ space: 16 }) {
Text('新增待办').fontSize(22).fontWeight(FontWeight.Bold)
TextInput({ placeholder: '请输入待办事项...' })
.onChange((value: string) => { this.title = value; }) // 实时同步文字
Button('保存').onClick(() => {
// 这里需要把数据传回去
})
}.padding(16)
}
}
TextInput是一个输入框,placeholder 是占位提示:没输入时灰色显示“请输入待办事项...”
onChange(...)只要输入框里的内容变化就触发,将输入内容同步给title
在新页面中得到的数据需要同步在主页面中,AppStorage是一个全app可见的储藏空间
在 AddTodo.ets 中存入:
private save() {
const v = this.title.trim();
if (v.length === 0) return;
// 将新待办的标题存入全局存储
AppStorage.setOrCreate('newTodoTitle', v);
// 关闭当前 AddTodo 页面,回到上一个页面
router.back();
}
在 Index.ets 中取出:
利用 onPageShow ,每次页面一显示就去检查储藏空间。
onPageShow() {
const newTitle: string | undefined = AppStorage.get('newTodoTitle');
// 如果有新标题且不为空,则添加
if (newTitle && newTitle.trim().length > 0) {
const newItem: Todo = {
id: Date.now(),
title: newTitle.trim(),
done: false
};
// 更新数组
this.todos = [newItem, ...this.todos];
// 读取完后立即清理全局存储,防止逻辑重复执行
AppStorage.setOrCreate('newTodoTitle', '');
}
}
给新建按钮添加回调函数,转到新页面
.onClick(() => { router.pushUrl({ url: 'pages/AddTodo' }) })
需要在文件顶部写
import router from '@ohos.router'
给新建页面的按钮添加回调函数
Row({ space: 12 }) {
Button('取消').onClick(() => router.back())
Button('保存').onClick(() => this.save())
}
路由能跳转的页面必须在配置里登记,最后将AddTodo 页面加入 pages 配置
entry/src/main/resources/base/profile/main_pages.json
加入AddTodo
{
"src": [
"pages/Index",
"pages/AddTodo"
]
}
全部完成之后点击运行
点击新建,输入新待办并保存,返回主页面,待办出现在列表最上方,功能正常


Index.ets完整内容
import router from '@ohos.router'
interface Todo {
id: number; // id
title: string; // 待办的内容
done: boolean; // 是否完成
}
@Entry
@Component
struct Index {
@State todos: Todo[] = [
{ id: 1, title: '安装 DevEco Studio', done: true },
{ id: 2, title: '新建一个项目', done: false }
];
onPageShow() {
// 从全局存储读取传回来的标题
const newTitle: string | undefined = AppStorage.get('newTodoTitle');
// 如果有新标题且不为空,则添加
if (newTitle && newTitle.trim().length > 0) {
const newItem: Todo = {
id: Date.now(),
title: newTitle.trim(),
done: false
};
// 更新数组
this.todos = [newItem, ...this.todos];
// 读取完后立即清理全局存储,防止逻辑重复执行
AppStorage.setOrCreate('newTodoTitle', '');
}
}
// 切换状态
private toggleDone(id: number) {
this.todos = this.todos.map(t => {
if (t.id === id) {
// 返回一个“新”对象,触发 UI 刷新
return { id: t.id, title: t.title, done: !t.done } as Todo;
}
return t;
});
}
// 删除任务
private removeTodo(id: number) {
this.todos = this.todos.filter(t => t.id !== id);
}
build() {
Column({ space: 12 }) {
// 标题行
Row() {
Text('ToDo Lite').fontSize(24).fontWeight(FontWeight.Bold)
Blank()
Button('新增')
.type(ButtonType.Capsule)
.onClick(() => {
router.pushUrl({ url: 'pages/AddTodo' })
})
}.width('100%')
.padding({ left: 16, right: 16, top: 16 })
// 列表
List() {
ForEach(this.todos, (item: Todo) => {
ListItem() {
Row() {
Text(item.title)
.fontSize(18)
.decoration({
type: item.done ? TextDecorationType.LineThrough : TextDecorationType.None
})
.opacity(item.done ? 0.5 : 1.0)
.layoutWeight(1)
Button('删除').type(ButtonType.Capsule).onClick(() => this.removeTodo(item.id))
}.width('100%').padding(16).onClick(() => this.toggleDone(item.id))
}
}, (item: Todo) => item.id.toString()+ item.done) // 唯一标识符
}
}
}
}
AddTodo.ets 完整内容
import router from '@ohos.router'
@Entry
@Component
struct AddTodo {
@State title: string = ''; // 输入的文字
private save() {
if (this.title.trim().length === 0) return;
// 存入"newTodoTitle"
AppStorage.setOrCreate('newTodoTitle', this.title.trim());
router.back(); // 回到上一页
}
build() {
Column({ space: 16 }) {
Text('新增待办').fontSize(22).fontWeight(FontWeight.Bold)
TextInput({ placeholder: '请输入待办事项...' })
.onChange((value: string) => { this.title = value; }) // 实时同步文字
Row({ space: 12 }) {
Button('取消').onClick(() => router.back())
Button('保存').onClick(() => this.save())
}
}.padding(16)
}
}
从环境搭建开始,到创建工程、编写页面、实现状态管理、完成页面跳转,最终实现了一个最简单的可用的 ToDo 应用。当然,上架应用商店的标准比这高得多,我们需要更有创新、更加丰富的应用
更多推荐




所有评论(0)