Harmoney北向开发入门篇(一)

相关链接

开发者文档中心 | 应用推广/应用接入和技术支持 | 华为开发者联盟

华为开发者学堂

hdc使用指导

安卓开发转鸿蒙开发到底有多简单? - 掘金

黑马-HarmonyOS4课程_哔哩哔哩_bilibili

初识鸿蒙开发

了解鸿蒙开发套件

鸿蒙开发套件由七个主要组件组成。

  1. HarmonyOS Design:视觉设计。
  2. ArkTs:项目开发语言。
  3. ArkUI:一套UI框架。
  4. ArkCompiler:AOT优化,优化字节码编译,提高运行效率。
  5. DevEco Studio:开发编译器;IDEA。
  6. DevEco Testing:面向测试人员的开发工具。
  7. AppGallery Connect:分发运营套件,提供了很多的云函数、云数据库功能。

整个鸿蒙应用的开发步骤分为三步:

  1. 准备和设计。
  2. 代码开发。
  3. 测试和上架。

而我们作为开发人员,主要接触的是DevEco Studio编译器、ArkTs开发语言、ArkUI框架。

DevEco Studio安装

DevEco 的初始化安装,主要需要三个东西:nodejs、OHPM包管理器、HarmonyOS SDK。

鸿蒙的编译器DevEco Studio,需要使用管理员身份执行,不然安装sdk可能会失败。

确保最终是全绿,才可以进行下一步的项目创建

踩坑

公司电脑加密系统也会导致DevEco Studio开发环境配置不成功。比如 ohpm卡安装

解决ohpm卡安装问题

OpenHarmony Ohpm安装历程(个人踩坑,最后安装成功)-CSDN博客

配置OHPM代理

设置Ohpm代理

找到FIle->Settings->Ohpm,点击Optimize config。

弹出以下弹窗:

ohpm registry :配置ohpm仓的地址信息,repo.harmonyos.com/ohpm/

如果需要设置网络代理,那么可以勾选HTTP proxy并进行配置。

关闭Windows defender

关闭defender,或者将devEco相关内容添加到排除项

运行首个项目

  1. 创建项目

创建首个项目,选择Create Project->Empty Ability,由此可知Ability等于安卓的Activity。

  1. 模拟器的配置

(和Android studio其实差不大多,摸索就行),DevEco Studio支持Remote 云控模拟器,还是很有意思的。

  1. 运行项目

点击run运行,helloworld就有了

了解ArkTS

以前学习的HTML网页开发,它包含了H5+CSS+JavaScript,从而实现了一个完整的网页结构。

那么也就意味着如果要掌握网页开发,需要同时掌握HTML语法、CSS语法、JavaScript语法,需要掌握三样内容。

而ArkTS相比起H5来说则更加容易,只需要掌握ArkTS这一门语言就可以完成UI和交互逻辑的开发工作了。

ArkTS与Typescript

  • ArkTS基于Typescript,而Typescript基于JavaScript;Typescript相比较于JavaScript新增了静态类型定义功能;所以ArkTS也具有Typescript的这些特性。
  • ArkTS可以理解为等同于网页的JavaScript。同时,ArkTS又拓展了声明式UI、状态管理等特性。

ArkTS的优点

  • 开发效率高、开发体验好。
  • 性能优越。
  • 有多系统适配,接入能力。

ArkTS虽然使用的是声明式UI,但是它底层有个方舟编译器,方舟编译器会帮我们把写的TS语言编译成字节码,最终转为机器码。而且还会把 字节码转换为机器码的 动作,从运行器提前到编译期,从而大大提高运行效率。

以及还有一套UI后端引擎,提供了一套统一的页面渲染的指令,这些指令最后提供给渲染总线,最终渲染总线将指令传递给了高效渲染引擎;高效渲染引擎会对我们的UI指令再进行一层的优化,大大提高页面渲染效率。

同时鸿蒙系统底层对ArkTS提供了不同平台的适配层和桥接层。

因此,ArkTS开发的鸿蒙应用,不仅简单,性能也很好,而且具有跨系统适配能力。

Typescript基础语法

Typescript演练场

访问Typescript官网,点击Playground,可以进入到Typescript演练场、,在此处可以练习Typescript语法。

变量声明

  • Typescript在JavaScript的基础上加入了静态类型检查功能,因此每一个变量都有固定的数据类型。
csharp 复制代码
let msg:string='hello world'//声明一个string类型的msg变量。 

let:声明变量的关键帧。也可以使用const,表示常量。

msg:变量名。

string:变量的数据类型;Typescript相较于JavaScript就是多了这一部分变量的数据类型声明。

'hello world':赋的值;可以使用单引号',也可以使用双引号""。

数据类型

  • string:字符串
csharp 复制代码
let msg:string='hello world'
    • 字符串拼接时,通过使用 符号,可以开启 ${} (单/双引号下不支持)。
ini 复制代码
const anExampleVariable = "Hello World"
console.log(`输出anExampleVariable=${anExampleVariable}`)
//打印结果:输出anExampleVariable=Hello World
  • number:数值类型;不区分整数、浮点数,都使用number。
ini 复制代码
let age:number=18
  • boolean:布尔类型
ini 复制代码
let finished:boolean=true
  • any:不确定类型,可以是任意的类型。
ini 复制代码
let a:any='jack'
a=21;
  • union:联合类型,可以是多个指定类型的一种。
ini 复制代码
//联合类型U,可以是string,也可以number、boolean;它们的数据类型可以自由切换。
let u:string|number|boolean="union";
u=100;
  • object:对象
css 复制代码
// 声明了一个对象,类别两个变量name,age。它们的值对应 name为jack,age为24.
let p={name:'jack',age:24}
//在控制台输出对象p的name属性。
console.log(p.name);
console.log(p['name']);
  • Array:数组;内部的元素可以是任意类型。
typescript 复制代码
let names:Array<string>=['Jack','Rose']
let ages:number[]=[20,21]
console.log(names[0])
console.log(ages[0].toString())

注意:

  • 开发时通常是在函数中会用到any、union类型。
  • 声明普通变量时,尽可能不使用any。

条件控制

Typescript和大多数语言类似,都是支持if-else、switch 条件控制语句的。

typescript 复制代码
let num:number=88;
//判断奇偶数
if(num%2===0){
  console.log("是偶数!")
}else{
  console.log("是奇数!")
}

//判断是否正数
if(num>0){
  console.log("是正数!")
}else if(num<0){
  console.log("是负数!")
}else{
  console.log("是0!")
}

[LOG]: "是偶数!" 
[LOG]: "是正数!" 
c 复制代码
let grade:string="A";
switch(grade){
    case "A" :
        console.log("优秀")
        break
    case "B":
        console.log("及格")
        break
    case "C":
        console.log("不及格")
        break
    default:
        console.log("非法输入")
        break
}
[LOG]: "优秀" 

注意:

  • 在Typescript中,空字符串、数字0、null、undefind都被认为是false,其他值则为true。

比较运算符:== 与 ===

  • 在Typescript中,比较运算符 '=='(相等运算符) 和 '==='(严格相等运算符) 都可以用于比较两个值是否相等,但是==如果比较的两个值类型不同,那么它会主动的对数据类型进行强制转换为相同的类型后再比较,这样的话比较损耗性能。
    所以建议优先使用 === 严格相等运算符。

循环迭代

Typescript支持for和while循环,并且为一些内置类型如Array等提供了快捷迭代语法。

  • for:常规for循环。
  • while:常规while循环。
  • for in 遍历:得到数组索引角标。
  • for of遍历:得到数组元素。
typescript 复制代码
//for循环
for(let i=0;i<10;i++){
  //输出0-9
  console.log("for循环,i="+i)
}
//while循环
let i=0;
while(i<10){
  //输出0-9
  console.log("while循环,i="+i)
  i++;
}

let list:Array<string>=['A','B','C','D','E']
//for in 迭代器,遍历得到数组角标索引。
for(const i in list){
  //输出数组角标
  console.log("for in 迭代获取list角标,i="+i)
  //输出数组的元素内容
  console.log("for in 迭代打印数组内容,第"+i+"个为"+list[i])
}
//for of 迭代器,遍历得到数组元素
for(const item of list){
  //输出数组内的元素内容
  console.log("for of 迭代打印list数组内容, item="+item)
}

函数

Typescript通常利用function关键字声明函数,并且支持可选参数、默认参数、箭头函数等特殊语法。

  • 无返回值函数
  • 有返回值函数
  • 箭头函数
    语法:let 函数名 =(参数名:参数类型)=>{ ... } 代码块内部可以 return 返回任意类型的数据。
typescript 复制代码
//无返回值函数,返回值void可以省略。
function sayHello(name:string):void{//:void 表示无返回值,可省略。
    console.log("你好,"+name)
}
sayHello("张三")

//有返回值的函数
function sum(x:number,y:number):number{
    return x+y;
}
console.log("1+1="+sum(1,1));

//箭头函数
let sayHi=(name:string)=>{
    console.log("你好,"+name)
}
sayHi("李四")

可选参数

可选参数,表示函数在被调用时,可选参数是可以不传值的。

