# HarmonyOS NEXT应用开发培训 **Repository Path**: maker--iot/maker-harmonyos-next ## Basic Information - **Project Name**: HarmonyOS NEXT应用开发培训 - **Description**: 【华为开发者布道师技术沙龙】北京信息科技大学&梅科尔工作室HarmonyOS NEXT应用开发培训(零基础教学) - **Primary Language**: Unknown - **License**: MulanPSL-2.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2024-07-29 - **Last Updated**: 2024-12-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # **介绍** 本次培训旨在培养高校学子对HarmonyOS操作系统的深刻理解与浓厚兴趣,通过系统化的学习与实践,使学员不仅能够掌握HarmonyOS应用开发的基本技能,更能激发其创新潜能,为未来投身于鸿蒙生态建设打下坚实基础。培训结束后,学员将具备独立设计、开发并部署HarmonyOS应用程序的能力,为个人职业生涯增添亮点,同时为高校与产业界的合作搭建桥梁,共同推动HarmonyOS生态的繁荣与发展。 ![](./img/1.png) ![](./img/2.png) # **相关视频与资料链接:** [BiliBili视频课程链接](https://www.bilibili.com/video/BV17w4m1k72C/?vd_source=6383f2b17f39b4d8aebc88e6e7708948) [课程相关培训资料链接](https://pan.baidu.com/s/1KHlwVHRqKHXS9heITb82cA?pwd=xyum) # **0. 开发环境:** ``` DevEcoStudio版本:DevEco Studio NEXT Developer Beta1 SDK版本:5.0.3.403 API版本:12 ``` # **1. HarmonyOS基础介绍**   鸿蒙系统(HarmonyOS)是华为公司自主研发的面向全场景的智能终端操作系统,旨在为不同设备的智能化、互联与协同提供统一的语言,为用户带来简捷、流畅、连接、安全可靠的全场景交互体验。它通过分布式架构和智能化技术,实现了不同设备之间的无缝连接和高效协同。提供统一的操作体验,覆盖智能手机、平板电脑、智能穿戴设备、智能家居、车载系统等多种设备和应用场景。在今年年初HarmonyOSNEXT鸿蒙星河版正式上限,并在6月21日发布开发版api12 beta版本。本篇文章主要介绍使用API12进行HarmonyOS NEXT应用开发。 ## **1.1 HarmonyOS开发工具**   HarmonyOS应用开发为开发者提供了全链路开发工具,为广大开发者进行开发赋能。整套工具链主要包含:HarmonyOS工具、HarmonyOS砖块、HarmonyOS底座三大部分。在HarmonyOS工具主要提供7大开发者套件,HarmonyOS砖块则提供包含地图、AR、搜索、广告、账号等30000+API,HarmonyOS底座则由8大领先技术构建。 ![](./img/3.png) 而我们本次主要讲解开发者套件的使用,从上到下可以划分为三个阶段:准备与设计、代码开发和准备与设计。 ![](./img/4.png) ## 阶段一:准备与设计 ### **Harmonyos Design** **Harmonyos Design**面向全场景智能体验的设计系统。全栈式设计规范:包括设计理念、人因研究、应用架构、人机交互、视觉风格、隐私设计、动效、音效、振动、多态控件、界面用语、全球化、无障碍等。提供了丰富的设计资源:HarmonyOS 字体、HarmonyOS Symbol 和 HarmonyOS 音效库。 相关资源链接: - [HarmonyOS 主题图标库 | icon素材免费下载](https://developer.huawei.com/consumer/cn/design/harmonyos-symbol/) - [HarmonyOS音效 | HarmonyOS设计](https://developer.huawei.com/consumer/cn/design/harmonyos-sound/) - [设计资源-HarmonyOS设计](https://developer.huawei.com/consumer/cn/design/resource/) ### **ArkCompiler** **ArkCompiler**即方舟编译器。支持多种编程语言、多种芯片平台联合编译、运行而设计的统一编译运行时平台。支持包括动态类型和静态类型语言在内的多种编程语言,如 ArkTS、TS、JS。能将我们开发的代码,转换成字节码,使设备快速读取,提高运行效率 ![](./img/5.png) ### **ArkTS&ArkUI** **ArkTS**和**ArkUI**是我们开发harmony应用的核心语言。设备底层开发于鸿蒙最开始的版本都是用的C或C++开发。而随着鸿蒙API的不断迭代,和代码的不断优化,鸿蒙开发语言也不断适配更新。从最开始的C/C++,到Jave、JS,再到现在的ArkTS。使开发更加简易快捷。 ![](./img/6.png) ArkTS在TypeScript( 简称TS)的基础上,匹配ArkUI框架扩展了声明式UI、状态管理等相应的能力,让开发者以更简洁、更自然的方式开发跨端应用。从Harmony OS NEXT Developer Preview 0版本开始,ArkTS在TS的基础上,进一步通过规范强化静态检查和分析。 - 许多错误在编译时可以被检测出来,不用等到运行时,这大大降低了代码运行错误的风险,有利于程序的健壮性; - 减少运行时的类型检查,从而降低了运行时负载,有助于提升执行性能。 ![](./img/7.png) ## 阶段二:开发和测试 ### **HUAWEI DevEco Studio** DevEco Studio是华为开发者联盟提供的HarmonyOS应用及元服务开发者的集成开发环境,是基于IntelliJ IDEA Community开源版本打造。提供一站式的分布式应用开发平台,支持分布式多端开发、分布式多端调测、多端模拟仿真,全方位的质量与安全保障。下方会提供下载与安装步骤。 ![](./img/8.png) 系统要求 ``` Windows: 操作系统:Windows 10/11 64 位 内存:16GB及以上 硬盘:100GB 及以上 分辨率:1280*800 像素及以上 macOS 操作系统:macOS(X86) 11/12/13/14 macOS(ARM) 12/13/14 内存:8GB及以上 硬盘:100GB 及以上 分辨率:1280*800 像素及以上 ``` ### **HUAWEI DevEco Testing** HUAWEI DevEco Testing提供一站式的应用测试服务平台。为开发者提供全新自动化测试框架,及稳定性、性能、应用基础质量等专项测试服务,覆盖应用测试全周期,助力打造高品质应用。自动化测试框架DevEco Testing Hypium,持续提升测试脚本开发效率及应用测试效率。 ![](./img/9.png) 系统要求 ``` Windows: 操作系统:Windows 10/11 64 位 内存:推荐使用16GB及以上,最小8GB 硬盘:100GB 及以上 分辨率:1600*900 像素及以上 macOS 操作系统:macOS(X86) /12/13/14 macOS(ARM) 12/13/14 内存:推荐使用16GB及以上,最小8GB 硬盘:100GB 及以上 分辨率:1600*900 像素及以上 ``` ## 阶段三:上架与运营 ### **AppGallery Connect** **AppGallery Connect**是华为应用市场推出的应用一站式服务平台,致力于为开发者提供应用创意、开发、分发、运营、分析全生命周期服务,构建全场景智慧化的应用生态。提高版本质量,开放分发和运营服务,帮助您获得用户并实现收入的规模增长。帮助开发者进行应用后续运营上架。 ![](./img/10.png) ## **1.2 DevEco Studio 安装** ## (1)下载安装包 ### 1)安装包下载地址 ### 2)根据自己的操作系统下载Betal版本 ![](./img/11.png) ## (2)安装 ### 1)解压文件,双击exe文件,进入安装界面 ### 2)默认安装于C:\Program Files路径下,也可以单击"浏览..."指定其他安装路径,然后单击下一步 ![](./img/12.png) ### 3)点击Next,进入安装选项页面,第一个一定要选上,后面三个最好也选上 ![](./img/13.png) ### 4)点击Next,进入安装页,直接点击安装 ![](./img/14.png) ### 5)安装完成后选择重启电脑,点完成 ![](./img/15.png) ### 6)重启完成后,会在桌面看到DevEco Studio的图标 ## (3)配置开发环境 ### 1)选择Agree,进入配置导入页面,选择不导入任何配置 ![](./img/16.png) ### 2)进入首次运行的欢迎页面,点Agree ![](./img/17.png) ### 3)出现这个界面,则DevEco Studio安装完成 ![](./img/18.png) ### 4)点击Create Project,选择空文件创建工程 ![](./img/19.png) ## (4)补充 ### 1)完成上述三步后就下载完成了,下载完成后可以点击Djagnose 进行环境诊断,防止环境配置错误 ![](./img/20.png) ### 2)官方文档中也有更加详细的环境安装报错解决示例说明 # **2. HarmonyOS 语法与组件** ## **2.1 变量声明与数据类型** ### **声明** ArkTS通过声明引入变量、常量、函数和类型。 ### **变量声明** 变量名:类型=值 ``` hi: string = 'hello'; ``` 变量命名条件 1. 只能包含数字、字母、下划线以及$符号 2. 首个符号,不能以数字开头,只能允许字母或者下划线以及$符号开头 3. 同一个名字的变量在相同作用域中只能声明一次 ### **类型** #### Number类型 数字,二进制,八进制,十进制,十六进制 ``` count_1 : number = 24 count_2 : number = 3.14; count_3 : number = 1e10; ``` #### Boolean类型 boolean 类型由 true 和 false 两个逻辑值组成 ``` let isDone: boolean = false; let Done: boolean = true; ``` #### String类型 string 代表字符序列;可以使用转义字符来表示字符。 字符串字面量由单引号(')或双引号(")之间括起来的零个或多个字符组成。 字符串字面量还有一特殊形式,是用反向单引号(`)括起来的模板字面量。 ``` name_1 :string = '双引号,单引号都可以' name_2 :string = 'Hello, world!\n'; name_3 :string= 'this is a string'; name_4 :string = `The result is ${a}`; ``` #### Array类型 array,即数组,是由可赋值给数组声明中指定的元素类型的数据组成的对象。 数组可由数组复合字面量(即用方括号括起来的零个或多个表达式的列表,其中 每个表达式为数组中的一个元素)来赋值。数组的长度由数组中元素的个数来确定。数组中第一个元素的索引为 0。 以下示例将创建包含三个元素的数组: ``` names: string[] = ['Alice', 'Bob', 'Carol']; ``` #### 元组 元组通常是一种数据结构,用于将多个不同类型的值组合成一个单一对象 ```   person : [string,number,boolean] = ['新年快乐',2024,true] ``` ## **2.2 常用基础组件** ### **容器类—Column和Row** #### 布局容器概念 Column表示沿垂直方向布局的容器 Row表示沿水平方向布局的容器 #### 对齐方式 justifyContent:设置子元素在主轴方向的对齐格式 alignItems:设置子元素在交叉轴方向的对齐格式 #### 主轴和交叉轴的概念 主轴:在Column容器中的子组件是按照从上到下的垂直方向布局的,其主轴的方向是垂直方向;在Row容器中的组件是按照从左到右的水平方向布局的,其主轴的方向是水平方向。 交叉轴:与主轴垂直相交的轴线,如果主轴是垂直方向,则交叉轴就是水平方向;如果主轴是水平方向,则交叉轴是垂直方向。 **代码示例** ``` Column() { // Column容器组件,用于垂直排列子元素 //Colum容器组件 Column({space:20}){ // 带有间距的Column容器 Text($r('app.string.wenben1')) // 显示资源文件中的字符串 .fontSize(50) // 设置字体大小 //ff0000 .fontColor(Color.Red) // 设置字体颜色,十六进制、直接、调用 //400正常的,800最高 .fontWeight(800) // 设置字体粗细 Text("文本2").fontSize(20) // 简单的文本显示 Text("文本3").fontSize(20) Text("文本4").fontSize(20) } .height("50%") .justifyContent(FlexAlign.Center) // 设置子元素在主轴的居中对齐 //Row容器组件 Row(){ // Row容器组件,用于水平排列子元素 Text("文本1").fontSize(20) Text("文本2").fontSize(20) Text("文本3").fontSize(20) Text("文本4").fontSize(20) }.justifyContent(FlexAlign.Center) // 设置子元素在主轴的居中对齐 } ``` ### **展示类—Text** Text组件用于在界面上展示一段文本信息 ### 创建方式 **string字符串** ``` Text('我是一段文本') ``` **引用Resource资源** 资源引用类型可以通过$r创建Resource类型对象,文件位置为/resources/base/element/string.json。 ``` Text($r('app.string.module_desc')) .baselineOffset(0) .fontSize(30) .border({ width: 1 }) .padding(10) .width(300) ``` ### 文本样式 fontSize:设置文本尺寸,为number类型时,使用fp单位。 fontColor:设置文本颜色 fontWeight:设置文本的字体粗细,number类型取值[100, 900],取值间隔为100,默认为400,取值越大,字体越粗。 string类型仅支持number类型取值的字符串形式,例如“400”,以及“bold”、“bolder”、“lighter”、“regular”、“medium”,分别对应FontWeight中相应的枚举值。默认值:FontWeight.Normal。 ``` Text("文本1") // 显示资源文件中的字符串 .fontSize(50) // 设置字体大小 //ff0000 .fontColor(Color.Red) // 设置字体颜色,十六进制、直接、调用 //400正常的,800最高 .fontWeight(800) // 设置字体粗细 Text("文本2").fontSize(20) // 简单的文本显示 ``` **图片展示效果:** ![](./img/21.png) ### **展示类—Image** 调用方式:$r( )资源组件与访问(数据源调用)、本地文件调用、网络图片 资源分类与访问官方文档:[](https://developer.harmonyos.com/cn/docs/documentation/doc-guides/resource-categories-and-access-0000001435940589) **代码展示** ``` Image($r('app.media.icon')) // 显示图片资源 .width(80) // 设置图片宽度 .height(80) // 设置图片高度 .borderRadius(90) // 设置图片边框圆角 ``` **图片展示效果:** ![](./img/22.png) ### **输入类—TextInput** TextInput:单行文本输入框组件 placeholder:输入框无输入时的提示文本 text:输入框当前的文本内容 **代码展示** ``` Column({space:20}) { // 一个Column容器 TextInput({ text: '15937895215' }).fontSize(20) // 输入框,初始文本 .width("80%") // 设置宽度 .height(50) // 设置高度 TextInput({ placeholder: '密码' }).fontSize(20) // 带占位符的输入框(提示文本) .type(InputType.Password) // 设置输入类型为密码 .width("80%") .height(50) .backgroundColor(Color.Red) // 设置背景颜色 } ``` **图片展示效果:** ![](./img/23.png) ### **操作类—Button** **文字形按钮** ``` Button("登录") // 按钮 .width("20%") // 设置宽度 .fontSize(20) // 设置字体大小 .type(ButtonType.Circle) // 设置按钮类型为圆形 ``` **自定义按钮,在Button内嵌套其他组件** ``` Button(){ Text("登录") // 按钮 .fontSize(20) // 设置字体大小 }.type(ButtonType.Normal) // 设置按钮类型为正常类型 .width("20%").height(40) .borderRadius(10) //设置按钮圆角 .backgroundColor("#ff0000") //设置按钮背景元素 ``` **图片展示效果** ![](./img/24.png) ### 按钮类型 Capsule:胶囊型按钮(圆角默认为高度的一半),不支持通过borderRadius属性重新设置圆角。 ``` Button('Disable', { type: ButtonType.Capsule, stateEffect: false }) .backgroundColor(0x317aff) .width(90) .height(40) ``` ![](./img/25.png) Circle:圆形按钮,不支持通过borderRadius属性重新设置圆角。 ``` Button('Circle', { type: ButtonType.Circle, stateEffect: false }) .backgroundColor(0x317aff) .width(90) .height(90) ``` ![](./img/26.png) Normal:普通按钮(默认不带圆角),支持通过borderRadius属性重新设置圆角。 ``` Button('Ok', { type: ButtonType.Normal, stateEffect: true }) .borderRadius(8) .backgroundColor(0x317aff) .width(90) .height(40) ``` ![](./img/27.png) ## **2.3 登陆界面搭建实操** 通过对以上基础组件的学习,我们大致可以实现一个账号登录界面,展示效果如下: ![](./img/28.png) **代码如下** ``` import { router } from '@kit.ArkUI' @Entry @Component //@Entry 和 @Component 是ArkUI中的装饰器,用于定义组件的入口和组件本身。@Entry 表示这个组件是应用的入口组件,@Component 表示这是一个组件 struct Login { //定义了一个名为Login的组件结构体 build() { //是组件的构建方法,用于定义组件的UI结构 Column({space:50}) { //开始一个垂直布局(Column),space:50 表示子元素之间的间隔为50vp //任务列表App Text("任务列表APP").fontSize(40).fontWeight(FontWeight.Bold) //显示文本“任务列表APP”,字体大小为40vp,字体加粗。 //手机号密码输入框 Column({space:20}) { //开始另一个垂直布局,用于放置手机号和密码输入框等控件,子元素间隔为20vp TextInput({ placeholder: '手机号' })//创建一个文本输入框,提示文本为“手机号”,并设置了一系列样式属性,如字体大小、宽度、高度、类型、边框半径等。 .fontSize(20) .width("80%") .height(40) .type(InputType.Normal) .borderRadius(15) // .backgroundColor(Color.Pink) TextInput({ placeholder: '密码' }) //类似上一个输入框,但用于输入密码,并设置了密码类型 .fontSize(20) .width("80%") .height(40) .type(InputType.Normal) .borderRadius(15) .type(InputType.Password) // .backgroundColor(Color.Pink) //我已阅读并同意协议 Row({ space: 5 }) { //子元素间隔为5vp Checkbox().width(22) //添加一个复选框,并设置宽度为22vp //协议颜色不一样 Row() { //开始另一个水平布局,用于放置“我已阅读并同意”及其后的文本 Text("我已阅读并同意").fontSize(14) Text("服务协议").fontSize(14).fontColor(Color.Blue)//在Row内使用Text控件拼接出“我已阅读并同意”以及两个不同颜色的文本(服务协议和QQ保护指引),并通过fontColor设置特定文本的颜色。 Text("与").fontSize(14) Text("QQ保护指引").fontSize(14).fontColor(Color.Blue) } } //按钮 Button() { //开始定义一个按钮 Text("登录") .fontSize(20) .fontColor(Color.White) .fontWeight(FontWeight.Bold) .onClick(()=>{ }) }.width(100) //结束按钮的文本设置,并设置按钮的宽度和高度 .height(40) } } .backgroundImage($r('app.media.background1')) //设置整个Column的背景图片,图片资源通过$r函数引用 .backgroundImageSize({width:'100%',height:"100%"}) //设置图片的大小,宽高为100%,占满整页 .justifyContent(FlexAlign.Center) //设置Flex容器(Column)的主轴对齐方式为居中 .height('100%')//设置Column的高度和宽度为父容器的100%,即全屏显示 .width('100%') } } ``` # **3 HarmonyOS 装饰器与事件:** 本模块主要讲解ArkTS装饰器与基础事件,使其能实现动态页面跳转。 ## **3.1 触控事件** ArkUI触控事件,根据输入源不同,主要划分为touch类与mouse类。 touch类的输入源包含:Finger(手指)、Pen(笔) mouse类的输入源包含:Mouse(鼠标)、Touchpad(触控板)、Joystick(手柄) 上述触控事件想要被触发,需要在事件方法的回调中添加组件响应逻辑。 通过事件方法可以配置组件支持的事件, 事件方法紧随组件并用“.”运算符连接。使用Lambda表达式配置组件的事件方法: ~~~ (parameters) -> expression 或 (parameters) ->{ statements; } ~~~ 例如,为Button组件添加点击onClick方法,在onClick方法的回调中添加点击响应逻辑。 ~~~ Button("点击事件") .onClick(()=>{ this.age=this.age+1 }) ~~~ ## **3.2路由跳转** 页面路由:是指在应用程序中实现不同页面之间的跳转和数据传递。 Router有两种页面跳转模式: ①router.pushUrl( ):目标页不会替换当前页,而是压入页面栈中,因此可以用router,back( )返回当前页。 ②router.replaceUrl( ):目标页替换当前页,当前页会被销毁并释放资源,无法返回当前页。 此外Router有两种页面实例模式: ①Standard:标准实例模式,每次跳转都会新建一个目标页并压入栈顶。默认就是这种模式 ②Single:单实例模式,如果目标页已经在栈中,则离栈顶最近的同UrT页面会被移动到栈顶并重新加载。 注意:页面栈的最大容量上限为32个页面,使用router.clear( )方法可以清空页面栈,释放内存 ~~~ //导入ArkUI的路由模块 import { router } from '@kit.ArkUI' @Entry @Component struct Login { build() { Column(){ Button("点击事件") .onClick(()=>{ router.pushUrl({ url:"pages/Enroll" }) }) } } } ~~~ ## **3.3 HarmonyOS 装饰器** 装饰器:用来装饰类、结构体、方法以及变量,赋予其特殊的含义,如 @Entry 、@Component 、 等这个状态变化会引起 UI 变更。 ArkTS装饰器分为三大类:组件定义装饰器、动态构建装饰器、状态管理装饰器。 ### **组件定义装饰器** ### **@ Entry** @Entry装饰的自定义组件用作页面的默认入口组件,加载页面时,将首先创建并呈现 作为一个入口组件,先载入@Entry,再载入其他组件。只有被@Entry和@Preview修饰的组件才能用预览器进行预览展示。 而在真机模拟时,通过在src/main/ets/entryability/EntryAbility.ets文件中在onWindowStageCreate配置应用启动刷新界面,此路径所在文件必须为被 @ Entry所装饰。 ``` onWindowStageCreate(windowStage: window.WindowStage): void { hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); //配置下方路径 windowStage.loadContent('pages/Load', (err) => { if (err.code) { hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); return; } hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.'); }); } ``` ### **@Preview** 用@Preview装饰的自定义组件可以在DevEco Studio的预览器上进行预览,加载页面时将创建并呈@Preview装饰的自定义组件。与@Entry不同的是,被@Preview装饰预览的是当前自定义组件,而前者预览的则是整个界面。 使用场景:①当存在有多个结构体,查看指定的结构体②查看静态类型自定义组件 ~~~ //预览组件 test1 @Preview @Component struct test1 { build() { Column(){ Button("你好") } } } //预览组件 test2 @Preview @Component struct test2 { build() { Column(){ Button("你好") } } } //预览组件 test3 @Preview @Component struct test3 { build() { Column(){ Button("你好") } } } ~~~ ![](./img/29.png) ### **@Component** @Component装饰的struct表示该结构体具有组件化能力,能够成为一个独立的组件,即自定义组件,在build方法里描述UI结构。 其本身拥有四大特性: ①可组合:允许开发人员组合使用内置组件、其他组件、公共属性和方法 ②可重用:自定义组件可以被其他组件重用,并作为不同的实例在不同的父组件或容器中使用 ③生命周期:生命周期的回调方法可以在组件中配置,用于业务逻辑处理 ④数据驱动更新:由状态变量的数据动,实现UI自动更新 @Component创建的子组件可以被调用,一种为本文件内调用: ~~~~ //父组件 @Entry @Component struct Index { build() { Column({space:20}) { //调用子组件 button1() } .justifyContent(FlexAlign.Center) .height('100%') .width('100%') } } //子组件button1 @Component struct button1{ build(){ Column(){ /* 显示年龄的按钮,点击增加年龄 */ Button("按钮") .type(ButtonType.Normal) .borderRadius(10) } } } ~~~~ 另一种为跨文件调用,需要通过import导入 ~~~ //Import.ets import { Export } from './Export' @Entry @Component struct Export { build() { Column({space:20}) { //调用子组件 Import() } .justifyContent(FlexAlign.Center) .height('100%') .width('100%') } } //Export @Component export struct Export{ build(){ Column(){ /* 显示年龄的按钮,点击增加年龄 */ Button("按钮") .type(ButtonType.Normal) .borderRadius(10) } } } ~~~ ### **动态UI装饰器** ### **@ Builder** @Builder装饰的方法用于定义组件的声明式UI描述,在一个自定义组件内快速生成多个布局内容当封装完自定义组件,但是只想调用其中的某个组件,不想单起文件,用@Builder 由于buttoner定义在了build()过程外面,struct里面,所以调用要用this.buttoner() ~~~ @Entry @Component struct Index { //自定义按钮 @Builder buttoner(){ Button("自定义按钮") .fontSize(50) } build() { Column({space:20}) { //调用子组件 Button("按钮1") this.buttoner() } .justifyContent(FlexAlign.Center) .height('100%') .width('100%') } } ~~~ ![](./img/30.png) ### **@Styles** 将新的属性函数添加到基本组件上,如Text、Column、Button等。通过@stvles装饰器可以快速定义并复用组件的自定义样式。 ~~~ @Entry @Component struct Index { build() { Column({space:20}) { //调用子组件 Button("你好") .card() Button("你好") .card() Button("你好") .card() } } } // 自定义组件样式。 @Styles function card(){ .width("50") .height("50") .backgroundColor(Color.Red) } ~~~ ![](./img/31.png) ### **动态UI装饰器** ### **@State** @State装饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的build方法进行UI刷新。 如下方当点击按钮时,修改message,Text文本组件中绑定this.messageText文本会发生改变。 ~~~ @Entry @Component struct Index { @state message:string = "Hello World" build() { Column({space:20}) { Text(this.message) Button("点击改变") .onClick(()=>{ this.message="你好 世界" }) } } } ~~~ ### **@Prop** @Prop与@State有相同的语义,但初始化方式不同。@Prop装饰的变量必须使用其父组 件提供的@State变量进行初始化,允许组件内部修改@Prop变量,但更改不会通知给父 组件,即@Prop属于**单向数据绑定**。 @Prop状态数据具有以下特征: ①支持简单类型:仅支持number、string、boolean简单类型; ②私有:仅在组件内访问; ③支持多个实例:一个组件中可以定义多个标有@Prop的属性; ~~~ @Entry @Component export struct Index { @State agef:number=18 build() { Column({space:20}) { Text(this.agef.toString()).fontSize(50) /* 父亲按钮,点击增加年龄 */ Button("父亲按钮"+this.agef) .onClick(()=>{ this.agef=this.agef+1 }) /* 调用子组件button1,传递(单向)年龄属性 */ button1({age:this.agef}) } .justifyContent(FlexAlign.Center) .height('100%') .width('100%') } } /** * 子组件button1 * @Prop 单向绑定 */ @Component struct button1{ @Prop age:number build(){ Column(){ /* 显示年龄的按钮,点击增加年龄 */ Button("单向数据"+this.age) .type(ButtonType.Normal) .borderRadius(10) .onClick(()=>{ this.age=this.age+1 }) } } } ~~~ ### **@Link** 创建自定义组件时需要将变量的引用传递给@Link变量,在创建组件的新实例时,必须 使用命名参数初始化所有@Link变量。@Link变量可以使用@State变量或@Link变量的引 用进行初始化,@State变量可以通过“$”操作符创建引用。@Link装饰的变量可以和父组件的@State变量建立**双向数据绑定**。 @Link状态数据具有以下特征: ①支持多种类型:@Link变量的值与@State变量的类型相同,即cass、number、string、boolean或这些类型的数组; ②私有:仅在组件内访问; ③单个数据源:初始化@Link变量的父组件的变量必须是@State变量; ~~~ @Entry @Component export struct Index { @State agef:number=18 build() { Column({space:20}) { Text(this.agef.toString()).fontSize(50) /* 父亲按钮,点击增加年龄 */ Button("父亲按钮"+this.agef) .onClick(()=>{ this.agef=this.agef+1 }) /* 调用子组件button2,链接(双向)年龄属性 */ button2({age:$agef}) } .justifyContent(FlexAlign.Center) .height('100%') .width('100%') } } /** * 子组件button2 * @Link 双向绑定 */ @Component struct button2{ @Link age:number build(){ Column(){ /* 显示年龄的按钮,点击增加年龄 */ Button("双向数据"+this.age) .type(ButtonType.Normal) .borderRadius(10) .onClick(()=>{ this.age=this.age+1 }) } } } ~~~ ![](./img/32.png) ### **@Observed和@ObiectLink数据管理** @Observed应用于类,表示该类中的数据变更被UI页面管理,例如:@Observed class ClassA {}。 @ObjectLink应用于被@Observed所装饰类的对象,例如:@ObjectLink a: ClassA。 ![](./img/33.png) # **4. HarmonyOS进阶组件与简易任务列表** 本模块主要讲解Tabs选项卡、List列表、ForeEach循环渲染的使用,提供各模块小案例代码,最终综合使用实现一个简单代办任务列表DEMO。 ## **4.1 Tabs选项卡**   在一个页面内快速实现视图内容的切换   当页面信息较多时,为了让用户能够聚焦于当前显示的内容,需要对页面内容进行分类,提高页面空间利用率。一方面提升查找信息的效率,另一方面精简用户单次获取到的信息量。   Tabs组件的页面组成包含两个部分,分别是TabContent和TabBar。TabContent是内容页,TabBar是导航页签栏,页面结构如下图所示,根据不同的导航类型,布局会有区别,可以分为底部导航、顶部导航、侧边导航,其导航栏分别位于底部、顶部和侧边。 ![](./img/34.png) ### **Tabs代码框架:** ``` Tabs(){ TabContent() TabContent() ... } ``` 可通过设置Tabs的BarPosition枚举与vertical属性完成导航栏的展示方向设置 BarPosition枚举: ![](./img/35.png) BarPosition为End,vertical属性方法设置为false时,页签位于容器底部 **代码示例**: ``` Tabs({barPosition :BarPosition.End }){ TabContent(){ // 调用简易代办列表组件 Text('简易代办列表') .fontSize(30) .fontWeight(FontWeight.Bold) } .tabBar('ListEasy') TabContent(){ // LISTHEIGHT Text('高级任务列表') .fontSize(30) .fontWeight(FontWeight.Bold) } .tabBar('ListHeight') TabContent(){ // LISTHEIGHT Text('Account') .fontSize(30) .fontWeight(FontWeight.Bold) } .tabBar('Account') } ``` 预览器效果: ![](./img/36.gif) ### **自定义导航栏** 导航栏参数传递变量与自定义导航栏函数定义 ``` @State index_ : number = 0 @Builder tabBuilder( Title : string , index : number , ){ Column(){ Text(Title) .fontSize(this.index_ === index ? 20 : 15) .fontColor(this.index_ === index ? '#ff00e2ff' : '#ffff0000') } } ``` 同时参数需要在.tabBar属性传递: ``` ... TabContent() { ... } .tabBar(this.tabBuilder('ListEasy',0)) ... ``` 预览器预览效果: ![](./img/37.gif) ## **4.2 List容器&ForEach循环渲染**   轻松高效显示结构化、可滚动的信息   列表是一种复杂的容器,当列表项达到一定数量,内容超过屏幕大小时,可以自动提供滚动功能。 它适合用于呈现同类数据类型或数据类型集,例如图片和文本。在列表中显示数据集合是许多应用程序中的常见要求(如通讯录、音乐列表、购物清单等)。 ### List代码框架 ``` List(){ ListItem(){ //列表内容 ... } } ``` #### List&ForEach代码框架   使用列表可以轻松高效地显示结构化、可滚动的信息。通过在List组件中按垂直或者水平方向线性排列子组件ListItemGroup或ListItem,为列表中的行或列提供单个视图,或使用循环渲染迭代一组行或列,或混合任意数量的单个视图和ForEach结构,构建一个列表。 定义ListEasy数组ForEach循环渲染调用: ``` ... //@State装饰的变量是组件内部的状态数据,当状态数据被修改时,会自动进行UI刷新。(状态管理) @State ListEasy: Array = [ "HarmonyOS_NEXT", "创建项目","封装工具文件", "定义类", "自定义组件","调用组件", "调用类", "导入配置文件","关系型数据库"]; ... ``` 接口:ForEach(arr: Array, itemGenerator: (item: Object, index: number) => void,) ![](./img/38.png) 调用ListEasy数组赋值ForEach组件arr接口,循环渲染ListItem快速构建列表,代码: ``` ... List({space : 30}){ // 循环渲染快速构建列表 ForEach(this.ListEasy , ( item : string) =>{ ListItem(){ //内容 Row(){ Text(item) .margin({ left: 10 }) .fontSize(30) } .width('90%') .height("8%") } .borderRadius(15) .backgroundColor(Color.White) }) } ... ``` 预览器预览效果: ![](./img/39.png) ## **4.3 简易任务列表**   使用Tabs、List与ForEach组件快速构建简易代办任务列表APP   定义自定义代办事项组件ListContent,使用@Preview装饰器预览代办UI复刻效果进行调试,预备进行调用 ``` ... Row(){ Checkbox() .onChange(isChange =>{ this.isChange = isChange }) Text(this.item) .margin({ left: 10 }) .fontSize(30) .decoration({ type : this.isChange ? TextDecorationType.LineThrough : TextDecorationType.None}) } .width('90%') .height("8%") ... ``` 预览器预览效果: ![](./img/40.png) ForEachu循环渲染自定义组件ListContent实现列表效果 ``` ... Column() { // 界面主题 Text('TaskListEasy') .fontSize(30) .fontWeight(FontWeight.Bold) .height('15%') // 创建建议任务代办列表 List({space : 30}){ // 循环渲染快速构建列表 ForEach(this.ListEasy , ( item : string) =>{ ListItem(){ //调用自定义组件 ListContent({item : item }) } .borderRadius(15) .backgroundColor(Color.White) }) } .alignListItem(ListItemAlign.Center) .height('85%') List(){ ListItem(){ } } } .alignItems(HorizontalAlign.Center) .backgroundColor('#ffb7ffff') ... ``` 预览器预览效果: ![](./img/41.png) 封装代办列表组件,在Tab导航栏界面调用,呈现代办列表效果 ``` ... Tabs({barPosition :BarPosition.End }){ TabContent(){ // 调用简易代办列表组件 Page_ListEasy() } .tabBar(this.tabBuilder('ListEasy',0))\ ... } ... ``` 预览效果: ![](./img/42.png) 补充: ForEach循环渲染中进行代办任务是否完成布尔判断针对同一个变量,布尔值发生变换所有代办任务事项都将发生改变,因此在自定义ListContent组件中定义变量,循环渲染组件为每一项代办生成一个布尔变量,用于单独控制代办事项是否完成。 ``` ... //定义布尔变量 @State isChange : boolean = false ... Text(this.item) .margin({ left: 10 }) .fontSize(30) //判断代办事项是否完成 .decoration({ type : this.isChange ? TextDecorationType.LineThrough : TextDecorationType.None}) ... ``` # **5. HarmonyOS关系型数据库**   关系型数据库(Relational Database,RDB)是一种基于关系模型来管理数据的数据库。关系型数据库基于SQLite组件提供了一套完整的对本地数据库进行管理的机制,对外提供了一系列的增、删、改、查等接口,也可以直接运行用户输入的SQL语句来满足复杂的场景需要。 ![](./img/43.png) 同一个应用程序可初始化多个数据库文件,用于存储不同类型的数据,进行读写于数据库操作。 ![](./img/44.png) ### 关系型数据库本地数据持久化登录注册DEMO   传统登录注册功能数据一般不在本地存储,本章主要借用登录注册学习关系型数据库开发与相关增删改查操作。   预备工作:在ETS目录下创建一个model文件夹,创建.ets文件LoadModel,打开文件,导入方舟数据管理模块relationalStore、ValuesBucket,创建LoadModel类 ``` // 导入模块 import { relationalStore, ValuesBucket } from '@kit.ArkData'; //类创建 class LoadModel{ //数据库操作 ... } let loadModel = new LoadModel(); export default loadModel as LoadModel; ``` 封装关系数据库(RDB)方法的接口,定义管理数据库表名称: ``` // 提供管理关系数据库(RDB)方法的接口 private rdbStore : relationalStore.RdbStore | undefined = undefined; // 定义数据库表名 private tableName_LOAD : string = 'LOAD' ```   用户进行数据库操作前,需要先拥有一个可操作的数据库,因此需要进行数据库初始化,我们创建一个initLoadDB对象,进行rdb配置、初始化SQL语句、获取RDB、执行SQL并保存rdbStore,完成初始化操作的封装,具体代码: ``` /** * 初始化用户账号数据库 */ initLoadDB( context :Context){ // 1.rdb配置 const config : relationalStore.StoreConfig = { name : 'LOAD.db', securityLevel : relationalStore.SecurityLevel.S2 } // 2.初始化SQL语句 const sql = `CREATE TABLE IF NOT EXISTS LOAD ( ID INTEGER PRIMARY KEY AUTOINCREMENT, ACCOUNT TEXT NOT NULL, PASSWORD TEXT NOT NULL )` // 3.获取RDB relationalStore.getRdbStore(context , config ,(err,rdbStore)=>{ if (err){ console.log('数据库表获取失败'+err) return } //执行SQL rdbStore.executeSql(sql) // 保存rdbStore this.rdbStore = rdbStore }) } ``` 为保证用户在启动APP时数据库完成初始化,因此我们在生命周期文件(EntryAbility)中调用在LoadModel类中封装好的initLoadDB()初始化方法: ``` ... onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); // 初始化登录注册数据库 LoadModel.initLoadDB(this.context) } ... ``` ![](./img/45.png)   用户登录前需要完成注册,我们首要进行增加功能封装 ``` /** * 注册用户,数据库添加账号密码 * @param account 注册账号,password 注册密码 * @returns 注册结果 */ addLoad( account : string , password : string){ // 查询用户是否注册 ... // 否,进行账户的添加 this.rdbStore?.insert(this.tableName_LOAD,{account,password}) console.log('注册成功') return } ``` ![](./img/46.png) 为验证注册是否成功,用户的数据是否被成功添加进数据库,我们可以进行查询操作,并将查询到的用户数据存储在一个数组中打印进行验证,为此我们定义了LoadInfo类,作为数组中元素的类型;主要包含用户账号、密码元素。 ``` // 定义登录注册信息传递类 @Observed export default class LoadInfo{ //账号 account : string //密码 password: string constructor(account : string , password: string) { this.account = account this.password = password } } ``` 随后封装查询功能,主要使用.query方法;并通过循环遍历数据库,将查询到的数据存储到数组中。 ``` /** * 查询已注册的账号密码 * @returns 返回账号密码键值对,数组形式存储 */ async getLoad(){ // 1.构建查询条件 let predicates = new relationalStore.RdbPredicates(this.tableName_LOAD) // 2.查询 let result = await (this.rdbStore as relationalStore.RdbStore).query( predicates , ['ID','ACCOUNT','PASSWORD'] ) // 3.解析查询结果 let Load : LoadInfo[] = [] while (!result.isAtLastRow){ result.goToNextRow() let account = result.getString(result.getColumnIndex('ACCOUNT')) let password = result.getString(result.getColumnIndex('PASSWORD')) Load.push(new LoadInfo(account,password)) } console.log('查询到的数据'+JSON.stringify(Load)) return Load } ``` 调用查询功能,在日志中打印数组,可以看到上一步注册的账号密码是否成功添加到数据库 ![](./img/47.png) 注册成功后用户可进行登录,主要为查询功能的使用;查询数据库信息,与用户输入账号密码进行验证,返回不同结果;登录成功跳转进入主界面。 ``` /** * 传入账号密码数据,登录信息验证 * @param account 用户账号 ;password 用户输入密码 * @returns 验证结果 */ async LoadGet( accountInput: string , passwordInput :string){ // 1.查询条件 let predicates = new relationalStore.RdbPredicates(this.tableName_LOAD) // 2.查询用户账户密码 let result = await (this.rdbStore as relationalStore.RdbStore).query( predicates , ['ID','ACCOUNT','PASSWORD'] ) // 3.解析结果 while (!result.isAtLastRow){ result.goToNextRow() // 验证账号 let account = result.getString(result.getColumnIndex('ACCOUNT')) if (account === accountInput) { //密码验证 let password = result.getString(result.getColumnIndex('PASSWORD')) if (password === passwordInput) { return 0 } } } return } ``` 若用户不再使用此账户,想要对账户进行注销,为此我们添加删除功能,主要调用.delete方法,在数据库擦除用户数据。 ``` /** * 传入账号密码数据,验证并注销账号 * @param account 用户账号 ;password 用户输入密码 * @returns 账户注销结果 * */ deleteLoad( accountInput: string , passwordInput :string ){ // 1.查询条件 let predicates = new relationalStore.RdbPredicates(this.tableName_LOAD) predicates.equalTo('ACCOUNT' ,accountInput) // 验证账号密码 this.LoadGet(accountInput,passwordInput).then( result =>{ if (result === 0) { this.rdbStore?.delete(predicates) } } ) } ``` 擦除前后我们分别进行查询,验证用户账号注销(删除操作)功能是否实现 ![](./img/43.gif) 用户可能会对密码进行修改,我们使用.equalTo方法找到要更新的账户,使用.update方法完成对数据库的更新。 ``` /** * 更新账号密码 * @param account 用户账号 ;password 用户输入密码 ;passwordNew要更新的密码 * @returns 更新结果 */ updateLoadStatus(account : string ,password : string, passwordNew : string) { // 1.要更新的数据 const data: relationalStore.ValuesBucket = { 'PASSWORD' : passwordNew }; // 2.更新的条件 let predicates = new relationalStore.RdbPredicates(this.tableName_LOAD) predicates.equalTo('ACCOUNT', account) // 3.验证密码 this.LoadGet( account , password ) .then( results =>{ if (results === 0) { // 4.更新操作 this.rdbStore?.update( data , predicates ) console.log('更新成功') return } }) return } ``` 最后将我们的更新密码功能与增删进行整合,完成基于关系型数据库本地数据库持久化登录注册DEMO的开发: ![](./img/44.gif) # 6. HarmonyOS 日常任务管理APP 整体日常任务管理APP分为三部分:任务进度卡片、新增任务按钮、任务列表。 当点击新增任务时,任务列表会随之刷新,用户可以通过填写输入框来添加新任务,并使任务进度卡片的总进度加一。用户也可以随时确认或删除当前任务。 ![](./img/50.png) ## 6.1 任务进度卡片 将文本、进度条、文本进行横向排列,并将后者用Stack()进行叠加。 其中**finishTask**为已完成指,**totalTask**为宗任务进度。进度条改变时文本会随之改变。 ~~~ Row(){ Text("任务进度") .fontSize(35) .fontWeight(800) Stack() { Progress({ value: this.finishTask, total: this.totalTask, type: ProgressType.Ring }) Row() { Text(this.finishTask.toString()) Text(" / " + this.totalTask.toString()) } } } .borderRadius(15) .backgroundColor(Color.White) .width("95%") .height("13%") .justifyContent(FlexAlign.SpaceEvenly) ~~~ ## 6.2新增任务按钮 定义一个数组用于存放任务数据 ~~~ @Observed class task{ id :number = 0 finished:boolean = false } .... @State tasks:task[] = [] ~~~ 当点击按钮时,数组随之增加一,总任务数量等于目前数组长度 ~~~ Button("新增任务") .width("50%") .onClick(()=>{ this.tasks.push(new task()) //总任务数量 this.totalTask = this.tasks.length }) ~~~