# 鸿蒙笔记 **Repository Path**: web-asen/hongmeng-notes ## Basic Information - **Project Name**: 鸿蒙笔记 - **Description**: 鸿蒙笔记,用来记录鸿蒙学习的过程 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-05-15 - **Last Updated**: 2025-12-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ArkTs # 鸿蒙入门篇 ## 什么是HarmonyOS? ```te HarmonyOS 是华为推出的一个全场景、多设备类型、多内核的分布式操作系统。 鸿蒙(即HarmonyOS,开发代号Ark,正式名称为华为终端鸿蒙智能设备操作系统软件)是华为公司自2012年以来开发的一款可兼容AOSP的操作系统。系统性能包括利用“分布式”技术将各款设备融合成一个“超级终端”,便于操作和共享各设备资源。 系统架构支持多内核,包括Linux内核、LiteOS和鸿蒙微内核,可按各种智能设备选择所需内核,例如在低功耗设备上使用LiteOS内核。 华为于2021年6月正式发布了HarmonyOS。 ``` ## HarmonyOS发展历程 **2019年:** 华为首次发布 HarmonyOS,这是一个全新的操作系统,旨在提供一个统一的、分布式的操作系统。 **2020年:** 华为推出 HarmonyOS 2.0 版本,支持更多设备类型,包括智能手机、平板、电视、智能穿戴等。 **2021年:** 华为推出 HarmonyOS 3.0 版本,增加了更多高级特性,如 AI 能力、分布式数据管理、分布式软总线等。 **2023年:** 华为推出 HarmonyOS 4.0 版本,ArkUI进一步完善组件能力和效果、应用框架优化了Extension能力。 **2024年:** 华为推出 HarmonyOS Next 版本,从HarmonyOS NEXT Developer Preview1起,HarmonyOS开放能力将以Kit维度呈现给开发者 | **新规则版本号** | **版本类型** | **OpenHarmony API Version** | **compileSdkVersion** | | ---------------- | ------------------ | --------------------------- | ------------------------------------ | | HarmonyOS NEXT | Developer Beta1 | 12 Beta1 | **5.0.0(12)** 5.0:SDK版本,12:api版本 | | HarmonyOS NEXT | Developer Preview2 | 11 Release | 4.1.0(11) | | HarmonyOS NEXT | Developer Preview1 | 11 Beta1 | 4.1.0(11) | | HarmonyOS NEXT | Developer Preview0 | 10 Release | 4.0.0(10) | ## 简介 从HarmonyOS NEXT Developer Preview1(API 11)版本开始,HarmonyOS SDK以Kit维度提供丰富、完备的开放能力,涵盖应用框架、应用服务、系统、媒体、图形、AI六大领域,例如: - **应用框架相关Kit开放能力:**Ability Kit(程序框架服务)、ArkUI(方舟UI框架)等。 - **应用服务相关Kit开放能力:**Account Kit(华为帐号服务)、Location Kit(位置服务)等。 - **系统相关Kit开放能力:**Network Kit(网络服务)、Universal Keystore Kit(密钥管理服务)等。 - **媒体相关Kit开放能力:**Audio Kit(音频服务)、Media Library Kit(媒体文件管理服务)等。 - **图形相关Kit开放能力:**ArkGraphics 2D(方舟2D图形服务)等。 - **AI相关Kit开放能力:**Intents Kit(意图框架服务)、HiAI Foundation Kit(HiAI Foundation服务)等。 ## HarmonyOS基本概念 #### 先了解一下什么是声明式和命令式? #### 例子:去超市买东西(好比就是业务) ```text 命令式: 命令式的方式就像是给一个人详细的指示,逐步告诉他如何完成任务。 1. 从家里出发。 2. 左转到主街道。 3. 走两条街到红绿灯。 4. 右转到超市停车场。 5. 找个停车位停车。 6. 进入超市。 7. 去蔬菜区买苹果。 8. 去乳制品区买牛奶。 9. 去结账柜台结账。 10. 返回停车场。 11. 开车回家。 在这个过程中,每一步都被详细列出,确保完成任务的每个步骤都明确无误。 例如我们常见的语言:c,c++,python,C#,java ``` **声明式:**(不用关心业务) ```text 声明式的方式则更像是直接说明你想要的结果,而不关心具体的步骤。 ● 去超市买苹果和牛奶。 类似我们前端的Vue,React ``` #### 总结 - **命令式**:像教人做事一样,提供详细的步骤和操作。每一步都明确描述,例如“左转、右转、取出、放置”等。 - **声明式**:像给出目标或结果一样,描述希望达到的状态,而不具体说明怎么做。目标明确但步骤灵活,例如“去超市买东西”、“准备早餐” | **开发范式名称** | **语言生态** | **UI更新方式** | **适用场景** | **适用人群** | | ---------------- | ------------ | -------------- | -------------------------------- | -------------------------------------- | | 声明式开发范式 | ArkTS语言 | 数据驱动更新 | 复杂度较大、团队合作度较高的程序 | 移动系统应用开发人员、系统应用开发人员 | | 类Web开发范式 | JS语言 | 数据驱动更新 | 界面较为简单的程序应用和卡片 | Web前端开发人员 | ### 应用模型 ```text 应用模型是HarmonyOS为开发者提供的应用程序所需能力的抽象提炼,它提供了应用程序必备的组件和运行机制。有了应用模型,开发者可以基于一套统一的模型进行应用开发,使应用开发更简单、高效。 ``` 随着系统的演进发展,HarmonyOS先后提供了两种应用模型: - **Stage模型:** HarmonyOS API 9开始新增的模型,是目前主推且会长期演进的模型。在该模型中,由于提供了AbilityStage、WindowStage等类作为应用组件和Window窗口的“舞台”,因此称这种应用模型为Stage模型。 - **FA(Feature Ability)模型:** HarmonyOS API 7开始支持的模型,已经不再主推。 ## 基础知识 ### 创建ArkTS工程 #### 填写相应的配置 ```text Project name:项目名称,这里规范尽量用大写开头,驼峰命名 Bundle name:项目bundle唯一标识名称,这里命名规则如下 :由字母、数字、下划线和符号“.”组成,且必须以字母开头。推荐采用反域名形式命名(如“com.example.demo”,建议第一级为域名后缀com,第二级为厂商/个人名,第三级为应用名,也可以多级)。 Save location:项目存放路径 Compile SDK:默认即可,编译的SDK版本,这里我们选择4.1这个版本,对应的api11,如果有最新版本优先选择5.0版本对应的api12 Compatible SDK: 默认,兼容版本,我们这里同上 Module name:项目的模块名称 Device type:支持的设备类型 ``` | **设备类型** | **枚举值** | **说明** | | ------------ | ---------- | ---------------------- | | 手机 | phone | | | 平板 | tablet | | | 2in1设备 | 2in1 | 指的是pc平板二合一电脑 | ### ArkTS工程目录结构 ```js //根目录下的结构 ● .hvigor: 存储购置信息的文件,主要用于发布打包; ● .idea:开发工具的相关配置文件; ● AppScope > app.json5:/*APP应用的全局配置信息。*/AppScope目录由DevEco Studio自动生成,不可更改 ● entry:HarmonyOS工程模块 //HAP包 ● oh_modules:用于存放三方库依赖信息。 ● build-profile.json5:应用级配置信息,包括签名signingConfigs、产品配置products等。其中products中可配置当前运行环境,默认为HarmonyOS。 ● hvigorfile.ts:应用级编译构建任务脚本。 ● local.properties:属性配置文件,主要保存本地的一些配置信息,比如sdk所在目录 ● oh-package.json:ohpm包依赖管理文件 ● hvigorw:Linux下执行的脚本文件(OHP编译构建脚本文件); ● hvigorw.bat:bat windows下执行的脚本文件(OHP编译构建脚本文件); //entry 下的结构 ● entry:HarmonyOS工程模块,编译构建生成一个HAP包。 1. src > main > ets:用于存放ArkTS源码。 2. src > main > ets > entryability:应用/服务的入口。 3. src > main > ets > pages:应用/服务包含的页面。 4. src > main > resources:用于存放应用/服务所用到的资源文件,如图形、多媒体、字符串、布局文件等。 5. src > main > module.json5:模块配置文件。主要包含HAP包的配置信息、应用/服务在具体设备上的配置信息以及应用/服务的全局配置信息。 6. build-profile.json5:当前的模块信息 、编译信息配置项,包括buildOption、targets配置等。 7. hvigorfile.ts:模块级编译构建任务脚本,开发者可以自定义相关任务和代码实现。 8. obfuscation-rules.txt:混淆规则文件。混淆开启后,在使用Release模式进行编译时,会对代码进行编译、混淆及压缩处理,保护代码资产。 ``` ## 一、数据类型 ```js Number String Boolean Array Enum(枚举类型:是一种特殊的数据类型,约定变量只能在一组数据范围内选择值) Object Union(联合类型:是一种灵活的数据类型,它修饰的变量可以存储不同类型的数据) Aliases ``` ### 1.1定义number类型 ```js let n1:number = 10 //也可以是小数 let n2:number = 20.5 //也可以禁制表示 let b1:number = 0o17 //八进制 最后一位数 0到7 结果是15 运算是1*8+7 let b2:number = 0x17 //16禁制 结果23 运算是1*16+7 let b3:number = 0b1111 //2禁制 只能放0和1 结果15 ``` ### 1.2定义boolean类型 ```js boolean类型有true和false两个逻辑值组成 let bFlag:boolean = true ``` ### 1.3定义字符串类型 ```js let s1:string = 'hellow' let s2:string = "hellow" let s3:string = `hello ${其他类型的变量拼接} \t s1` //字符串拼接 \t转义字符 后面可以拼接s1变量值 ``` ### 1.4枚举类型定义 ```js //定义枚举类型 //默认值就是 从0开始 也就是说GoodMan是0,BadMan是1,SexMan是2 enmu Man{ GoodMan, BadMan, SexMan, } //使用 let you:Man = Man.GoodMan //这里的输出就是0 //也可以给枚举类型指定值 enmu Man{ GoodMan = 100, BadMan = 200, SexMan = 300, } let you1:Man = Man.GoodMan //这里的输出就是100 ``` ### 1.5联合类型 ```js //例如定义一个分数 let score:string | number |null = "A" //这里的score可以是 string类型也可以是number类型 也可以是null类型 score = 80 //这里不会报错 score = null //这里不会报错 score = true //这里会报错 ``` ### 1.6定义数组类型 ```js //定义一个字符串数组 数组的取值是一个空数组 let arr:string[] = [] //第二种定义数组长度 let arr2:number[] = new Array(10) //这里数组的长度就是11 因为从0开始 //第三种定义数组的方式 let arr3: /* 1.数组取下标的时候 如果不存在 则报undefined 2.支持动态扩容 例如arr2[100] = 100 这时数组的长度就变成了101 */ ``` ## 二、ArkTs 运算符 ### 2.1 赋值运算符 ```js 1. =, 使用方式如x=y。 2. 复合赋值运算符将赋值与运算符组合在一起,其中xop=y等于x=xop y 3. 运算符列举如下:+=、-=、*=、/=、%=、<<=、>>=、>>>=、&=、|=、^= ``` ### 2.2 比较运算符 | 运算符 | 说明 | | ------ | -------------------------------------------- | | == | 如果两个操作数相等,则返回true。 | | != | 如果两个操作数不相等,则返回true。 | | > | 如果左操作数大于右操作数,则返回true。 | | >= | 如果左操作数大于或等于右操作数,则返回true。 | | < | 如果左操作数小于右操作数,则返回true. | | <= | 如果左操作数小于或等于右操作数,则返回true。 | ### 2.3 算数运算符 一元运算符为 -、+、--、++ 二元运算符列举如下 | 运算符 | 说明 | | ------ | ---------- | | + | 加法 | | - | 减法 | | * | 乘法 | | / | 除法 | | % | 除法后取余 | ### 2.4 位运算符 | 运算符 | 说明 | | ------ | ------------------------------------------------------------ | | a & b | 按位与:如果两个操作数的对应位都为1,则将这个位设置为1,否则设置为0。 | | a \| b | 按位或:如果两个操作数的相应位中至少有一个为1,则将这个位设置为1,否则设置为0。 | | a ^ b | 按位异或:如果两个操作数的对应位不同,则将这个位设置为1,否则设置为0。 | | ~a | 按位非:反转操作数的位。 | | a<>b | 算术右移:将a的二进制表示向右移b位,带符号扩展 | | a>>>b | 逻辑右移:将a的二进制表示向右移b位,左边补0。 | ### 2.5 逻辑运算符 | 运算符 | 说明 | | -------- | ---- | | a && b | 并且 | | a \|\| b | 或 | | !a | 非 | ## 三、ArkTs分支和循环 ### 3.1语句 ```js if语句 和js一样使用 Switch语句 和js一样使用 条件表达式 For语句 For-of语句 While语句 Do-while语句 Break(结束整个循环)/Continue(跳出本次循环) ``` ## 四、ArkTs函数 ### 4.1 函数的定义 函数:是可以被重复使用的代码块 “包裹” 起来,有利于代码复用 作用: 函数可以把具有相同或者相似逻辑的代码 调用:函数名() 定义: ```js function 函数名(参数1:类型,参数2:类型):返回值类型{ 函数体 } //注意:先定义 后使用(变量、函数都是如此) //参数2 也可以是默认值 function 函数名(参数1:类型,参数2:类型 = 10):返回值类型{ 函数体 } //参数2 也可以是可选参数 用?号表示 function 函数名(参数1:类型,参数2?:类型):返回值类型{ 函数体 } //参数2 也可以是 可变参数 官方称之为Rest参数 function 函数名(参数1:类型,...参数2:类型[]):返回值类型{ 函数体 } //注意:可变参数必须放在列表的最后一个位置 ``` ### 4.2箭头函数 箭头函数:箭头函数是 比普通的函数更简洁 的 一种函数写法 ```js //1. 无参数也无返回值的箭头函数 也可以称之为匿名函数 ()=>{ 函数体 } //2. 无参数也无返回值的有名箭头函数 let fn1 = ()=>{ 函数体 } // 3. 有参数有返回值的有名箭头函数 let fn1 = (n:number):number =>{ return 0 } ``` ### 4.3函数类型 函数类型:函数类型代表函数的引用,通常用于定义回调 ```js //自定义的一个函数类型,相当于一个函数的指针,引用 type 自定义函数类型名(myFunType) = ()=>void //使用自定义函数类型 function printCar(){ console.log(111) } let 变量名(fn):自定义函数类型名(myFunType) = printCar //这里的 printCar 千万不能加() //然后再下面直接调用fn就可以了 fn() //2.通过点击事件来触发函数类型的定义 let 自定义函数类型名(myBtnClickFun):引用上面的函数类型(定义myFunType) = 等于这个函数体(printCar) Button("测试").onclick( 自定义函数类型名(myBtnClickFun)) ``` ## 五、ArkTs 类和对象基本使用 ### 5.1对象 对象:客观存在的物体就是对象,是一个可以存储多个数据的容器 ```js 先定义对象类型 //通过class类型定义对象类型 必须给一个默认值 class C{ n:number = 0 s:string = '' } //使用 let obj:C = {n:10,s:'a'} console.log(obj) ``` ### 5.2 类 类和对象的基本概念:类就是一种类型,是一种对象的抽象 对象的抽象集合就是类,也就是自定义类型 ```js //类定义的格式 class 类名{ 字段声明 构造方法 普通方法 静态成员 } 例如: //定义字段声明第一种方式 class (类名)Employee{ name:string = "" comm:number = 0 } //定义字段声明第二种方式 使用构造方法 class 类名)Employee{ name:string comm:number //构造方法 只能写一个 不能有多个构造方法 constructor(name:string,comm:number){ this.name = name this.comm = comm } //类里面定义方法 方法名(show)(){ console.log(this.name,this.comm) } } //3.使用上面的类 //先导出这个类 export class (类名)Employee{ } //然后在这个页面中引用这个类 import {Employee} from "类的路径文件";//或者导入时候 也可以起别名 import {Employee as emp } from "类的路径文件" //此时aa就是一个对象,可以使用.去访问对象中的方法和属性 let aa = new Employee() 或者 new emp() aa.方法名() aa.属性名 ``` ### 5.3修饰符 有三个访问修饰符 private(私有) protected(保护) public(公有) ```js class 类名)Employee{ //默认是public公有的修饰符 name:string comm:number private sal:number //使用私有的修饰符对这个字段进行修饰 类里面自己可以访问到 外部引入的就访问不到这个字段了 //外部如果想访问私有属性的字段,需要通过调用方法去取到 getSal(){ return this.sal } //如果想修改私有属性的值 需要通过方法调用去设置 setSal(sal:number){ this.sal = sal } } ``` ### 5.4继承 继承:子承父业 作用: ```js 1. 通过继承可以实现代码复用 2. 通过多态可以实现更加灵活的代码 3. 通过接口编程,实现各个模块之间的解耦 4. 作为架构师的常用编程实现手段,作为设计模式实现的必要手段 ``` 语法 ```js //通过继承,子类可以拥有父类非私有的属性和方法 注意:私有的不能继承 //通过继承,子类还可以重写来自父类的方法 //1. 可以理解为 父类有构造器(constructor)的时候 继承的语法 class 子类 extends 父类{ } let 变量 = new 子类(参数1,参数2) //2. 父类没有构造器(constructor)的时候 继承的语法 //子类对象的产生 晚于父类对象的产生 class 父类{ name:string comm:number } class 子类 extends 父类{ constructor(name:string,comm:number){ super() //调用父类的默认的构造方法 相当于父类先执行 this.name = name this.comm = comm } } let 变量 = new 子类(参数1,参数2) ``` ## 六、Arktd多态 静态的多态:方法的重载 ​ 一个类中的方法名相同,但是参数不同,重载 动态的多态:方法的重写 ### 6.1 静态成员 静态成员属于类,不属于对象 ```js //static 关键字开头的成员 1.静态的字段 2.静态的方法 3.静态的代码块 ``` 语法 ```js class 类名{ //这是静态字段 static pi:number = 3.1415926 //这是静态方法 static show(){ 函数体 } //这是静态代码块 注意:每一个类中只有一个静态代码块,静态代码块在这个类导进来的时候就会自动执行 static { console.log('静态代码块') } } console.log(类名.pi) console.log(类名.show()) ``` ## 七、ArkTs接口和泛型 ### 7.1泛型 泛型的概念:类的参数化,泛型被广泛用在集合类型的数据结构中,可以保证类型的安全,使用的时候也可以防止进行强制类型转换。 ```js //传进来的是什么类型 这个T就是什么类型 //也就是类型参数化 将类型作为参数传递进来 class 类名{ //尖括号中的T可以自定义取名 取T是常见的 index:number = -1 data:T[] = [] //入栈 push(o:T){ this.index++ this.data[this.index] = o } //出栈 pop():T返回值类型{ let o = this.data[this.index] this.index-- return o } } //使用的时候 let 变量名 = new 类名() //类型就传递到类的T中 变量名.push(1) 变量名.push(2) let o = 变量名.pop() console.log(o) ``` ### 7.2 泛型约束 ```js class 类名1{ } //T extends 类名1 泛型约束 class 类名{ } //使用 //一旦泛型约束了 这个类 只能放这个类的属性和类的子级 放不了其他的值 let 变量 = new 类名<类名1>() 变量.push(类名1.属性) 变量.push(类名1.子类) ``` ### 7.3泛型函数 ```js //例如取数组长度的时候,有的字符串数组,有的数字数组,类型不一样的情况下要写两套函数,不符合面向对象编程 这时候需要用到泛型函数 语法 function getLast(arr:T[]):T(返回值类型){ //T[] 将arr数组的类型动态的传到T身上 注意这里的T[]决定了类型 return arr[arr.length-1] } 使用 let o1 = getLast([1,2,3]) cobsole.log(o1) //T[] 这里的T此时是number类型 决定了是number类型,也决定了:T是number类型 let o2 = getLast(['1','2','3']) console.log(o2) //T[] 这里的T此时是string类型 决定了是string类型,也决定了:T是string类型 ``` ## 八、ArkTs安全 ```js //例如 let x:string = null let y:number = null let z:object = null //但是在ArkTs中是不支持的,Arkts提供了四种解决方案 ``` ### 8.1 联合类型解决 ```js let x:string | null = null let y:number |null = null let z:object | null = null //在每种类型上加上或者 ``` ### 8.2 使用非空断言运算符 默认情况下,ArkTS中的所有类型都是不可为空的。如果要设置为空需要进行特殊的处理,并且在获取 可能为空的值的时候也需要特殊处理。 例如: ```js //后缀运算符! 可用于断言其操作数为空 //应用于空值时,运算符将抛出错误。否则,值的类型将从T | null更改为T: let a:number | null = null let b:number b = a! + 1; //此时这里的a!就是非空断言 不参与运算 此时的结果是1 ``` ### 8.3 空值合并运算符 默认情况下,ArkTS中的所有类型都是不可为空的。如果要设置为空需要进行特殊的处理,并且在获取 可能为空的值的时候也需要特殊处理, ```js //空值合并二元运算符??用于检查左侧表达式的求值是否等于null。如果是,则表达式的结果为右侧表达式;否则,结果为左侧表达式 //换句话说,a??b等价于三元运算符a!null?a:b。 undefine===null=>true class 类名{ name:string | null = null getName():string | null{ return this.name //这时候返回的事null 但是期望的是'' //需要换成下面的写法 if(this.name===null){ return '' }else{ return this.name } //对上面的if else 简写 return this.name ?? '' //如果this.name有值就返回this.name 否则返回''字符串 } } ``` ### 9.2 next升级 ```js 1. UI更新,Diff算法的升级,之前是component和elemnt树形结构,现在是页面代码一变化单节点NODE的函数式 2. 逻辑和ui分离 流程步骤的缩减:之前是七个步骤,现在是只有两步 保存流转数据——>恢复流转数据 3. 高级ui组件扩展能力 基于XComponent组件的C++自绘制引擎接入 4. Arkts语言,之前是Ts语言 4. 常见布局 5 ui组件 5.1 华为图片库 5.2 通用属性 5.3 装饰器 5.4 常用的UI组件 6. 页面路由和导航 7. 图形 8. 动画 9. 交互事件 10. 自定义能力 11. 使用ndk构建ui ``` ## 十、常见布局 ```js Column 列布局 Row 行布局 Stack 栈布局 Flex 弹性布局 ``` ### 10.1 Column 列布局 ```js 1.从上往下的布局 2. 会使内部的元素默认横向居中对齐 主轴: 竖着叫主轴 justifyContent 垂直方向对其 交叉轴 横着叫交叉轴 alignItems() 水平方向的对齐 使用: //space:5 意思就是文本与文本之间 相隔5vp;为什么不用px 因为px是物理单位 vp是虚拟单位 Coumn({space:5}){ Text('文本').width(150).border({width:1,color:'red'}) Text('文本').width(150).border({width:1,color:'red'})//这时候会显示上面一个文本,下面一个文本,因为用的列布局从上往下 } ``` ### 10.2 Row 行布局 ```js 1.从左往右布局 2. 默认垂直方向居中 3. 默认水平方向也居中 主轴: 横着叫主轴 justifyContent 垂直方向对其 交叉轴 竖着叫交叉轴 alignItems() 水平方向的对齐 使用: //space:5 意思就是文本与文本之间 相隔5vp;为什么不用px 因为px是物理单位 vp是虚拟单位 Row({space:5}){ Text('文本').width(150).border({width:1,color:'red'}) Text('文本').width(150).border({width:1,color:'red'})//这时候会显示从左往右,正常排序 } ``` ### 10.3 Stack 栈布局(层叠布局) ```js 1. 栈布局有9种布局方式 1.1 TopStart上左 1.2 Top 上中 1.3 TopEnd 上右 1.4 Start 左中 1.5 Center 八方向居中 1.6 End 右中 1.7 BittomStart 下左 1.8 Bottom 下中 1.9 Bottom 下右 2. 默认在垂直方向居中 水平方向居中 Stack({alignContent:Alignment.上面的9种对其方式}){ text('文本1').width(150) //如果想让第一个元素覆盖下面两个 就在设置.Zindex(1) text('文本2').width(150) text('文本3').width(150)//此时 三个文本就会叠在一起 3在2的上面 2在1的上面 } ``` ### 10.4 flex弹性布局 ```js 1. 主轴方向:direction:取值:FlexDirection.Row 和 FlexDirection.Column 2. 主轴对齐方式:justifyContent 取值FlexAlign.SpaceAround 3. 交叉轴对齐方式:alignItems 取值ItemAlign.Start 4. 布局换行 wrap 取值FlexWrap.Wrap 5.默认是从左往右的对齐方式 跟平常的布局一样 flex(){ text('文本1').width(150) text('文本2').width(150) text('文本3').width(150) } ``` ### 10.5 相对布局 ```js 1.RelativeContainer下面不管多少子元素都在左上角集合 RelativeContainer(){ Text('row1').width(100).height(100).backgroundColor("#ff0000").id("song1") .alignRules({ 'top': { 'anchor': '__container__(相对于父级RelativeContainer)', 'align': VerticalAlign.Top(顶部位置) }, 'left': { 'anchor': '__container__(相对于父级RelativeContainer)', 'align': HorizontalAlign.Start(左侧位置) } }) Text('row2').width(100).height(100).backgroundColor("#00ff00") .alignRules({ 'top': { 'anchor': 'song1(相对于上面的text为song1为父级)', 'align': VerticalAlign.Top(顶部位置) }, 'left': { 'anchor': 'song1(相对于上面的text为song1为父级)', 'align': HorizontalAlign.Start(左侧位置) } }) } ``` ## 十一 常见的组件 ```js 1.文本框Text 使用: Text('文本').样式1().样式2 2.图片元素Image 图片都会放在 resources目录下base下media文件夹内 //通过$r()访问图片路径 Image($r('app.media.图片名')).样式1().样式2 //如果项目中有分文件夹分别存放图片时需要放在rawFile文件夹下新建,注意media文件夹下不能新建目录 //image访问rawFile文件夹时 Image($rawFile('目录名/图片名.后缀')).样式1().样式2 //如果图片需要涉及访问网络图片 需要再module.json5配置文件中加配置项,如果不加的话真机和模拟器上无法显示 "requestPermissions":[ {"name":"ohos.permission.INTERNET"} ] 3.文本输入框TextInput TextInput({placeholder:'请输入账号'}) 4.按钮Button //并且绑定点击事件 Button('按钮文字').样式1().样式2.type(ButtonType.Normal(去除默认圆角)).onclick(()=>{ //比如创建一个对象,之前可以使用字面量来写 现在需要该进成接口,或者类 1. 用接口创建的字面量 interface User{ uname:string upwd:string } 2.使用接口创建对象 //没有var声明 只有let 或者const let obj:User = { uname:"", upwd:"" } 3.使用类来创建字面量 class User{ uname:string upwd:string constructor(uname:string,upwd:string){ this.uname = uname this.upwd = upwd } } 4.使用类创建对象 let obj = new User('admin','123') }) 5.分割线组件 Divider() 6.swiper 轮播图组件 使用: //轮播控制组件 这样可以通过按钮控制轮播上一张,下一张 swipCtl:SwiperController = new SwiperController() Swiper(this.swipCtl){ text('文本1') text('文本2') } .width('80%') .height(300) .loop(false) //false:最后一张结束后 不自动回到第一张 .autoPlay(true) //自动轮播,默认是false .interval(1000) //自动轮播间隔时间 .indicatorStyle({ //设置轮播图圆点的样式,目前这个属性已经过期 size:20,//圆点的大小 left:10,//圆点距离左侧轮播的距离 color:"red",//圆点的颜色 }) Button('<').onclick(()=>{ this.swipCtl.showPrevious() //切换上一张 }) Button('>').onclick(()=>{ this.swipCtl.showNext() //切换下一张 }) ``` ## 十三动态变量 ### 13.1 @State 动态变量 如果页面上需要向vue的ref似的 数据一变化 页面也跟着变化 就使用@State 使用: ```js //先定义动态变量 @State timer:number = 5 class PostLoginMobilePayloadType { phone: string code: string constructor(phone: string, code: string) { this.phone = phone this.code = code } } @State formData: PostLoginMobilePayloadType = new PostLoginMobilePayloadType('', '') //在页面中 build(){ Stack(){ //第一种动态数据双向绑定 Button(`按钮${this.timer}`).样式1() TextInput({ placeholder: "请输入手机号", text: this.formData.phone }) .onChange((value) => { this.formData.phone = value }) //第二种通过$$内置组件双向同步数据 TextInput({ placeholder: "请输入手机号", text: $$this.formData.phone }) } } ``` ### 13.2 @State多层数据发生改变不发生变化 ```typescript class Stu2{ name:string = '' } interface Stu{ name:string, child:Stu2 } @State stu:Stu = { name:'a', child{ name:"b" } } Button('改变父级名').onClick(()=>{ this.stu.name = "c" //可以发生改变 }) Button('改变子级名').onClick(()=>{ this.stu.child.name = "c" //无法改变 //解决办法1 this.stu.child = {name:'c'} //这样就可以解决了 改变不了二级的改变一级的 }) ``` ### 13.2解决方法2: 通过@ObjectLink和@Observed装饰器来解决@State多层数据发生改变不发生变化 @Observed 必须给子对象加上 @ObjectLink 必须是在组件上加 ```js class Stu2{ name:string constructor(name:string){ this.name = name } } interface Stu{ name:string, child:Stu2 } @State stu:Stu = { name:'a', child{ name:new Stu2('b') } } //主页面中的子组件 @Component struct IndexChild{ @ObjectLink child:Stu2(类型) build(){ Column(){ Button('改变子级名').onClick(()=>{ this.child.name = "c" //这样就可以了 }) } } } //主页面 Column(){ //给组件传参 //组件名({属性:数据}) IndexChild({child:this.stu.child}) } ``` ## 十四 装饰器 ### 14.1 @Styles 如果每个组件的样式都需要单独设置,在开发过程中会出现大量代码在进行重复样式设置,虽然可以复制粘贴,但为了代码简洁性和后续方便维护,我们推出了可以提炼公共样式进行复用的装饰器@Styles。 @Styles装饰器可以将多条样式设置提炼成一个方法,直接在组件声明的位置调用。通过@Styles装饰器可以快速定义并复用自定义样式。用于快速定义并复用自定义样式 ```js //全局的需要加function //注意 这里的全局不是项目的全局 而是组件的全局 别妄想export import 目前不支持 @Styles function iptStyles(){ .height(50) .backgroundColor(Color.Transparent) .borderRadius(0) .border({ width: { bottom: 1 }, color: '#ccc' }) } //局部使用 struct Login{ //注意:局部的不需要加function @Styles iptStyles(){ .height(50) .backgroundColor(Color.Transparent) .borderRadius(0) .border({ width: { bottom: 1 }, color: '#ccc' }) } build(){ TextInput({ placeholder: "请输入手机号", text: this.formData.phone }).iptStyles() TextInput({ placeholder: "请输入验证码", text: $$this.formData.code }) .iptStyles() } } ``` ### 14.2 @Extend 用于扩展原生组件样式,通过传参提供更灵活的样式复用 ​ 仅支持全局 ​ 支持传参,传递状态自动同步,也可以传递回调函数 ```js @Extend(组件名TextInput) function iptStyles(data:number){ .layoutWeight(data) //动态传递 .height(50) .backgroundColor(Color.Transparent) .borderRadius(0) .border({ width: { bottom: 1 }, color: '#ccc' }) } //局部使用 struct Login{ build(){ TextInput({ placeholder: "请输入手机号", text: this.formData.phone }).iptStyles(0) TextInput({ placeholder: "请输入验证码", text: $$this.formData.code }) .iptStyles(1) //而现在layoutWeight也想封装到iptStyles全局中 .layoutWeight(1) //主轴方向自适应 } } ``` ### 14.3 @Builder 封装的UI界面 ArkUl还提供了一种更轻量的UI元素复用机制 @Builder ,可以将重复使用的U1元素抽象成一个方法,在 build 方法里调用。 减少写公共组件和样式组件 而是直接封装了 - 全局定义使用 ```js //定义 @Builder function 函数名Builder(){} //使用 函数名Builder() //第一种demo 直接抛 @Builder export function TestBuilder(){ Text('hello')//假设这里面封装了弹窗 } import {TestBuilder} from "路径" //第二种demo通过WrappedBuilder包裹抛 @Builder function TestBuilder(value:string,size:number){ //假设这里面封装了弹窗 } export const globalTestBuilder:WrappedBuilder<[string,number(注意:没有传参可以不用写)]> = wrapBuilder(TestBuilder) //使用 import {globalTestBuilder} from "路径" globalTestBuilder.builder(实参1,实参2) ``` - 组件内定义使用 ```ts //定义 和全局比较去掉function @Builder 函数名Builder(){} //使用 函数名Builder() //demo struct Login{ @Builder IptBuiler(placeholder:string,field:'phone'|'code'){ TextInput({ placeholder: placeholder, //text: (this.formData as Record<'phone'|'code',string>)[field] 第一种使用强制断言写法 text: }).iptStyles(0) } build(){ this.IptBuiler('请输入手机号','phone') this.IptBuiler('请输入验证码','code') Text('这是局部的') } } ``` - 引用传递 调用@Buider装饰的函数默认按值传递。当传递的参数为状态变量时,状态变量的改变不会引起@Buider方法内的UI剧新。所以当使用状态变量的时候,推荐使用按引用传递。 ```js //定义类型 interface ObjType{ num:number } struct Index{ @State num:number = 1 @Builder NumBuilder(obj:ObjType){ //假设这块代码超多 //Text('num'+this.num) @Builder内部直接使用响应式状态没事 //Text('num' + data) //如果这样使用 理论上没问题 【实际上响应式无效】 Text('num' + obj.num) //对象是引用类型 借用引用类型的机制处理 //假设这块代码超多 } build(){ Column(){ //this.NumBuilder(this.num) //需要用到对象类型传参 this.NumBuilder({num:this.num}) Button('num++').onClick(()=>{ this.num++ }) } } } ``` ### 14.4 @Watch ```js struct Index{ @State @Watch('自定义方法名onNumUpldate') num:number = 1 //注意这里不能写箭头函数 onNumUpldate(){ } build(){ Counter(){ Text(this.num.toString()) } .margin(100) .onInc(()=>{ this.num++ }) .onDec(()=>{ this.num-- }) } } ``` ## 十五、forEach ```js //原来的forEach 数组.forEach(()=>{}) //鸿蒙的forEach 1.注意鸿蒙ForEach是大驼峰 2.视图层也就是build直接写ForEach ForEach(数组,(item,i)=>{},) ``` ## 十六、组件状态共享 ### 16.1 场景 圣旨:只要被调用就是子组件(公共组件/逻辑组件 必定都是子组件)。 圣旨:子组件写的数据 就是父传子。 不同父调用NavBar子组件所显示的内容不一样,父得传递数据给子也就是组件通信。 ### 16.2 父子单向 ```js /** * 细节1:api9的时候 @Prop不可以设置默认值,api2的时候可以了 * 细节2:父变化会同步子,子变化不会同步父 * 细节3:鸿蒙非常骚气,不加修饰符(@Prop)也能接受数据,但是失去响应式, */ 例如: //定义子组件 @Component struct Child{ //此时这边通过@Prop 需要接收父穿过来 @Prop title:string = "也可以设置默认值" build(){ Column(){ Text('子' + this.title) Child({ //传递的名字:数据 }) } } } //定义父组件 @Entry @Component struct Demo{ private title:string = "首页" build(){ Column(){ Text('父') Child({ //传递的名字:数据 title:this.title }) } } } ``` ### 16.3 父子双向 ```js // @Prop单向 父变子变 子变【父不变】 // @Link双向父变子变 子变【父变】 /* 子组件中被@Link装饰的变量 / 状态与其父组件中对应的数据源建立双向数据绑定(子可以修改父的数据,并且父同步) 步骤1:父组件传值的时候需要 this.改成$ 步骤2:子组件 @Link 修饰数据 */ /* 1.当装饰的数据类型为boolean、string、number类型时,可以同步观察到数值的变化,如果是对象或者数组则只能观察到第一层修改的变化。 2.类型必须被指定,且和双向绑定状态变量的类型相同。 3.不支持any,不支持简单类型和复杂类型的联合类型,不允许使用undefined和null。不支持Length、Resourcestr、Resourcecolor类型ResourceStr、ResourceColor为简单类型和复杂类型的联合类型 4.不允许初始化,装饰后为必传参数 5.传值的时候需要加上$。 6.只能从父组件中@State、 @Link、 @prop、 @provide、 @Consume、 @objectlink, @storageLink、 @storageProp、 @Localstoragel @LocalStorageProp装饰变量初始化子组件 */ 例如: //定义子组件 @Component struct Child{ //此时这边通过@Link 需要接收父穿过来 不能设置默认值 @Link title:string build(){ Column(){ Text('子' + this.title) Child({ //传递的名字:数据 }) } } } //定义父组件 @Entry @Component struct Demo{ private title:string = "首页" build(){ Column(){ Text('父') Child({ //传递的名字:数据 title:$title }) } } } ``` ### 16.4 后代 ```js 例如 //定义孙子组件 @Component struct sun{ //通过@Consume直接接收demo组件传递的数据 并且是双向响应式的数据,不能设置默认值 @Consume num:number; build(){ Column(){ Text('孙') } } } //定义子组件 @Component struct child{ build(){ Column(){ Text('子') sun() } } } //定义父组件 @Entry @Component struct Demo{ //通过@Provide 定义的数据 可以直接传到sun组件中 @Provide num:number = 1 build(){ Column(){ Text('父'+ this.num) Child() } } } ``` ## 十七、@BuilderParam 传递UI ### 17.1尾随闭包 ```js 例如: //封装一个公共的带图标的button组件 @Component export struct Button2{ //接收父组件传递过来的button文字 @Prop title = "" //接收父组件传递过来的UI标签内容 @BuilderParam DefaultContent(注:这个名字随便写):()=>void = this.DefaultContentBuild(注:设置默认值) @Build DefaultContentBuild(){ Text("UI默认值") } build(){ Row(){ //使用传递过来的ui内容 this.DefaultContent() Text(this.title) } } } //定义父组件 @Entry @Component struct Index{ build(){ //使用封装好的按钮组件 Button2({title:"立即创建"}){ //花括号里面就是尾随闭包 届时图片的image就会传递到@BuilderParam DefaultContent方法里 image("加号图片") } } } ``` ### 17.2 具名 指定具体的名字 传递UI内容 ```js //定义一个组件 @Component struct NavBar{ //接收父组件传递过来的值 @BuildParam Left:()=>void private title:string = "" @Builder RightBuilder(){ Text('默认值') } //如果没有传 直接使用默认值 @BuildParam Right:()=>void = this.RightBuilder build(){ Row(){ //调用 this.Left() Text(this.title) this.Right() } } } @Entry @Component struct Index{ //定义传给NavBar组件的值 @Builder LeftBuilder(){ Text('左') } @Builder RightBuilder(){ Text('右') } build(){ Column{ NavBar({ Left:this.LeftBuilder, title:"首页", //Right:this.RightBuilder //如果这里不传 }) } } } ``` ## 十八、路由 ### router路由配置 #### 18.1 路由配置 resource/base/profile/main_pages.json 文件中 ```js { "src":[ "pages/Index", "pages/Cart" ] } //细节1:路由配置在json文件中 所以必须写 双引号,数组的最后一个不能写逗号 //细节2:咱们后面写代码目录统一小写,组件名大写 ``` #### 18.2 路由跳转 页面栈的最大容量为32个页面。如果超过这个限制,可以调用router.clear()方法清空历史页面栈,释放内存空间。 Router模块提供了两种跳转模式,分别是router.pushUrl()和router.replaceUrl()。这两种模式决定了目标页是否会替换当前页 router.pushUrl 保存当前页面跳转 ```js import {router} from "@kit.ArkUI" Button("登录").onClick(()=>{ router.pushUrl({ url:"pages/Index",//要跳转的路径 params:{},//要携带的参数 }) }) ``` router.replaceUrl 关闭当前页面跳转 ```js import { router } from "@kit.ArkUI" Button("登录").onclick(()=>{ router.replaceUrl({ url:"pages/Index",//要跳转的路径 params:{id:'111'},//要携带的参数 }) }) ``` 注:也可以通过 new NavPathStack() 创建页面栈进行跳转 #### 18.3 接收跳转传参 ```js //通过aboutToAppear 接收路由跳转过来的参数 import { router } from "@kit.ArkUI" interface RouterParams{ id:string } aboutToAppear(){ const params = router.getParams() as RouterParams //通过getParams获取传递过来的参数 console.log(JSON.stringify(params)) //注意这里真机才能打印出来 console.log(params.id) //这时候模拟器可以打印出来 } ``` #### 18.4 跳转前增加页面弹窗 ```js Button("返回上一页").onClick(()=>{ router.showAlertBeforeBackPage({ message:"是否返回上一页,确定要返回吗" }) /** 此时用户点击了返回才可以返回上一页 **/ //返回上一页 router.back() }) ``` #### 18.5 实例模式 ```js //需要页面栈只有一份 就单实例 否则默认 //需要返回pushUrl否则replaceUrl Standard(多实例) //标准实例模式,也是默认情况下的实例模式。每次调用该方法都会新建一个目标页,并压入栈顶 //比如页面有1页面,2页面 3页面 1跳到2在跳到3在跳到2就会新创建一个2页面栈在3页面的上面 Single(单实例) //单实例模式。即如果目标页的ur!在页面栈中已经存在同ur1页面,则离栈顶最近的同url页面会被移动到栈顶,并重新加载;如果目标页的ur在页面栈中不存在同url页面,则按照标准模式跳转。 //比如页面有1页面,2页面 3页面 1跳到2在跳到3在跳到2 就会把之前的2页面移到3页面的上面不会新建页面栈 //语法: router.pushUrl({ url:"pages/Index",//要跳转的路径 },router.RouterMode.Standard)//默认就是此模式 ``` ### **Navigation (官方推荐的组件导航)** * #### 系统路由表 ```js 系统路由表是动态路由的一种实现方式。从API version 12开始,Navigation支持使用系统路由表的方式进行动态路由。各业务模块(HSP/HAR)中需要独立配置route_map.json文件,在触发路由跳转时,应用只需要通过NavPathStack提供的路由方法,传入需要路由的页面配置名称,此时系统会自动完成路由模块的动态加载、页面组件构建,并完成路由跳转,从而实现了开发层面的模块解耦。系统路由表支持模拟器但不支持预览器。其主要步骤如下: ``` #### 1. 在配置文件module.json5添加路由表配置: ```js { "module" : { "routerMap": "$profile:route_map",//添加路由配置地址 } } ``` #### 2.添加如下配置信息 ```js //加完路由配置文件地址后,需要在resources/base/profile中创建route_map.json文件 { "routerMap": [ { "name": "zhuye",//页面需要跳转的名字 "pageSourceFile": "src/main/ets/pages/zhuye.ets",//对应名字绑定文件的地址 "buildFunction": "zhuyeBuilder",//对应文件页面入口函数的builder对应是哪个 "data": { "description" : "主页页面",//描述 } } ] } ``` #### 3. Navigation 页面跳转流程 ```js //子页面 //暂时没有 /*@Builder export function ziyeBuilder(){ ziye() } @Component struct ziye{ //创建一个页面栈对象并传入Navigation //pathStack变量就是控制跳转的对象 pathStack: NavPathStack = new NavPathStack() build(){ } }*/ //这里其实可以理解为 Index为启动页 ————>跳转到 zhuye里面 //主页 @Builder export function zhuyeBuilder(){ zhuye() } @Component struct zhuye{ //创建一个页面栈对象并传入Navigation //pathStack变量就是控制跳转的对象 pathStack: NavPathStack = new NavPathStack() build(){ NavDestination(){ //代码放在这里面 }.onReady((context:NavDestinationContext)=>{ this.pathStack = context.pathStack }) } } //首页 @entry @Component struct Index(){ //创建一个页面栈对象并传入Navigation //pathStack变量就是控制跳转的对象 pathStack: NavPathStack = new NavPathStack() build(){ Navigation(this.pathStack).onAppear(()=>{ //页面要显示的时候 //进行跳转 this.pathStack.pushPathByName("zhuye",null,false) }).hideNavBar(true)//不会把自己放到控制跳转对象里面 -> 点返回的时候不会返回到这个页面 } } //onAppear是一个生命周期 此外:NavDestination的onAppear,onDisAppear,onShown,onHidden,onWillAppear,onWillDisappear,onWillShow,onWillHide接口的生命周期时序 ``` #### 4.Navigation 缺点 ```js 1. 过度依赖了架包,耦合性很高 ``` ## 十九、生命周期 组件和页面在创建、显示、销毁的这一整个过程中,会自动调用一系列的函数(术语生命周期钩子),让开发者有机会在特定的阶段运行自己的代码;例如组件显示过程中请求接口获取数据,组件销毁时释放内存避免内存泄漏等。 - 鸿蒙项目在运行过程中产生的数据创建、数据销毁就是生命周期 - 生命周期函数 =》也就是上述过程中调用的函数 页面生命周期,即被@Entry装饰的组件生命周期,提供以下生命周期接口 - onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景。 - onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景 - onBackPress:当用户点击返回按钮时触发。 组件生命周期,即一般用@Component装饰的自定义组件的生命周期,提供以下生命周期接口 - aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行。 - onDidBuild:组件build()函数执行完成之后回调该接口,开发者可以在这个阶段进行埋点数据上报等不影响实际UI的功能。不建议在onDidBuild函数中更改状态变量、使用animateTo等功能,这可能会导致不稳定的UI表现。 - aboutToDisappear:aboutToDisappear函数在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量特别是@Link变量的修改可能会导致应用程序行为不稳定。 生命周期 生命周期详解 ## 二十、网络请求 ### 20.1 申请网络权限 在进行网络请求前,您需要在module.json5文件中申明网络访问权限 ```js { "module":{ "requestPermissions":[ "name":"ohos.permission.INTERNET" ] } } //应用访问网络需要申请ohos,permission.INTERNET权限,因为和谐操作系统提供了一种访问控制机制即应用权限,用来保证这些数据或功能不会被不当或恶意使用。 ``` ### 20.2 发送网络请求步骤 - 导入http模块 ```js import http from "@ohos.net.http" ``` - 创建网络请求对象 ```js const httpRequest = http.createHttp() ``` - 调用request函数发送请求 ```js httpRequest.request( 请求地址?参数名=数据&....,//当get请求用来传递参数 { methods:http.RequestMethod.GET / POST, header:{//配置请求头 }, extraData:{ //当post请求用 param1:"111", param1:"222" } } ).then(res=>{ if(res.responseCode==200){ console.log(res.result) } }) //细节1:95% 都可以在preview里面直接用,但是5%必须模拟器用(旧版本DevEco编辑器不适配苹果ARM架构) //细节2:网络请求和图片一样 模拟器必须配置权限 ``` ### 20.3 封装网络请求 工作中95%不用 ```js //引入http模块 import {http} from "@kit.NetworkKit" //定义公共地址 const baseURL = "" //封装get请求 export const get = (path:string):Promise=>{ //创建httpRequest对象 const httpRequest = http.createHttp() //调用request函数 return httpRequest.request( //这里的return 返回的是.then执行完后的结果 baseURL+path, { method:http.RequestMethod.GET, } ).then(res=>{ return JSON.parse(res.result as string) as T }) } //封装post请求 export const post = (path:string,data:object):Promise =>{ //创建httpRequest对象 const httpRequest = http.createHttp() return httpRequest.request( //这里的return 返回的是.then执行完后的结果 baseURL+path, { header:{ "content-type":"application/json" }, method:http.RequestMethod.POST, extraData:data } ).then(res=>{ return JSON.parse(res.result as string) as T }) } //使用 //定义类型 interface PostSmsPayLoadType{ mobile:string } Button("发送短信").onClick(async()=>{ const res = await post('请求地址',{mobile:""} as PostSmsPayLoadType) console.log(JSON.stringify(res)) }) ``` ## 二十一 弹窗的多种展示形态 ### 21.1模态、非模态 模态是有交互的,非模态会自动消失的。 【模态】一词是振动力学中的概念,应用在交互中模态是中断主目标流程/穿插在主目标流程中,做一个决定或完成一个任务的一种方式,模态弹窗就是当我们需要用户聚焦与一个任务时的弹出内容。模态弹窗出现在用户的操作视野内时,禁用所有背景内容,并需要用户对弹窗作出回应/判断,所以禁用父级内容和强制交互是模态弹窗的特征。模态弹窗有着引人注意的优势,所以它一般用来承载层级高的信息,但强聚焦能力同时带来强打扰的不良体验,所以要结合场景慎重使用。 模态弹窗常见种类有:对话框(Dialog)/警示框(Alter)、活动面板(Activity views、Action Sheets、DrawerLayou..)、气泡弹窗(Popover/Popup)。 非模态】在特征上则与模态相反,非模态弹窗一般不需要用户操作,具有时效性,可在出现并停留一定时间后自主消失。非模态弹窗对用户的打扰较小,所以它常用与轻量级的反馈机制下,通过获取用户短暂关注来进行反馈和提醒。常见的种类有Hud/Toast,提示栏和Snackbar。 业务需求中常提到的弹窗,从业务目标“减少跳出,完成某任务"来分析指的是模态弹窗,接下来主要分析一下模态弹窗的种类和分别适用与哪些场景。我们用形态区分一下常见的几种模态弹窗 ```js 文本提示框(promptAction.showToast) 对话框(promptAction.showDialog) 自定义弹框(promptAction.openCustomDialog/closeCustomDialog) 默认弹窗:AlertDialog警告弹窗 自定义弹窗:@CustomDialog自定义弹窗不推荐 菜单Menu 气泡Popup 日历选择器弹窗(CalendarPickerDialog) 日期滑动选择器弹窗(DatePickerDialog) 时间滑动选择器弹窗(TimePickerDialog) 文本滑动选择器弹窗(TextPickerDialog) 列表选择弹窗(ActionSheet) ``` ### 21.2 非模态弹窗 ```js promptAction.showToast({ message:"消息内容", duration:3000 ,//(注:显示时效,number类型,单位:毫秒), bottom:"50%",//弹窗显示的位置 backgroundColor:Color.Black,//设置弹窗的背景颜色 backgroundBlurStyle:BlurStyle.NONE,//去掉弹窗的模糊度 textColor:Color.white,//设置弹窗文字的颜色 }) ``` ### 21.3模态框 CustomDialog 侵入式(基础自定义弹窗*管方不推荐) ```js //1写弹窗组件 =>装饰器使用 @CustomDialog //2在页面去实例化弹窗 new CustomDialogController({builder:弹窗组件名字(),}) //3通过open展示、close关闭 //语法 //定义弹窗组件 @CustomDialog struct DeleteDialog{ private controller?:CustomDialogCotroller //?是指不是双向绑定的时候,没有默认值用?代替 build(){ //弹窗布局 Row(){ Button('确定').onClick(()=>{ this.controller?.close() //关闭弹窗 }) Button("取消").onClick(()=>{ this.controller?.close() //关闭弹窗 }) } } } //页面组件 @Entry @Component struct Demo{ //使用上面定义的弹窗{}外面都是固定语法 private diadio:CustomDialogController =new CustomDialogController({ build:DeleteDialog(*这里面也可以传参*),//使用上面定义的弹窗ui组件 }) build(){ Button("弹出").onClick(()=>{ this.diadio.open()//打开弹窗 }) } } ``` ### 21.4 openCustomDialog 第一种不常用的写法 ```js import { promptAction }from'kit.ArkUI @Entry @Component struct Demo { private dialogId:number = 0 //定义弹窗内容 @Builder DeleteDiadioBuilder(){ Column(){ text("确定删除吗") Row(){ Button('确定').onClick(()=>{ promptAction.closeCustomDialog(this.dialogId) //关闭弹窗 }) Button("取消").onClick(()=>{ promptAction.closeCustomDialog(this.dialogId) //关闭弹窗 }) } } } build(){ Button('弹出').onclick(()=> { promptAction.openCustomDialog({ builder:()=>{ this.DeleteDiadioBuilder() //使用弹出内容 } }).then((dialogId:number)=>{ this.dialogId = dialogId }) }) } } ``` 第二种写法 ```js import {ComponentContent} from "@kit.ArkUI" //定义params类型 interface DeleteBuilderType{ ok:()=>void, cancel:()=>void } @builder function DeleteBuilder(params:DeleteBuilderType){ //params 接收传递过来的 ok和cancel参数 text("确定删除吗") Row(){ Button('确定').onClick(()=>params.ok()) Button("取消").onClick(()=>params.cancel) } } @Entry @Component struct Demo{ private contentNode:ComponentContent = new ComponentContent(this.getUIContext(),wrapBuilder(DeleteBuilder),{ //DeleteBuilder 需要传递给组件的参数 ok:()=>{ this.getUIContext().getPropmptAction().closeCustomDialog(this.contentNode) //关闭弹窗 }, cancel:()=>{ this.getUIContext().getPropmptAction().closeCustomDialog(this.contentNode) //关闭弹窗 } } as DeleteBuilderType) build(){ Button('打开').onClick(()=>{ //this.getUIContext 获取当前环境上下文 this.getUIContext().getPropmptAction().openCustomDialog(this.contentNode,{ autoCancel:false,//禁止点击弹窗空白区域关闭 }) }) } } ``` ## 二十二布局容器 ### 22.1 线性布局 Column:子元素在垂直方向依次排列 Row:子元素在水平方向依次排列 间距: ```js 在布局容器内,可以通过space属性设置排列方向上子元素的间距,使各子元素在排列方向上有等间距效果。 ``` ### 22.2弹性布局 ```js @Entry @Component struct Demo{ build(){ Flex({//设置flex布局参数 direction:FlexDirection.Column,//设置垂直排列 默认是横向排列 justifyContent:FlexAlign.SpaceBetween,//设置 横向排列对齐方式 wrap:FlexWrap.Wrap,//设置换行 alignItems:ItemAlign.Center,//垂直居中 }){ Text().width(200).height(100).backgroundColor(Color.pink) Text().width(200).height(100).backgroundColor(Color.pink).alignSelf(ItemAlign.Center)//设置单独的元素居中 Text().width(200).height(100).backgroundColor(Color.pink) } } } ``` ### 22.3 层叠布局 Stack 层叠布局(StackLayout)用于在屏幕上预留一块区域来显示组件中的元素,提供元素可以重叠的布局。层叠布局通过Stack容器组件实现位置的固定定位与层叠,容器中的子元素(子组件)依次入栈,后一个子元素覆盖前一个子元素,子元素可以叠加,也可以设置位置。 zIndex Stack容器中兄弟组件显示层级关系可以通过Z序控制的zindex属性改变。zindex值越大,显示层级越高,即zindex值大的组件会覆盖在zIndex值小的组件上方。一般来说组件的默认层级都是1,可以直接使用一些交大的数去尝试改变层级。类似前端的z-index属性。 ```js Stack({alignContent:对齐方式}){ Column(){}.width('90%').height('100%').backgroundColor(Color.Red) Botton('按钮').width(100).height(100).backgroundColor(Color.Green).zIndex(2) text("文字").width(200).height(200).backgroundColor(Color.yellow) } /* 执行完成后: 黄色覆盖绿色 绿色覆盖红色 下面的覆盖上面的 如果想让绿色在黄色上面可以使用zIndex(值) */ ``` ## 二十三 图标库 ### 使用系统图标 - **`$r('sys.symbol.arrow_up_left_and_arrow_down_right')`**:`$r` 函数用于访问资源,这里访问系统资源中的 `arrow_up_left_and_arrow_down_right` 图标。 - **`SymbolSpan()`**:用于显示符号或图标,`$r` 函数返回的图标资源将会作为内容插入。 - **`sys`**: 通常代表系统资源的根命名空间,里面包含了系统提供的各种资源,比如图标、字符串、颜色等 - **`symbol`**: 在系统资源命名空间 `sys` 下,`symbol` 专门用来存放图标或符号。你可以通过 `sys.symbol` 访问这些内置符号和图标。 - fontSize():设置icon图标的大小。 ```js Text(){ SymbolSpan($r('sys.symbol.circle_righthalf_inset_filled')) .fontSize(20) } ``` ### 使用自定义图标 ```js ``` ## 补充知识 ### 1. 单位 ```js 鸿蒙中默认是vp单位 Row(){}.width("100vp") 只有鸿蒙中的文字单位是fp //例如 text("111").fongSize("20fp") //fp和vp是一样的都是相对单位 //而px是绝对单位 ``` ### 2.打包上线 ```js //第一步 登录官方网址:https://developer.huawei.com/consumer/cn/service/josp/agc/index.html#/ //第二步 选择开发与服务,选择添加项目 //选择添加应用(这块地方不能乱写的) 这时*应用名称*对应的是 项目根目录下AppScope->app.json5->label属性 这时的*应用包名*对应的是 项目根目录下AppScope->app.json5->bundleName属性 //第三步生成密钥和证书文件 3.1 打开DevEco studio开发软件->选择构建->选择生成密钥和证书请求文件 3.2 点击new->选择文件夹(底部要填写自定义文件名字)->填写证书的密码(要求8位)->确认密码->点击ok 3.3 填写key下面的Alias(key名)这里的名字可以自定义 3.4 剩下的需要填写城市信息等等(只要填上就行)->点击next 3.5 选择CSR文件存储位置(底部要填写自定义文件名字)->点击确定->finish //第四步 4.1 回到官方网址:https://developer.huawei.com/consumer/cn/service/josp/agc/index.html#/ 4.2 选择证书、APPID和Profile->新增证书->填写证书名称、选择发布证书、选择刚刚生成的csr文件->点击提交->点击下载证书 //第五步 5.1 回到官方网址:https://developer.huawei.com/consumer/cn/service/josp/agc/index.html#/ 5.2 选择我的项目 选择左侧导航的全部功能->HarmonyOs应用(HAP Provision Profile管理)->选择添加->填写对应的选择->选择右上角添加->点击下载Profile证书 //第六步 6.1 打开DevEco studio开发软件->选择文件->选择项目结构->选择Signing Configs(签名配置)->只勾选Support HarmonyOS、选择P12证书、填写密码、填写KEY、填写确认密码、选择对应的p7b证书、选择对应cer证书 //第七步 7.1 打开DevEco studio开发软件->构建->选择编译->选择编译App(s) 7.2 这时打包的文件就会在项目根目录下 build->outputs->default hmSmartFarm-default-signed.app(有签名的app) hmSmartFarm-default-unsigned(无签名的app) //第八步 8.1 回到官方网址:https://developer.huawei.com/consumer/cn/service/josp/agc/index.html#/ 8.2 选择我的应用->点击新建->选择刚刚创建的项目->点击保存 8.3 这时候会来到我的应用->应用信息->填写应用logo(尺寸216*216px)选择应用分类->点击保存->点击下一步 8.4 这时候来到版本信息->填写应用介绍、应用的一句话简介、应用的截图(尺寸1280*720,至少三张) //来源于地址https://www.bilibili.com/video/BV1uN4y1q7ga/?spm_id_from=333.337.search-card.all.click&vd_source=93a54ab75a390b73900f3758f749ee88 ```