scss 复制代码
//可选参数,在参数名后面添加?符号,表示该参数是可选的
function sayHello(name?:string){
    console.log(name)//输出:undefined
    //检查是否为name参数传入值
    name=name?name:"陌生人"
    console.log("你好,"+name)//输出:"你好,陌生人" 
}
sayHello()

默认参数

默认参数,表示参数具有默认值,如果调用时该参数未传值,则使用默认值。

c 复制代码
//默认参数,在参数类型后面添加  ="默认值" 
function sayHello(name:string="陌生人"){
    console.log(name)//输出:陌生人
    //检查是否为name参数传入值
    name=name?name:"陌生人"
    console.log("你好,"+name)//输出:你好,陌生人
}
sayHello()

类和接口

Typescript具备面向对象编程的基本语法,例如interface、class、enum等。也具备封装、继承、多态等面向对象基本特征。

php 复制代码
//定义枚举
enum Msg{
  HI="Hi",
  HELLO="Hello",
}

//定义接口
interface A{
  say(msg:Msg):void
}

//实现接口
class B implements A{
  say(msg:Msg):void{
    console.log(msg+",I'm B")
  }
}

//创建B的实例对象
let b:B=new B();
b.say(Msg.HELLO)
//输出:"Hello,I'm B" 

总结:

  • 枚举:
    • 枚举支持赋值(支持任意类型,不需要声明),如果不赋值,则默认从0开始,按照顺序每个枚举值默认+1。
    • 如果枚举中有一个成员定义了值,那么其他枚举元素也必须要定义值,无法再使用默认的值。
    • 枚举中每个成员的数据类型可以存在不同。
  • 在接口中定义的函数、类中实现的接口函数中,函数不需要添加function关键字。
  • Typescript中对象的创建是通过new关键字来实现的。

typescript 复制代码
//定义矩形类
class Rectangle{
  //成员变量
  //定义类中成员变量时,不需要添加let关键字。
  //private关键字表示当前成员变量为私有的。成员变量默认的访问权限是public。
  private mWidth:number
  private mLength:number

  //构造函数
  constructor(width:number,length:number){
    //在函数中操作成员变量时,必须用 this 去获取变量。不使用this的话就无法操作成员变量。
    this.mWidth=width
    this.mLength=length
  }

  //成员函数
  public area():number{
    return this.mWidth+this.mLength
  }
}

//定义正方形类
class Square extends Rectangle{
  //定义构造函数
  constructor(side:number){
    //调用父类的构造函数
    super(side,side)
  }
}

let sq=new Square(5)
console.log("正方形的面积为:"+sq.area())
//输出:"正方形的面积为:10"

总结:

  • 定义类中的成员变量时,不需要添加let关键字。
  • 在类函数中操作当前类的成员变量时,必须用 this 去获取变量。不使用this的话就无法操作成员变量。、
  • 类的构造函数不需要函数名。
  • 在 TypeScript 中,类的成员(包括属性和方法)的访问权限默认是 public。
    TypeScript中的访问修饰符:public、private、protected
  • extends:继承。
get、set函数

private 修饰的成员变量是受保护的,在类的外面无法进行直接赋值和取值;但是是可以使用 get、set 方法来对 private 修饰的成员变量进行赋值和取值

在Typescript中也有get、set函数;它们的使用方式为:

  • get 函数名():变量类型
  • set 函数名(入参:变量类型)

注意:

  • 使用 get、set方法的成员变量命名时建议在前面加 _
  • get 和 set 方法的名称,建议使用去掉 _ 的成员变量名称
  • 在类外使用时,通过操作get和set修饰的方法名(以对象属性的方式操作),来间接的操作成员变量
csharp 复制代码
export class NameBean{
  private _name:string;//名称
  get name():string{
    return this._name
  }
  set name(setName:string){
    this.name=setName
  }
}

模块开发

当应用复杂时,我们可以把通用功能抽取到单独的TS文件中,每个TS文件都是一个模块(moudle)。模块可以相互加载,提高代码复用性。

在Typescript中,我们可以将想要提供给其他TS文件使用的函数、变量、类进行导出(export),如下代码:

使用export修饰class、 function,从而导出给其他TS文件使用。

typescript 复制代码
//定义矩形类,并使用export导出。
export class Rectangle{
  //成员变量
  public mWidth:number
  public mLength:number
  //构造函数
  constructor(width:number,length:number){
    this.mWidth=width
    this.mLength=length
  }
}

export function area(rec:Rectangle):number{
  return rec.mWidth+rec.mLength
}

在其他模块(TS文件)中,将可导出的功能导入(import)到当前模块内使用:

javascript 复制代码
//通过import语法导入其他TS文件的功能,from后面写文件的地址
//import语句建议放在代码顶部。
import {Rectangle,area} from '../rectangle'

//导入功能后,可以在当前文件内任意地方进行Rectangle、area的调用。
//创建Rectangle对象
let r = new Rectangle(10,20)
//调用area方法
console.log("面积为:"+area(r))

了解Harmony基本工程目录

Ohos项目结构分类

将项目的目录视图,切换到鸿蒙的Ohos视图

  • AppScope中存放应用全局所需要的资源文件。
  • Entry是entry是应用的主模块,存放HarmonyOS应用的代码、资源等。
    • ets文件夹内存放代码编写文件(我们开发主要集中在ets文件夹中)
      • page存放页面 :index.ets是初始页面
    • configuration则存放相应模块配置文件
    • resources对应文件公共资源
  • 最外层的configuration,存放工程应用级的配置文件
    • build-profile.json5是工程级配置信息,包括签名、产品配置等。
    • hvigorfile.ts是工程级编译构建任务脚本,hvigor是基于任务管理机制实现的一款全新的自动化构建工具,主要提供任务注册编排,工程模型管理、配置管理等核心能力。
    • oh-package.json5是工程级依赖配置文件,用于记录引入包的配置信息。
  • 在AppScope,其中有resources文件夹和配置文件app.json5。AppScope > resources> base文件夹中包含element和media两个文件夹
    • 其中element文件夹主要存放公共的字符串、布局文件等资源。
    • media存放全局公共的多媒体资源文件。
  • 将项目切换到 project 视图:oh_modules 是工程的依赖包,存放工程依赖的源文件。

module结构分类

接下来看模块中的结构分类:

  • entry模块下的src目录中主要包含总的main文件夹,单元测试目录ohosTest,以及模块级的配置文件。
  • main文件夹中,ets文件夹用于存放ets代码,resources文件存放模块内的多媒体及布局文件等,module.json5文件为模块的配置文件。
  • ohosTest是单元测试目录。
  • build-profile.json5是模块级 配置信息,包括编译构建配置项
  • hvigorfile.ts文件是模块级构建脚本。
  • oh-package.json5是模块级依赖配置信息文件。
  • 进入src>main>ets目录中,其分为entryability、pages两个文件夹。
    • entryability存放ability文件,用于当前ability应用逻辑和生命周期管理。
    • pages存放UI界面相关代码文件,初始会生成一个Index页面。
  • resources目录下存放模块公共 的多媒体、字符串及布局文件等资源,分别存放在element、media文件夹中。

app.json5

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

其中配置信息如下:

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

module.json5

模块的配置文件,包含当前模块的配置信息。mainElement指定启动首页Ability的名称,abilities中可以声明多个Ability。

deviceTypes:声明了当前模块支持的设备类型,这里是手机phone和平板tablet。

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

