相关链接
开发者文档中心 | 应用推广/应用接入和技术支持 | 华为开发者联盟
初识鸿蒙开发
了解鸿蒙开发套件
鸿蒙开发套件由七个主要组件组成。
- HarmonyOS Design:视觉设计。
- ArkTs:项目开发语言。
- ArkUI:一套UI框架。
- ArkCompiler:AOT优化,优化字节码编译,提高运行效率。
- DevEco Studio:开发编译器;IDEA。
- DevEco Testing:面向测试人员的开发工具。
- AppGallery Connect:分发运营套件,提供了很多的云函数、云数据库功能。
整个鸿蒙应用的开发步骤分为三步:
- 准备和设计。
- 代码开发。
- 测试和上架。
而我们作为开发人员,主要接触的是DevEco Studio编译器、ArkTs开发语言、ArkUI框架。
DevEco Studio安装
DevEco 的初始化安装,主要需要三个东西:nodejs、OHPM包管理器、HarmonyOS SDK。
鸿蒙的编译器DevEco Studio,需要使用管理员身份执行,不然安装sdk可能会失败。
确保最终是全绿,才可以进行下一步的项目创建
踩坑
公司电脑加密系统也会导致DevEco Studio开发环境配置不成功。比如 ohpm卡安装
解决ohpm卡安装问题
OpenHarmony Ohpm安装历程(个人踩坑,最后安装成功)-CSDN博客
设置Ohpm代理
找到FIle->Settings->Ohpm,点击Optimize config。
弹出以下弹窗:
ohpm registry :配置ohpm仓的地址信息,repo.harmonyos.com/ohpm/
如果需要设置网络代理,那么可以勾选HTTP proxy并进行配置。
关闭Windows defender
关闭defender,或者将devEco相关内容添加到排除项
运行首个项目
- 创建项目
创建首个项目,选择Create Project->Empty Ability,由此可知Ability等于安卓的Activity。
- 模拟器的配置
(和Android studio其实差不大多,摸索就行),DevEco Studio支持Remote 云控模拟器,还是很有意思的。
- 运行项目
点击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刷新。
- @Entry:用来修饰结构体组件;标记当前组件是入口组件,也就是当前组件是支持被独立访问的。即一个页面。
- 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通常用来加载图片显示。
- 声明Image组件并设置图片源
php
Image(src:string|PixelMap|Resource)
入参数据类型介绍:
-
- string格式:通常用来加载网络图片,应用访问网络需要声明网络访问权限:ohos.permission.INTERNET。
Image("....png") - PixelMap格式:可以加载像素图,常用在图片编辑中。该方式使用较复杂。
Image(pixelMapObject) - Resource格式:加载资源图片,推荐使用此种方式。
- string格式:通常用来加载网络图片,应用访问网络需要声明网络访问权限:ohos.permission.INTERNET。
-
-
- 方式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: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图标。
-
- 添加图片属性
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:文本显示组件
- 声明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的值来显示。
- 设置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组件用于输入单行文本,响应输入事件。
- 声明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)
}
})
- 添加属性和事件
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 | 电话号码输入模式。支持输入数字、+、-、*,长度不限制。 |
邮箱地址输入模式。支持数字、字母、下划线、@字符。 | |
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组件主要用来响应点击操作,可以包含子组件。
- 声明Button组件,label是按钮文字
css
Button(label?:ResourceStr)
-
- ResourceStr:可以传普通字符串,也可以传string文件中的文本。
- 按钮有两种类型,传递了文字则是文字型按钮,不传递文字则是自定义按钮。
-
-
- 文字型按钮:
-
less
Button("按钮")
-
-
- 自定义按钮,在Button内嵌套其他组件:
-
scss
Button(){
ImageView($r("app.media.search")).width(20).height(10)
}
- 添加属性和事件
scss
Button("点我")
.width(100)
.height(30)
.type(ButtonType.Normal)
.onclick(()=>{
//处理点击事件
})
- type:按钮类型。
-
- Capsule:胶囊型按钮(圆角默认为高度的一半)
- Circle:圆形按钮
- Normal:普通按钮(默认不带圆角)
- Capsule:胶囊型按钮(圆角默认为高度的一半)
LoadingProgress:加载中组件
LoadingProgress组件用于显示加载进展,比如应用的登录界面,当我们点击登录的时候,显示的"正在登录"的进度条状态。LoadingProgress的使用非常简单,只需要设置颜色和宽高就可以了。
scss
LoadingProgress()
.color(Color.Blue)
.height(60)
.width(60)
Slider:滑动条组件
- 滑动条组件声明:
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//是否反向滑动。
})
- 更多属性、事件。
-
- 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,内部对应着上中,左中右。
- anchor:锚点,即我们给组件设置的id,'container '表示指向当前组件父容器。
如:left: { anchor: 'container', align: HorizontalAlign.Start },表示当前组件的左侧坐标,以父容器作为参考对象,对齐父容器的水平方向起始位置(左侧)。
ArkUI-循环控制/FOREACH
ArkUI中,可以通过使用FOREACH函数来生成列表视图。
案例:
上述案例中,是一个商品列表,实现逻辑大致为:
- 首先抽出单条item的布局。
- 定义item所需要的数据类型Bean类。
- 定义列表,存储每项Item的数据。
- 借助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,必须唯一,用于列表内部的渲染判断
}
)
案例:实现一个列表
- 首先,我们先在组件中写好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})
}
- 定义每条Item的所需要数据类型的Bean类。
typescript
export interface ShopItemBean {
_id: number; //商品ID
_imageUrl: string; //图片地址
_name: string; //商名称
_price: number; //商品价格
}
- 创建列表的数据数组
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 },
];
- 使用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()
})
- 绘制完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() 来使用这个组件:
自定义组件案例:封装标题栏自定义组件
- 首先编写标题栏的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})
- 创建一个自定义View:CommonTitleBar
scss
/**
* 标题栏组件
*/
@Component
struct CommonTitleBar{
build(){
//..放置写好的item布局
}
}
- 放置第一步中写好的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})
}
}
- 给自定义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 })
....
- 在主页使用CommonTitleBar组件
scss
import { CommonTitleBar } from '../widget/CommonTitleBar';//导入组件
build() {
Column() {
CommonTitleBar({_title:'商品列表'})
.margin(10)//所有的自定义组件也是添加样式函数
list....
}
}
- 这样一个公共的标题栏组件就完成了。
自定义构建函数及案例:封装ListItem布局
接下来我们还想将商品列表的ListItem也进行封装,那么可以继续使用自定义组件的方式来封装;但是,我们还有一种更方便的方法,自定义构建函数。
- 在全局中,使用@Builder来标识自定义构建函数,自定义构建函数建议使用大写字母开头,因为它也可以被理解成一个匿名的自定义View组件
less
@Builder function ShopItemCard(){...}
- 在函数中,完成声明式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)
}
- 此时发现函数中 item 报红,因为拿不到item数据,所以增加一个形参:
less
@Builder function ShopItemCard(item:ShopItemBean){...}
- 在List组件中,调用自定义构建函数:
scss
List({ space: 10 }) { //列表项的间距
ForEach(this.items, (item: ShopItemBean) => {
ListItem() { //ListItem是标记,不是容器。
//此处调用自定义构建函数
ShopItemCard(item)
}
})
}
- 这样便完成了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(){...}
自定义构建函数又分为:全局和局部函数,作用域不同。
- 自定义View
- 自定义样式函数 :@Styles functionName(){...}
自定义样式函数用来集中控制公共样式,便于后期对样式统一的修改。
@Styles装饰器只能修改所有组件公共的属性,如果想使用特定组件的专用属性,可以使用@Extend()继承。
@Styles 装饰器 支持修饰全局和局部函数,而@Extend 装饰器只允许修饰全局函数。