我们简单了解以下几个:

  • name:该标签标识当前module的名字,module打包成hap后,表示hap的名称,标签值采用字符串表示(最大长度31个字节),该名称在整个应用要唯一;如 entry。
  • mainElement:指定启动首页Ability的名称;该标签标识模块的入口ability名称或者extension名称;如 EntryAbility。
  • deviceTypes:该标签标识hap可以运行在哪类设备上,标签值采用字符串数组的表示。如 ["phone",tablet"] 表示当前模块可以运行在手机和平板上。
  • pages:对应的是main_pages.json文件,用于配置ability中用到的page信息。
  • abilities:是一个数组,存放当前模块中所有的ability元能力的配置信息,其中可以有多个ability。
    • name:该标签标识当前ability的逻辑名,该名称在整个应用要唯一,标签值采用字符串表示(最大长度127个字节)。
    • srcEntry:当前模块的入口Ability文件路径。 如:"./ets/entryability/EntryAbility.ts"
kotlin 复制代码
{
  "module": {
    //name:该标签标识当前module的名字,module打包成hap后,表示hap的名称,标签值采用字符串表示(最大长度31个字节),该名称在整个应用要唯一。
    "name": "entry",
    //type: 表示模块的类型,类型有三种,分别是entry、feature和har。
    "type": "entry",
    //description:当前模块的描述信息。
    "description": "$string:module_desc",
    //mainElement: 该标签标识hap的入口ability名称或者extension名称。只有配置为mainElement的ability或者extension才允许在服务中心露出。
    "mainElement": "EntryAbility",
    //deviceTypes:该标签标识hap可以运行在哪类设备上,标签值采用字符串数组的表示。
    "deviceTypes": [
      "phone",
      "tablet"
    ]],
    //deliveryWithInstall:标识当前Module是否在用户主动安装的时候安装,表示该Module对应的HAP是否跟随应用一起安装。
    // - true:主动安装时安装。- false:主动安装时不安装。
    "deliveryWithInstall": true,
    //installationFree:标识当前Module是否支持免安装特性。- true:表示支持免安装特性,且符合免安装约束。- false:表示不支持免安装特性。
    "installationFree": false,
    //pages:对应的是main_pages.json文件,用于配置ability中用到的page信息。
    "pages": "$profile:main_pages",
    //abilities:是一个数组,存放当前模块中所有的ability元能力的配置信息,其中可以有多个ability。
    "abilities": [
      {
        //name:该标签标识当前ability的逻辑名,该名称在整个应用要唯一,标签值采用字符串表示(最大长度127个字节)。
        "name": "EntryAbility",
        //srcEntry:当前模块的入口文件路径。
        "srcEntry": "./ets/entryability/EntryAbility.ts",
        //description:ability的描述信息。
        "description": "$string:EntryAbility_desc",
        // icon:ability的图标。该标签标识ability图标,标签值为资源文件的索引。该标签可缺省,缺省值为空。
        // 如果ability被配置为MainElement,该标签必须配置。
        "icon": "$media:icon",
        //label: ability的标签名。
        "label": "$string:EntryAbility_label",
        //startWindowIcon 启动页面的图标。
        "startWindowIcon": "$media:icon",
        //startWindowBackground: 启动页面的背景色。
        "startWindowBackground": "$color:start_window_background",
        "exported": true,
        // skills:标识能够接收的意图的action值的集合,取值通常为系统预定义的action值,也允许自定义。
        "skills": [
          {
            "entities": [
              "entity.system.home"
            ],
            "actions": [
              "action.system.home"
            ]
          }
        ]
        // visible:ability是否可以被其他应用程序调用,true表示可以被其它应用调用, false表示不可以被其它应用调用。
        // entities:标识能够接收Want的Entity值的集合。
        // actions:标识能够接收的Want的Action值的集合,取值通常为系统预定义的action值,也允许自定义。
      }
    ]
  }
}

main_pages.json

entry/src/main/resources/base/profile/main_pages.json文件保存的是当前模块中页面page的路径配置信息,所有需要进行路由跳转的page页面都要在这里进行配置。

index.ets

less 复制代码
@Entry
@Component
struct Index {
  @State message: string = 'Hello World'

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .fontColor("#36D")
          // .onClick(()=>{
          //     //处理事件            
          // })
          .onClick((event)=>{
              //处理事件
            
          })
      .width('100%')
    }
    .height('100%')
  }
}
  • struct index{ }:在上述代码中声明了一个自定义组件index,自定义组件是可复用的UI单元。
    但是自定义组件不能仅仅靠关键词struct,还需要添加 装饰器 标记来声明当前结构体的信息。
    被 @Component 装饰器修饰的结构体,才能称为自定义结构体组件。
    从java注解漫谈到typescript装饰器------注解与装饰器
  • 装饰器:用来装饰类结构、方法、变量
    • @Entry:用来修饰结构体组件;标记当前组件是入口组件,也就是当前组件是支持被独立访问的。即一个页面。
      如果组件不添加@Entry,那么这个组件就是普通组件。
      预览器只会预览使用了@Enrty装饰器的组件。
    • @Component:用来修饰结构体组件;标记声明为 自定义组件
    • @State:用来修饰变量;标记这个变量为状态变量,值变化时会触发对应的UI刷新。
  • build(){ } 函数:其内部以声明式方式描述UI结构。
    如上述代码中的Row()、Column()、Text()都是ArkTS提供的组件。
    这些组件也分为两类:
    • 容器组件:用来完成页面布局,例如:Row、Column。
    • 基础组件:自带样式和功能的页面元素,例如 Text文本组件。
  • UI样式的属性方法:设置组件的UI样式。
    通过调用自定义组件提供的属性方法,来修改组件样式信息,
    如 fontsize()设置字体大小,fontweight设置字体粗细,fontColor设置字体颜色,等等。
  • UI事件方法(监听):设置组件的事件回调。
    UI组件支持设置例如 点击 等事件的监听。如:onClick(()=>{ //处理事件... }) 在View被点击时会触发方法块里的代码。

EntryAbility.ts

javascript 复制代码
import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import window from '@ohos.window';

export default class EntryAbility extends UIAbility {
  onCreate(want, launchParam) {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
  }

  onDestroy() {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
  }

  onWindowStageCreate(windowStage: window.WindowStage) {
    // Main window is created, set main page for this ability
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

    windowStage.loadContent('pages/Index1', (err, data) => {
      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. Data: %{public}s', JSON.stringify(data) ?? '');
    });
  }

  onWindowStageDestroy() {
    // Main window is destroyed, release UI related resources
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
  }

  onForeground() {
    // Ability has brought to foreground
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
  }

  onBackground() {
    // Ability has back to background
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
  }
}

类似Activity,这里面有很多内置的生命周期,如创建、销毁;前台、后台。

ArkUI-组件

ArkUI提供的常用组件

Image:图片显示组件

Image通常用来加载图片显示。

  1. 声明Image组件并设置图片源
php 复制代码
Image(src:string|PixelMap|Resource)

入参数据类型介绍:

    • string格式:通常用来加载网络图片,应用访问网络需要声明网络访问权限:ohos.permission.INTERNET。
      Image("....png")
    • PixelMap格式:可以加载像素图,常用在图片编辑中。该方式使用较复杂。
      Image(pixelMapObject)
    • Resource格式:加载资源图片,推荐使用此种方式。
      • 方式1:Image( ** <math xmlns="http://www.w3.org/1998/Math/MathML"> r ∗ ∗ ( ′ a p p . m e d i a . i c o n ′ ) ) r**('app.media.icon')) </math>r∗∗(′app.media.icon′))r 方式的路径则是对应了src的resource文件夹中media目录中的icon图标。
        该方式可以省略文件的后缀名。
      • 方式2:Image( ** <math xmlns="http://www.w3.org/1998/Math/MathML"> r a w f i l e ∗ ∗ ( ′ g m z r . p n g ′ ) ) rawfile**('gmzr.png')) </math>rawfile∗∗(′gmzr.png′))rawfile 方式则是对应了src的resource文件夹中rawfile目录下的 gmzr.png 文件
        当前方式不允许省略文件后缀名。
  1. 添加图片属性
scss 复制代码
Image($r("app.media.icon"))
  .objectFit(ImageFit.Cover)//设置图片缩放效果,默认Cover
  .width(50)//宽度
  .height(50)//高度
  .borderRadius(10)//边框圆角,不设置时默认方角。
  .interpolation(ImageInterpolation.High)//图片插值:将不清晰图片的锯齿消除,提高图片清晰度。

图片适应模式

objectFit方法属性用于设置图片的适应模式,参数类型是ImageFit;

ImageFit包含以下几种类型:

  • Contain:保持宽高比进行缩小或者放大,使得图片完全显示在显示边界内。
  • Cover(默认值):保持宽高比进行缩小或者放大,使得图片两边都大于或等于显示边界。
  • Auto:自适应显示。
  • Fill:不保持宽高比进行放大缩小,使得图片充满显示边界。
  • ScaleDown:保持宽高比显示,图片缩小或者保持不变。
  • None:保持原有尺寸显示。

网络权限申请

加载网络图片时,需要配置文件权限声明应用权限列表

kotlin 复制代码
{
  "module" : {
    // ...
    "requestPermissions":[
      {
        //申请网络权限
        "name": "ohos.permission.INTERNET",
      },
      {
        //示例:
        "name" : "ohos.permission.PERMISSION1",
        "reason": "$string:reason",
        "usedScene": {
          "abilities": [
            "FormAbility"
          ],
          "when":"inuse"
        }
      }
    ]
  }
}

UI组件宽高设置

UI组件的宽高设置有三种方式:

  • 直接在width函数中写数值,如 .width(50),这样的话会默认使用vp虚拟像素作为宽度的单位,即 50vp。
    vp是鸿蒙的一种虚拟像素,用来适配多分辨率设备而存在,理解为安卓的dp。
    推荐使用此方式。
  • 使用字符串类型的数值,如 .width("50"),该方式的单位则是像素px,多分辨率下适配性会不太好。
  • 使用字符串类型的百分比,如 .width("100%"),该方式的单位是百分比,表示占当前组件父布局的百分比。

官方文档查看快捷方式

将鼠标悬停在组件上,点击Show in API Reference,即可打开官方组件文档。

Text:文本显示组件

  1. 声明Text组件并设置文本内容:
scss 复制代码
Text(content?:string|Resource)

文本内容来源有两个方式:

  • string格式:自己传入的字符串,会直接显示在Text组件中。
scss 复制代码
Text("文本内容")
  • Resource格式:来自资源文件的字符串,如
bash 复制代码
Text($r("app.string.EntryAbility_label"))

当前系统语言为中文时,这种方式则是从 Resource\zh_CN\string.json 文件中获取对应name为EntryAbility_label的值来显示。

  1. 设置Text的成员属性
scss 复制代码
Text(this.message)
  .fontSize(50)
  .fontWeight(FontWeight.Bold)
  .fontColor("#36D")
    • fontSize:设置字体大小。
    • fontweight:设置字体的粗细。
    • fontColor:设置文字颜色。
    • 更多的API参考官方API文档。

| 名称 | 参数类型 | 描述 |
|------------|--------------------|-----------------------------------------------------------------------------------------------|------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| fontColor | ResourceColor | 设置文本颜色。 |
| fontSize | Length | Resource | 设置文本尺寸,Length为number类型时,使用fp单位。 |
| fontStyle | FontStyle | 设置文本的字体样式。默认值:FontStyle.Normal。 |
| fontWeight | number | FontWeight | string | 设置文本的字体粗细,number类型取值[100, 900],取值间隔为100,默认为400,取值越大,字体越粗。string类型仅支持number类型取值的字符串形式,例如"400",以及"bold"、"bolder"、"lighter"、"regular"、"medium",分别对应FontWeight中相应的枚举值。默认值:FontWeight.Normal。 |
| fontFamily | string | Resource | 设置文本的字体列表。使用多个字体,使用","进行分割,优先级按顺序生效。例如:"Arial,sans-serif"。 |
| textAlign | TextAlign | 设置文本的对齐方式。Start(默认值):水平对齐首部。Center:水平居中对齐。 End:水平对齐尾部。 |
| decoration | TextDecorationType | 设置文本装饰线样式及其颜色。{ type: TextDecorationType.Underline, color: Color.Black } 给文本设置了下划线,下划线颜色为黑色 |

多语言适配

鸿蒙APP的多语言适配和Android差不多,在Resource文件夹下生成多个目录,每个目录按照指定的规则命名。之后APP会根据所运行在的系统语言版本 从对应的目录下读取string.json,并展示对应文本。

base目录:默认目录,当找不到相对应的语言版本时,默认从base查找。

en_US目录:英文的资源目录,当系统语言为英语时,优先从当前目录下读取文本。

zh_CN目录:中文的资源目录,当系统语言为中文时,优先从当前目录下读取文本。

TextInput:文本输入框

TextInput组件用于输入单行文本,响应输入事件。

  1. 声明TextInput组件:
scss 复制代码
TextInput({placeholder?:ResourceStr,text?:ResourceStr})
    • placeholder:输入框无文本时的提示文本
    • text:输入框当前的文本内容
less 复制代码
@State mImgWidth: number = 300

TextInput({
  placeholder: "请输入指定宽度",
  text: "" + this.mImgWidth
})
  .type(InputType.Number)
  .onChange((value) => {
    if (value != this.mImgWidth.toString() && value.length > 0) {
      this.mImgWidth = Number.parseInt(value)
    }
  })
  1. 添加属性和事件
scss 复制代码
TextInput({text:"当前输入文本"})
  .width(150)//宽
  .height(30)//高
  .backgroundColor("#FFF")//输入框背景色
  .type(InputType.Password)//输入框类型,如密码类型
  .onChange((value) => {//设置文字变化监听
    if (value != this.mImgWidth.toString() && value.length > 0) {
      this.mImgWidth = Number.parseInt(value)
    }
  })
    • width:宽度。
    • height:高度。
    • backgroundColor:输入框背景色。
    • type:设置输入类型。
名称 描述
Normal 基本输入模式。支持输入数字、字母、下划线、空格、特殊字符。
Number 纯数字输入模式。
PhoneNumber 电话号码输入模式。支持输入数字、+、-、*,长度不限制。
Email 邮箱地址输入模式。支持数字、字母、下划线、@字符。
Password 密码输入模式。支持输入数字、字母、下划线、空格、特殊字符。
    • onChange((value) => {}):设置文字输入变化事件监听

设置光标位置

使用TextInputController动态设置。

less 复制代码
@Entry
@Component
struct TextInputDemo {
  controller: TextInputController = new TextInputController()//创建控制器
 
  build() {
    Column() {
      TextInput({ controller: this.controller })//在这里设置控制器
      Button('设置光标位置')
        .onClick(() => {
          this.controller.caretPosition(2)//事件触发式,通过caretPosition函数来修改光标位置。
        })
    }
    .height('100%')
    .backgroundColor(0xE6F2FD)
  }
}

获取输入文本

我们可以给TextInput设置onChange事件,输入文本发生变化时触发回调

scss 复制代码
@Entry
@Component
struct TextInputDemo {
  @State text: string = ''
 
  build() {
    Column() {
      TextInput({ placeholder: '请输入账号' })
        .caretColor(Color.Blue)
        .onChange((value: string) => {
          //在此处监听文字变化后的完整文本。
          this.text = value
        })
      Text(this.text)
    }
    .alignItems(HorizontalAlign.Center)
    .padding(12)
    .backgroundColor(0xE6F2FD)
  }
}

Button:按钮组件

Button组件主要用来响应点击操作,可以包含子组件。

  1. 声明Button组件,label是按钮文字
css 复制代码
Button(label?:ResourceStr)
    • ResourceStr:可以传普通字符串,也可以传string文件中的文本。
    • 按钮有两种类型,传递了文字则是文字型按钮,不传递文字则是自定义按钮。
      • 文字型按钮:
less 复制代码
Button("按钮")
      • 自定义按钮,在Button内嵌套其他组件:
scss 复制代码
Button(){
  ImageView($r("app.media.search")).width(20).height(10)
}
  1. 添加属性和事件
scss 复制代码
Button("点我")
.width(100)
.height(30)
.type(ButtonType.Normal)
.onclick(()=>{
  //处理点击事件
})
  • type:按钮类型。
    • Capsule:胶囊型按钮(圆角默认为高度的一半)
    • Circle:圆形按钮
    • Normal:普通按钮(默认不带圆角)

LoadingProgress:加载中组件

LoadingProgress组件用于显示加载进展,比如应用的登录界面,当我们点击登录的时候,显示的"正在登录"的进度条状态。LoadingProgress的使用非常简单,只需要设置颜色和宽高就可以了。

scss 复制代码
LoadingProgress()
  .color(Color.Blue)
  .height(60)
  .width(60)

Slider:滑动条组件

  1. 滑动条组件声明:
scss 复制代码
Slider(options?:SliderOptions)

滑动条组件在创建时,支持传入 SliderOption 类型的对象(不传入则使用默认样式),SliderOption 类型中声明了一些的滑动条组件的UI属性。

    • min:最小值。
    • max:最大值。
    • value:当前默认值。
    • setp:滑动时的步长。
    • style:默认OutSet:滑块在滑条外面。 SliderStyle.inset:滑块在滑条里面。
    • direction:滑动条方向水平或垂直。
    • reverse:是否反转滑动条的滑动方向。
      false时:水平滑动条,左边为起始位,右边为终点;垂直滑动条,下面起始位,上面终点位。
      true时:则相反。
arduino 复制代码
Slider({
  min:0,//最小值
  max:100,//最大值
  value:50,//当前值
  step:1,//滑动步长
  style:SliderStyle.OutSet,//默认OutSet:滑块在滑条外面。 另一个inset:滑块在滑条里面。
  direction:Axis.Horizontal,//滑动条水平 或 垂直
  reverse:false//是否反向滑动。 
})
  1. 更多属性、事件。
    • showTips:是否展示value的百分比提示。
    • blockColor:滑块的颜色
    • trackThickness:滑动条的粗细
    • onchange((value)=>{}):监听滑块滑动时值的变更。value就是滑块当前值
javascript 复制代码
.width("90%")
  .showTips(true)//是否展示value百分比提示
  .blockColor("#36d")//滑块的颜色
  .trackThickness(5)//滑动条的粗细
  .onChange((value)=>{//监听滑块滑动时值的变更。
    //value就是滑块当前值
    this.mImgWidth=value
  })

使用资源引用类型

Resource是资源引用类型,用于设置组件属性的值。推荐大家优先使用Resource类型,将资源文件(字符串、图片、音频等)统一存放于resources目录下,便于开发者统一维护。同时系统可以根据当前配置加载合适的资源,例如,开发者可以根据屏幕尺寸呈现不同的布局效果,或根据语言设置提供不同的字符串。

例如下面的这段代码,直接在代码中写入了字符串和数字这样的硬编码。

scss 复制代码
Button('登录', { type: ButtonType.Capsule, stateEffect: true })
  .width(300)
  .height(40)
  .fontSize(16)
  .fontWeight(FontWeight.Medium)
  .backgroundColor('#007DFF')

我们可以将这些硬编码写到entry/src/main/resources下的资源文件中。

在string.json中定义Button显示的文本。

json 复制代码
{
  "string": [
    {
      "name": "login_text",
      "value": "登录"
    }
  ]
}

在float.json中定义Button的宽高和字体大小。

css 复制代码
{
  "float": [
    {
      "name": "button_width",
      "value": "300vp"
    },
    {
      "name": "button_height",
      "value": "40vp"
    },
    {
      "name": "login_fontSize",
      "value": "18fp"
    }
  ]
}

在color.json中定义Button的背景颜色。

css 复制代码
{
  "color": [
    {
      "name": "button_color",
      "value": "#1890ff"
    }
  ]
}

然后在Button组件通过"$r('app.type.name')"的形式引用应用资源。

  • app代表应用内resources目录中定义的资源;
  • type代表资源类型(或资源的存放位置),可以取"color"、"float"、"string"、"plural"、"media";
  • name代表资源命名,由开发者定义资源时确定。
bash 复制代码
Button($r('app.string.login_text'), { type: ButtonType.Capsule })
  .width($r('app.float.button_width'))
  .height($r('app.float.button_height'))
  .fontSize($r('app.float.login_fontSize'))
  .backgroundColor($r('app.color.button_color'))

ArkUI-容器组件

Column和Row容器

当容器内部有多个组件时,Column(列)会将内部组件从上往下排列,等于安卓垂直方向的LinearLayout;

而Row(行)容器会将内部组件从左向右排列,等于安卓水平方向的LinearLayout。

主轴与交叉轴

已知我们的两个容器它们内部元素的排列是有方向的,那么它们排列方向的那条中间轴称之为主轴,与之垂直的称为交叉轴。我们容器中组件的对齐则是围绕这两个轴来实现的。

容器提供了以下两个方法,用来调整容器内元素的对齐方式:

属性方法名 说明 参数
justifyContent 设置子元素在主轴方向的对齐格式 FlexAlign枚举
alignItems 设置子元素在交叉轴方向的对齐格式 Row容器使用VerticalAlign枚举;Column容器使用HorizontalAlign枚举。

设置主轴对齐方式

枚举 功能
FlexAlign.start 主轴方向,内部元素默认往主轴起始位置靠拢。
FlexAlign.center 主轴方向,内部元素默认往主轴中间位置靠拢。
FlexAlign.end 主轴方向,内部元素默认往主轴终点位置靠拢。
FlexAlign.SpaceBetween 主轴方向,头尾的元素贴近起点/终点,其余元素居中平分间距。
FlexAlign.SpaceAround 主轴方向,头尾的元素靠近起点/终点(头尾元素间距为其他元素间距的一半),其余元素居中平分剩余间距。
FlexAlign.SpaceEvenly 主轴方向,所有元素靠近主轴中间,平分间距。

设置交叉轴对齐方式

VerticalAlign

因为Row容器内部元素是水平排列的,所以交叉轴是垂直的,因此使用VerticalAlign,控制垂直方向的元素排列。

枚举 功能
VerticalAlign.start 垂直方向,靠近交叉轴的起始位置。
VerticalAlign.center 垂直方向,靠近交叉轴的中间位置。
VerticalAlign.end 垂直方向,靠近交叉轴的终点位置。
HorizontalAlign

因为Column容器内部元素是垂直排列的,所以交叉轴是水平的,因此使用HorizontalAlign,控制水平方向的元素排列。

枚举 功能
HorizontalAlign.start 水平方向,靠近交叉轴的起始位置。
HorizontalAlign.center 水平方向,靠近交叉轴的中间位置。
HorizontalAlign.end 水平方向,靠近交叉轴的终点位置。

设置元素内、外边距

每个UI组件都支持设置自身的内外边距。内边距使用padding(),外边距使用margin()。

scss 复制代码
margin(value: Margin | Length)
padding(value: Padding | Length)

参数说明:

  • Length类型:支持传入string、number、Resource三种类型。如: "1%",10, <math xmlns="http://www.w3.org/1998/Math/MathML"> r 、 r、 </math>r、rawfile方式。
scss 复制代码
.margin(20)//设置上下左右边距为20
  • Margin类型:Margin是一个对象,它里面有四个参数:
    • top:上边距。
    • bottom:下边距。
    • left:左边距。
    • right:右边距。
    • 以上四个参数可以选择性设置,不设置则默认0。
css 复制代码
.margin({top:20,bottom:20,left:0,right:0})//设置top、bottom、left、right边距。
.margin({top:20,bottom:20})//只设置top、bottom的边距,其他的不设置。

blank组件

blan组件是个空组件,主要用来占位,它会占满当前父容器主轴方向的空闲位置。

比如我写了一个布局:一个Row容器,内部由3个组件构成,分别为返回键、标题、刷新按钮;

我想使返回按钮和标题栏居左,而刷新按钮居右。

此时则可以在标题栏 和 刷新按钮的中间放置一个Blank组件,这样一来,刷新按钮就被挤到了右侧。

案例:图片放大缩小页面

使用前面学习的Ark常用组件来完成图片放大缩小页面的开发。

效果图:

功能介绍:

  • 布局使用Column、Row容器;图片组件使用Image、输入框使用TextInput、文本显示使用Text、放大/缩小按钮使用Button、滑动条使用Slider。
  • 当修改图片宽度文字时,调整Image的宽度使其呈现放大效果,同时拖动条位置也跟随变化。
  • 当点击放大/缩小按钮时,Image放大/缩小,同时拖动条与图片宽度文字也同步更新。
  • 当拖动Slider时,Image跟随滑动变化,同时图片宽度文字同步更新。

完成代码:Linxy/鸿蒙应用开发入门代码仓库

scss 复制代码
@Entry
@Component
struct Index {
  @State message: string = 'Hello World'
  @State mImgWidth: number = 300 //用来修饰变量;标记这个变量为状态变量,值变化时会触发对应的UI刷新。

  build() {
    Row() {
      Column() {
        Image($rawfile("gmzr.png"))//添加rawfile文件夹下的资源文件
          .width(this.mImgWidth)//组件的宽度绑定mImgWidth
          .height(400)//高设置为400vp
          .interpolation(ImageInterpolation.High)//去除图片锯齿

        Row() {
          Text("图片宽度")//文字文本
          TextInput({
            placeholder: "请输入指定宽度",//提示词,类似hint
            text: this.mImgWidth.toFixed(0)//获取mImgWidth的文本,包含小数点后0位。
          })
            .type(InputType.Number)//只允许输入数字
            .onChange((value) => {
              //输入文字监听
              if (value != this.mImgWidth.toString() && value.length > 0) {
                this.mImgWidth = Number.parseInt(value)
              }
            })
            .width(200)
        }
        .width("90%")
        .justifyContent(FlexAlign.SpaceBetween)//主轴对齐方式:头尾的元素贴近起点/终点,其余元素居中平分间距。
        .alignItems(VerticalAlign.Center)//内部元素沿交叉轴居中,row容器中交叉轴为垂直,所以这里效果为元素上下居中。
        .margin({top:20,bottom:20,left:0,right:0})//设置容器的外边距

        Row() {
          Button("放大")
            .width(80)
            .height(30)
            .onClick(() => {
              //监听点击事件
              if (this.mImgWidth < SLIDER_MAX) {
                if (this.mImgWidth + 50 > SLIDER_MAX) {
                  this.mImgWidth = SLIDER_MAX
                } else {
                  this.mImgWidth += 50
                }
              }
            })
            .type(ButtonType.Normal)//按钮使用默认的方形样式

          Button("缩小")
            .width(80)
            .height(30)
            .onClick(() => {
              if (this.mImgWidth > SLIDER_MIN) {
                if (this.mImgWidth - 50 < SLIDER_MIN) {
                  this.mImgWidth = SLIDER_MIN
                } else {
                  this.mImgWidth -= 50
                }
              }
            })
            .type(ButtonType.Normal)
        }.width("100%")//宽度为父容器的百分百,占满
        .justifyContent(FlexAlign.SpaceEvenly)//主轴方向,所有元素靠近主轴中间,平分间距。 左右居中等间距。

        Slider({
          min: SLIDER_MIN, //最小值
          max: SLIDER_MAX, //最大值
          value: this.mImgWidth, //当前值
          step: 1, //滑动步长
          style: SliderStyle.OutSet, //默认OutSet:滑块在滑条外面。 另一个inset:滑块在滑条里面。
          direction: Axis.Horizontal, //滑动条水平 或 垂直
          reverse: false //是否反向滑动
        })
          .width("90%")
          .showTips(true) //是否展示value百分比提示
          .blockColor("#36d") //滑块的颜色
          .trackThickness(5) //滑动条的粗细
          .onChange((value) => { //监听滑块滑动时值的变更。
            //value就是滑块当前值
            this.mImgWidth = value
          })
      }
      .height('100%')
      // .justifyContent(FlexAlign.SpaceEvenly)
      .justifyContent(FlexAlign.Center)//主轴方向居中,这里是Column主轴垂直,即垂直居中。
    }
    .height('100%')
  }
}

const SLIDER_MIN = 0;
const SLIDER_MAX = 2000;

function name(params) {

}

布局检查器

在DevEcoStudio编译器中,点击Previewer查看预览视图,再点击Inspector打开布局检查功能。

打开布局检查功能后,就能够点击预览页面的视图、或者点击ComponentTree中的item项,编译器会自动帮助我们定位到对应的代码块,并展示UI相关的其他信息及获取布局结构。

容器内元素间距space

Row容器和Column容器都支持设置内部元素间的间距值,用法为:

less 复制代码
Row({space:20}) {...}
Coloumn({space:20}) {...}

RelativeContainer相对布局

RelativeContainer是一个相对布局容器,通常用于复杂场景中元素对齐的布局。

  • 容器内的组件区分 水平方向 和 垂直方向:
    • 水平方向为left, middle, right,对应容器的HorizontalAlign.Start, HorizontalAlign.Center, HorizontalAlign.End。
    • 垂直方向为top, center, bottom,对应容器的VerticalAlign.Top, VerticalAlign.Center, VerticalAlign.Bottom。
  • 容器内的组件必须设置ID,如果不设置ID,那么该组件则不会被显示。
  • 容器内组件主要通过设置alignRules属性来决定位置的摆放规则:
less 复制代码
RelativeContainer() {
  Button()
    .id('ConfigBtn')
    .alignRules({
      left: { anchor: '__container__', align: HorizontalAlign.Start },
      top: { anchor: '__container__', align: VerticalAlign.Top },
    })
}
  • alignRules函数的参数是一个 AlignRuleOption 接口,AlignRuleOption 接口中有六个属性,分别是:
    • 水平方向上的:left、middle、right,即左中右。
    • 垂直方向上的:top、center、bottom,即上中下。

它们对应着组件的指定位置的对齐规则。

  • AlignRuleOption中的属性是对象,它们由两个参数构成:
    • anchor:锚点,即我们给组件设置的id,'container '表示指向当前组件父容器。
      锚点用于标识当前组件的某个位置是以该锚点对象作为参照物的。
    • align:对齐方式,水平方向上是HorizontalAlign,垂直方向上是VerticalAlign,内部对应着上中,左中右。

如:left: { anchor: 'container', align: HorizontalAlign.Start },表示当前组件的左侧坐标,以父容器作为参考对象,对齐父容器的水平方向起始位置(左侧)。

ArkUI-循环控制/FOREACH

ArkUI中,可以通过使用FOREACH函数来生成列表视图。

案例:

上述案例中,是一个商品列表,实现逻辑大致为:

  1. 首先抽出单条item的布局。
  2. 定义item所需要的数据类型Bean类。
  3. 定义列表,存储每项Item的数据。
  4. 借助foreach循环函数,根据传入数据,而生成组件并渲染

FOREACH

在ArkUI中,绘制列表需要借助FOREACH函数。

FOREACH函数共有三个入参:

  • Array:第一个参数是要遍历的数据数组,即列表的数据源。
  • (item:any,index?:number)=>{}:第二个参数是函数回调,在此处进行页面组件的生成渲染。
  • keyGenerator?:(item:any,index?:number):string:第三个参数也是函数回调,它非常重要,我们要在函数中为item生成唯一标识符,并作为返回值返回。(唯一标识符,通常在Array数组内部会有定义好的。)
scss 复制代码
FOREACH(
  arr:Array,//要遍历的数据数组
  (item:any,index?:number)=>{
    //页面组件生成的函数回调
    Row(){
      Image(item.image)
      Column(){
        Text(item.name)
        Text(item.price)
      }
    }
  },
  keyGenerator?:(item:any,index?:number):string => {
    //定义每个item项的ID,必须唯一,用于列表内部的渲染判断
  }
)

案例:实现一个列表

  1. 首先,我们先在组件中写好item的布局:

绘制的item如下:

scss 复制代码
Row({space:20}) { //Row({space:20}) 表示设置元素间的主轴的间距为20
  Image($rawfile('gmzr.png'))
    .width(100)
    .height(100)
  Column(){
    Text("商品名称")
      .fontSize(20)
      .fontColor('#000000')
      .fontWeight(FontWeight.Bold)
    Text("商品售价:¥190")
      .fontSize(20)
      .fontColor('#ff0000')
      .fontWeight(FontWeight.Bold)
  }.alignItems(HorizontalAlign.Start)
    .justifyContent(FlexAlign.SpaceEvenly)
    .height(130)
}.width('auto')
  .margin({left:15,top:15,bottom:15})
}
  1. 定义每条Item的所需要数据类型的Bean类。
typescript 复制代码
export interface ShopItemBean {
  _id: number; //商品ID
  _imageUrl: string; //图片地址
  _name: string; //商名称
  _price: number; //商品价格
}
  1. 创建列表的数据数组
yaml 复制代码
private items: ShopItemBean[] = [
    { _id: 1, _name: '华为p10', _imageUrl: '', _price: 1 },
    { _id: 2, _name: '小米1', _imageUrl: '', _price: 2 },
    { _id: 3, _name: '华为p12', _imageUrl: '', _price: 3 },
    { _id: 4, _name: '华为p13', _imageUrl: '', _price: 4 },
    { _id: 5, _name: '华为p14', _imageUrl: '', _price: 5 },
    { _id: 6, _name: '华为p15', _imageUrl: '', _price: 6 },
    { _id: 7, _name: '华为p16', _imageUrl: '', _price: 7 },
    { _id: 8, _name: '华为p17', _imageUrl: '', _price: 8 },
    { _id: 9, _name: '华为p18', _imageUrl: '', _price: 9 },
    { _id: 10, _name: '华为p19', _imageUrl: '', _price: 10 },
    { _id: 11, _name: '华为p20', _imageUrl: '', _price: 11 },
    { _id: 12, _name: '华为p21', _imageUrl: '', _price: 12 },
  ];
  1. 使用FOREACH,绘制item
typescript 复制代码
    ForEach(this.items,(item:ShopItemBean,index:number)=>{
      //进行item布局的绘制
      在此处放入我们第一步骤填写的Item布局,并且将item的数据填入到item中
    },(item:ShopItemBean,index?:number):string => {
      //定义每个item项的ID,必须唯一,用于列表内部的渲染判断
      return item.id.toString()
    })
  1. 绘制完item后,页面布局是滑动不了的,还需要添加一个滑动控件;
    首先在FOREACH外面添加一个容器A,在容器A外部包裹一个scroll控件就能滑动了。
    (因为scroll控件内只能包裹一个控件,所以要用一个容器A来包裹FOREACH的列表item)
scss 复制代码
Scroll() {
  Column() {
    FOREACH(....)
  }
}

完整代码:Linxy/ArkUI实现列表

scss 复制代码
import { ShopItemBean } from 'ets/bean/ShopItemBean'

@Entry
@Component
struct Index2 {
  private items: ShopItemBean[] = [
    { _id: 1, _name: '华为p10', _imageUrl: '', _price: 1 },
    { _id: 2, _name: '小米1', _imageUrl: '', _price: 2 },
    { _id: 3, _name: '华为p12', _imageUrl: '', _price: 3 },
    { _id: 4, _name: '华为p13', _imageUrl: '', _price: 4 },
    { _id: 5, _name: '华为p14', _imageUrl: '', _price: 5 },
    { _id: 6, _name: '华为p15', _imageUrl: '', _price: 6 },
    { _id: 7, _name: '华为p16', _imageUrl: '', _price: 7 },
    { _id: 8, _name: '华为p17', _imageUrl: '', _price: 8 },
    { _id: 9, _name: '华为p18', _imageUrl: '', _price: 9 },
    { _id: 10, _name: '华为p19', _imageUrl: '', _price: 10 },
    { _id: 11, _name: '华为p20', _imageUrl: '', _price: 11 },
    { _id: 12, _name: '华为p21', _imageUrl: '', _price: 12 },
  ];

  build() {
    Column() {
      Text("商品列表")
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
        .margin({ left: 20 })
      Scroll() {
        Column() {
          ForEach(this.items, (item: ShopItemBean, index: number) => {
            //进行item布局的绘制
            Row() {
              Image('https://img0.baidu.com/it/u=3628503530,464378779&fm=253&fmt=auto&app=120&f=JPEG?w=1200&h=800')
                .width(100)
                .height(100)
                .borderRadius(20)
                .interpolation(ImageInterpolation.High)
              Column() {
                Text(item._name)
                  .fontSize(20)
                  .fontColor('#000000')
                  .fontWeight(FontWeight.Bold)
                Text("商品售价:¥" + item._price)
                  .fontSize(20)
                  .fontColor('#ff0000')
                  .fontWeight(FontWeight.Bold)
              }.alignItems(HorizontalAlign.Start)
              .justifyContent(FlexAlign.SpaceEvenly)
              .height(130)
              .margin({ left: 20 })
            }
            .width('95%')
            .margin({ top: 15, bottom: 15 })
            .padding(15)
            .alignItems(VerticalAlign.Center)
            .justifyContent(FlexAlign.Start)
            .backgroundColor('#c6c6c6')
            .borderRadius(20)
          }, (item: ShopItemBean, index?: number): string => {
            //定义每个item项的ID,必须唯一,用于列表内部的渲染判断
            return item._id.toString()
          })
        }.width('100%')
        .alignItems(HorizontalAlign.Center)
        .padding({ bottom: 70 })
      }.width('auto')
      .height('100%')
    }
    .width('100%')
    .height('100%')
    .alignItems(HorizontalAlign.Start)
    .margin({ top: 30 })
  }
}

ArkUI-List容器组件

上述完成了一个简单的列表页面开发,但是它存在局限性,比如说无法滑动(需要自己去实现)、没有组件复用 等问题。

而List容器是正宗的列表布局容器组件(Android的RecycleView),列表容器具备以下特点:

  • 列表项(ListItem)数量过多超出屏幕后,会自动提供滚动功能。
  • 列表项(ListItem)既可以纵向排列,也可以横向排列。

使用方法:

scss 复制代码
List({space:10}){//列表项的间距
  ForEach([1,2,3,4],(item:number)=>{
    ListItem(){//ListItem是标记,不是容器。
      //列表项内容,只能包含一个根组件
      Text(item.toString())
        .fontColor('#ff00ff')
        .width('auto')
        .height('auto')
        .fontSize(20)
    }
  })
}.width('100%')
  .listDirection(Axis.Vertical)//列表方向:默认垂直

使用List容器实现商品列表页面:

scss 复制代码
import { ShopItemBean } from 'ets/bean/ShopItemBean'

@Entry
@Component
struct Index3 {
  private items: ShopItemBean[] = [
    { _id: 1, _name: '华为p10', _imageUrl: '', _price: 1 },
    { _id: 2, _name: '小米1', _imageUrl: '', _price: 2 },
    { _id: 3, _name: '华为p12', _imageUrl: '', _price: 3 },
    { _id: 4, _name: '华为p13', _imageUrl: '', _price: 4 },
    { _id: 5, _name: '华为p14', _imageUrl: '', _price: 5 },
    { _id: 6, _name: '华为p15', _imageUrl: '', _price: 6 },
    { _id: 7, _name: '华为p16', _imageUrl: '', _price: 7 },
    { _id: 8, _name: '华为p17', _imageUrl: '', _price: 8 },
    { _id: 9, _name: '华为p18', _imageUrl: '', _price: 9 },
    { _id: 10, _name: '华为p19', _imageUrl: '', _price: 10 },
    { _id: 11, _name: '华为p20', _imageUrl: '', _price: 11 },
    { _id: 12, _name: '华为p21', _imageUrl: '', _price: 12 },
  ];

  build() {
    Column() {
      Text("商品列表")
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
        .margin({ left: 20 })

      List({ space: 10 }) { //列表项的间距
        ForEach(this.items, (item: ShopItemBean) => {
          ListItem() { //ListItem是标记,不是容器。
            //列表项内容,只能包含一个根组件
            //进行item布局的绘制
            Row({ space: 20 }) {
              Image('https://img0.baidu.com/it/u=3628503530,464378779&fm=253&fmt=auto&app=120&f=JPEG?w=1200&h=800')
                .width(100)
                .height(100)
                .borderRadius(20)
                .interpolation(ImageInterpolation.High)
              Column() {
                Text(item._name)
                  .fontSize(20)
                  .fontColor('#000000')
                  .fontWeight(FontWeight.Bold)
                Text("商品售价:¥" + item._price)
                  .fontSize(20)
                  .fontColor('#ff0000')
                  .fontWeight(FontWeight.Bold)
              }.alignItems(HorizontalAlign.Start)
              .justifyContent(FlexAlign.SpaceEvenly)
              .height(130)
            }
            .width('95%')
            .margin({ top: 15, bottom: 15 })
            .padding(15)
            .alignItems(VerticalAlign.Center)
            .justifyContent(FlexAlign.Start)
            .backgroundColor('#c6c6c6')
            .borderRadius(20)
          }
        })
      }.width('100%')
      .layoutWeight(1)//注意,使用这个方式来配置好list组件的高度,如果高度没配置好,可能会出现滑动,显示不完整等问题。
      //listDirection():设置List列表内部排列方向,参数是 Axis 枚举:默认垂直;
      // Vertical(默认值):子组件ListItem在List容器组件中呈纵向排列。Horizontal:子组件ListItem在List容器组件中呈横向排列。
      .listDirection(Axis.Vertical) 
      .alignListItem(ListItemAlign.Center)//列表内item项交叉轴居中
    }.width('100%')
    .alignItems(HorizontalAlign.Center)
  }
}

设置列表分割线

List组件子组件ListItem之间默认是没有分割线的,部分场景子组件ListItem间需要设置分割线,这时候可以使用List组件的divider属性。

divider属性包含四个参数:

  • strokeWidth: 分割线的线宽。
  • color: 分割线的颜色。
  • startMargin:分割线距离列表侧边起始端的距离。
  • endMargin: 分割线距离列表侧边结束端的距离。
scss 复制代码
List({ space: 10 }) { //列表项的间距
  ForEach(this.items, (item: ShopItemBean) => {
    ListItem() { //ListItem是标记,不是容器。
      ShopItemCard(item)
    }
  })
}.width('100%')
  //设置分割线
  .divider({strokeWidth:2,startMargin:5,endMargin:5,color:"#ff00ff"})

List列表滚动事件监听

List组件提供了一系列事件方法用来监听列表的滚动,我们可以根据需要,监听这些事件来做一些操作:

  • onScroll:列表滑动时触发,返回值scrollOffset为滑动偏移量,scrollState为当前滑动状态。
  • onScrollIndex:列表滑动时触发,返回值分别为滑动起始位置索引值与滑动结束位置索引值。
  • onReachStart:列表到达起始位置时触发。
  • onReachEnd:列表到底末尾位置时触发。
  • onScrollStop:列表滑动停止时触发。
typescript 复制代码
List({ space: 10 }) {
  ForEach(this.arr, (item) => {
    ListItem() {
      Text(`${item}`)
        ...
    }
  }, item => item)
}
//列表滑动时触发,返回值分别为滑动起始位置索引值与滑动结束位置索引值。
.onScrollIndex((firstIndex: number, lastIndex: number) => {
  console.info('first' + firstIndex)
  console.info('last' + lastIndex)
})
//列表滑动时触发,返回值scrollOffset为滑动偏移量,scrollState为当前滑动状态。
.onScroll((scrollOffset: number, scrollState: ScrollState) => {
  console.info('scrollOffset' + scrollOffset)
  console.info('scrollState' + scrollState)
})
// 列表到达起始位置时触发。
.onReachStart(() => {
  console.info('onReachStart')
})
// 列表到底末尾位置时触发。
.onReachEnd(() => {
  console.info('onReachEnd')
})
// onScrollStop:列表滑动停止时触发。
.onScrollStop(() => {
  console.info('onScrollStop')
})

Grid组件的使用

Grid组件简介

Grid组件为网格容器,是一种网格列表,由"行"和"列"分割的单元格所组成,通过指定"项目"所在的单元格做出各种各样的布局。Grid组件一般和子组件GridItem一起使用,Grid列表中的每一个条目对应一个GridItem组件。

使用ForEach渲染网格布局

和List组件一样,Grid组件也可以使用ForEach来渲染多个列表项GridItem,我们通过下面的这段示例代码来介绍Grid组件的使用。

scss 复制代码
@Entry
@Component
struct GridExample {
  // 定义一个长度为16的数组
  private arr: string[] = new Array(16).fill('').map((_, index) => `item ${index}`);

  build() {
    Column() {
      Grid() {
        ForEach(this.arr, (item: string) => {
          GridItem() {
            Text(item)
              .fontSize(16)
              .fontColor(Color.White)
              .backgroundColor(0x007DFF)
              .width('100%')
              .height('100%')
              .textAlign(TextAlign.Center)
          }
        }, item => item)
      }
      .columnsTemplate('1fr 1fr 1fr 1fr')
        .rowsTemplate('1fr 1fr 1fr 1fr')
        .columnsGap(10)
        .rowsGap(10)
        .height(300)
    }
    .width('100%')
      .padding(12)
      .backgroundColor(0xF1F3F5)
  }
}

示例代码中创建了16个GridItem列表项。同时设置columnsTemplate的值为'1fr 1fr 1fr 1fr',表示这个网格为4列,将Grid允许的宽分为4等分,每列占1份;rowsTemplate的值为'1fr 1fr 1fr 1fr',表示这个网格为4行,将Grid允许的高分为4等分,每行占1份。这样就构成了一个4行4列的网格列表,然后使用columnsGap设置列间距为10vp,使用rowsGap设置行间距也为10vp。示例代码效果图如下:

上面构建的网格布局使用了固定的行数和列数,所以构建出的网格是不可滚动的。然而有时候因为内容较多,我们通过滚动的方式来显示更多的内容,就需要一个可以滚动的网格布局。我们只需要设置rowsTemplate和columnsTemplate之间的一个即可,另一个不设置。

如:将示例代码中GridItem的高度设置为固定值,例如100;仅设置columnsTemplate属性,不设置rowsTemplate属性,就可以实现Grid列表的滚动:

scss 复制代码
Grid() {
  ForEach(this.arr, (item: string) => {
    GridItem() {
      Text(item)
        .height(100)
        ...
        }
  }, item => item)
}
.columnsTemplate('1fr 1fr 1fr 1fr')//将网格分为4行,每行占容器一等份的长度
  .columnsGap(10)//设置列间距为10vp
  .rowsGap(10)//设置行间距
  .height(300)//固定高度

此外,Grid像List一样也可以使用onScrollIndex来监听列表的滚动。

ArkUI-自定义组件

自定义组件声明

自定义组件大家并不陌生;在ArkUI中,自定义组件的实现是一个struct结构体+@Component装饰器组合,就完成了声明。

less 复制代码
@Component
struct Index3 {
}

使用自定义组件

比如我完成了一个名为 Header 的自定义组件,那么我在代码中使用就是 直接使用 Header() 来使用这个组件:

自定义组件案例:封装标题栏自定义组件

  1. 首先编写标题栏的item
scss 复制代码
Row(){
  Image($r('app.media.back'))
    .width(40)
    .height(40)
    .interpolation(ImageInterpolation.High)
  Text("标题")
    .fontSize(30)
    .fontColor('#000000')
    .fontWeight(FontWeight.Bold)
    .margin({left:30})
          Blank()
  Image($r('app.media.refresh'))
    .width(40)
    .height(40)
}.justifyContent(FlexAlign.Start)
  .width("100%")
  .alignItems(VerticalAlign.Center)
  .padding({left:20,top:20,right:20,bottom:20})
  1. 创建一个自定义View:CommonTitleBar
scss 复制代码
/**
 * 标题栏组件
 */
@Component
struct CommonTitleBar{
  build(){
    //..放置写好的item布局
  }
}
  1. 放置第一步中写好的item布局代码
scss 复制代码
/**
 * 标题栏组件
 */
@Component
export struct CommonTitleBar{
  build(){
    Row(){
      Image($r('app.media.back'))
        .width(40)
        .height(40)
        .interpolation(ImageInterpolation.High)
      Text("标题")
        .fontSize(30)
        .fontColor('#000000')
        .fontWeight(FontWeight.Bold)
        .margin({left:30})
      Blank()
      Image($r('app.media.refresh'))
        .width(40)
        .height(40)
    }.justifyContent(FlexAlign.Start)
    .width("100%")
    .alignItems(VerticalAlign.Center)
    .padding({left:20,top:20,right:20,bottom:20})
  }
}
  1. 给自定义View设置所需的成员参数,并配置构造函数,使用。
scss 复制代码
@State private _title: string = "";

constructor(_title: string) {
  super();
  this._title = _title
}

....
      Text(this._title)
        .fontSize(30)
        .fontColor('#000000')
        .fontWeight(FontWeight.Bold)
        .margin({ left: 30 })
....
  1. 在主页使用CommonTitleBar组件
scss 复制代码
import { CommonTitleBar } from '../widget/CommonTitleBar';//导入组件

build() {
  Column() {
    CommonTitleBar({_title:'商品列表'})
      .margin(10)//所有的自定义组件也是添加样式函数
    list....
  }
}
  1. 这样一个公共的标题栏组件就完成了。

自定义构建函数及案例:封装ListItem布局

接下来我们还想将商品列表的ListItem也进行封装,那么可以继续使用自定义组件的方式来封装;但是,我们还有一种更方便的方法,自定义构建函数。

  1. 在全局中,使用@Builder来标识自定义构建函数,自定义构建函数建议使用大写字母开头,因为它也可以被理解成一个匿名的自定义View组件
less 复制代码
@Builder function ShopItemCard(){...}
  1. 在函数中,完成声明式UI的声明:将我们先前写的ListItem的布局复制过来。
scss 复制代码
//全局自定义构建函数
@Builder function ShopItemCard(){
  //列表项内容,只能包含一个根组件
  //进行item布局的绘制
  Row({ space: 20 }) {
    Image('https://img0.baidu.com/it/u=3628503530,464378779&fm=253&fmt=auto&app=120&f=JPEG?w=1200&h=800')
      .width(100)
      .height(100)
      .borderRadius(20)
      .interpolation(ImageInterpolation.High)
    Column() {
      Text(item._name)
        .fontSize(20)
        .fontColor('#000000')
        .fontWeight(FontWeight.Bold)
      Text("商品售价:¥" + item._price)
        .fontSize(20)
        .fontColor('#ff0000')
        .fontWeight(FontWeight.Bold)
    }.alignItems(HorizontalAlign.Start)
    .justifyContent(FlexAlign.SpaceEvenly)
    .height(130)
  }
  .width('95%')
  .margin({ top: 15, bottom: 15 })
  .padding(15)
  .alignItems(VerticalAlign.Center)
  .justifyContent(FlexAlign.Start)
  .backgroundColor('#c6c6c6')
  .borderRadius(20)
}
  1. 此时发现函数中 item 报红,因为拿不到item数据,所以增加一个形参:
less 复制代码
@Builder function ShopItemCard(item:ShopItemBean){...}
  1. 在List组件中,调用自定义构建函数:
scss 复制代码
List({ space: 10 }) { //列表项的间距
  ForEach(this.items, (item: ShopItemBean) => {
    ListItem() { //ListItem是标记,不是容器。
      //此处调用自定义构建函数
      ShopItemCard(item)
    }
  })
}
  1. 这样便完成了ListItem的UI绘制。

局部自定义构建函数

我们刚才的实例中自定义构建函数是声明在全局的,即全局自定义构建函数,这样的话其他文件也能使用它。

如果只想给将自定义构建函数放在类中去使用,不给外部使用,那么就得使用局部自定义构建函数。即 把函数的声明&定义放在class中;但是请注意,在class类中的函数不可以使用function关键字,所以在class中function关键字是要省略的,即:

less 复制代码
class MyClass(){
    //局部自定义构建函数
    @Builder ShopItemCard(item:ShopItemBean){...}
}

注意:调用局部自定义构建函数时,添加 this.前缀,因为类中的成员,必须使用this来指向。

自定义公共样式函数

自定义公共样式 与 自定义组件的构建函数大差不差,只是将 @Builder 装饰器 更换为了 @Styles。

自定义公共样式函数主要是封装公共的样式,给其他View使用。

定义:如下定义了一个公共样式函数,并将width和height设置为100%

less 复制代码
//自定义公共样式函数
@Styles function mathStyleWH(){
  .width('100%')
  .height('100%')
}

使用:直接在组件的尾部调用我们的mathStyleWH()函数,等于Column组件调用了width('100%') 和 height('100%')两个函数。这玩意像宏定义。

scss 复制代码
Column()
  .mathStyleWH()

注意

  • 自定义公共样式函数也分 全局 和 局部,与自定义构建函数一致。
  • 自定义公共样式函数,默认情况下只能修改所有组件的公共函数,即通用的属性方法。
  • 如果需要修改某个组件的特定属性,如Text组件的fontsize属性,那么就需要使用@Extend装饰器,表示继承该View。
java 复制代码
//指定父类为Text组件,设置其的特定属性。
@Extend(Text) function  setFontColor(){
    .fontColor('#00ff00')
}

总结

  • 自定义组件有两种方式:
    • 自定义View
      @Component Struct View
    • 自定义构建函数
      @Builder funName(){...}
      自定义构建函数又分为:全局和局部函数,作用域不同。
  • 自定义样式函数 :@Styles functionName(){...}
    自定义样式函数用来集中控制公共样式,便于后期对样式统一的修改。
    @Styles装饰器只能修改所有组件公共的属性,如果想使用特定组件的专用属性,可以使用@Extend()继承。
    @Styles 装饰器 支持修饰全局和局部函数,而@Extend 装饰器只允许修饰全局函数。
相关推荐
沈剑心3 小时前
如何在鸿蒙系统上实现「沉浸式」页面?
前端·harmonyos
Georgewu3 小时前
【HarmonyOS】鸿蒙应用加载读取csv文件
前端·harmonyos
Georgewu4 小时前
【HarmonyOS】 鸿蒙图片或视频保存相册
前端·harmonyos
川石教育9 小时前
鸿蒙开发-ArkTS 中使用 filter 组件
harmonyos·鸿蒙·鸿蒙应用开发·鸿蒙开发·鸿蒙开发培训·arkts语言
李洋-蛟龙腾飞公司10 小时前
HarmonyOS Next 应用元服务开发-分布式数据对象迁移数据权限与基础数据
分布式·华为·harmonyos
Damon小智10 小时前
HarmonyOS NEXT 技术实践-实现音乐服务卡片
华为·harmonyos·鸿蒙·harmonyos next·服务卡片
play_big_knife10 小时前
鸿蒙项目云捐助第十七讲云捐助我的页面上半部分的实现
华为·harmonyos·鸿蒙·云开发·鸿蒙开发·鸿蒙next·华为云开发
枫叶丹416 小时前
【HarmonyOS之旅】HarmonyOS开发基础知识(三)
华为od·华为·华为云·harmonyos
SoraLuna21 小时前
「Mac畅玩鸿蒙与硬件47」UI互动应用篇24 - 虚拟音乐控制台
开发语言·macos·ui·华为·harmonyos
AORO_BEIDOU1 天前
单北斗+鸿蒙系统+国产芯片,遨游防爆手机自主可控“三保险”
华为·智能手机·harmonyos