HarmonyOs概述
HarmonyOs是一款面向万物互联时代的、全新的分布式操作系统
在传统的单设备系统能力基础上,HarmonyOs提出了基于同一套系统能力、适配多种终端形态的分布式理念,能够支持手机、平板、智能穿戴、智慧屏、车机、PC、智能音箱、耳机、AR/VR眼镜等多种终端设备,提供全场景(移动办公、运动健康、社交通信、媒体娱乐等)业务能力。
HarmonyOS有三大特征:
搭载该操作系统的设备在系统层面融为一体、形成超级终端,让设备的硬件能力可以弹性扩展,实现设备之间硬件互助,资源共享,对消费者而言,HarmonyOs能够将生活场景中的各类终端进行能力整合,实现不同终端设备之间的快速连接、能力互助、资源共享,匹配合适的设备、提供流畅的全场景体验。
面向开发者,实现一次开发,多端部署。
对应用开发者而言,HarmonyOs采用了多种分布式技术,使应用开发与不同终端设备的形态差异无关,从而让开发者能够聚焦上层业务逻辑,更加便捷、高效地开发应用。
一套操作系统可以满足不同能力的设备需求,实现统一0S,弹性部署。
对设备开发者而言,HarmonyOs采用了组件化的设计方案,可根据设备的资源能力和业务特征灵活裁剪,满足不同形态终端设备对操作系统的要求。
HarmonyOs提供了支持多种开发语言的API,供开发者进行应用开发。支持的开发语言包括ArkTS、JS(JavaScript)、C/C++、java.
1月18日,华为宣布HarmonyOS NEXT鸿蒙星河版开发者预览面向开发者开放申请。HarmonyOS NEXT命名为鸿蒙星河版,这一版本的推出意味着鸿蒙将去除安卓AOSP代码走向“独立”也就是“纯血版鸿蒙”。
2024年,鸿蒙Harmony os有望取代苹果ios,成为中国市场上第二大智能手机操作系统。
但是鸿蒙的意义不止于手机操作系统,而是开启万物智联时代的“钥匙”。
2.DevEco Studio的使用
在HarmonyOs应用开发学习之前,需要进行一些准备工作,首先需要完成开发工具DevEco Studio的下载与安装以及环境配置。
2.1 安装DevEco Studio
进入DevEco Studio下载官网,下滑找到如下页面内容。DevEco Studio提供了Windows版本和Mac版本选择,可以根据操作系统选择对应的版本进行下载。
这里以window为例进行安装
下载完成后,解压文件,双击下载"devecostudio-windows-xxxx.exe",进入DevEco Studio安装向导,点击“Next”
在如下界面选择安装路径,默认安装于"c:\program Files"下,也可以单独"Browse..."指定其他路径安装,然后点击"Next"。注意:安装路径不要出现中文。
如下安装选项界面勾选DevEco Studio后,单击"Next",直接安装完成
点击Install
进入DevEco Studio安装。
安装完成以后,单击“Finish”完成安装
2.2 配置环境
双击已安装的DevEco Studio快捷方式进入配置页面,IDE会进入配置向导,选择Agree,同意相应的条款,进入配置页。
选择不导入设置,点击ok
进入DevEco Studio配置页面,首先需要进行基础配置,包括Nodejs与Ohpm的安装路径设置,选择从
华为镜像下载至合适的路径。
点击Next进入SDK配置,设置为合适的路径
点击Next会显示"SDK Licence Agreement",阅读 相关协议后,勾选“Accept”点击“Next”
点击“Next”进行配置项预览页,进行配置项的确认.
确定完成后,单击“next”进行下一步
配置成功如图
如果出现配置异常解决方案
//清除npm缓存
npm cache clean --force
//将镜像源设置为华为镜像源
npm config set registry https://mirrors.huaweicloud.com/repository/npm/
等待配置自动下载完成,完成后,单击“Finish”,IDE会进入欢迎页,我们页就成功配置好了开发环境。
准备工作完成后,接下来将进入DevEco Studio进行工程创建和运行。
3.创建HelloWorld工程目录
双击DevEco Studio,首次打开DevEco Studio,那么进入的页面为
在欢迎页单击Create Project,进入项目创建页面,选择第一个空模板,选择以后点击Next
进入到项目配置目录,配置好了以后点击finish
4.运行HelloWorld工程
进入IDE后,我们首先了解一下基础的界面,整个IDE的界面大致上分为4个部分,分别是代码编辑区,通知栏,工程目录区,预览区
注意:
这里的预览区需要点击Previewer,首次打开如果提示
需要点击File->Settings->Build,Execution,Deployment->Build Tools->Hvigor,取消勾选最后一项,点击ok,最后Build-clean Project 预览成功
4.1 工程目录区
工程目录区的内容是我们创建的整个项目工程,包括项目的配置具体的文件,项目对应的依赖包,等等。
4.2 代码编辑区
可以打开/切换我们需要编辑的文件
按住ctrl可以进行缩放大小
可以在文件中进行具体内容的编辑
使用ctrl+s进行保存。
4.3 预览区
需要选择ets文件,右侧会出现 Previewer,需要点击Previewer进行预览效果,默认为移动端,竖屏。
4.3.1 横竖屏幕切换
点击图标切换
会显示横屏幕屏幕,再次点击,会显示竖屏
4.3.2 不同设置预览设置
点击按钮
可以看到设备列表
如果需要配置可以将整个按钮选择
点击编辑按钮
可以设置移动端参数
再次点击可取消。
4.3.4 组件预览界面
点击按钮Tt
右侧会出现组件预览界面,选择组件,会选中编辑页对应的代码
关闭点击眼睛按钮
4.3.4 自动同步效果
点击按钮Live preview,将会关闭 Live preview。
如果出现上图标识,表示编辑器更改代码,预览区不会同步更新。我们使用的时候建议打开live preview,实现同步更新。
4.4 通知栏
通知栏也叫信息栏。
run表示项目运行的信息栏。
problems 当前错误,提醒信息栏
terminal 命令行终端
log 模拟器真机运行的日志通知栏
proviewerLog 预览器日志通知栏
4.5 使用模拟器,运行Hello World
安装模拟器
使用模拟器运行应用
1.单击File > Settings > SDK(macOS系统为DevEco Studio > Preferences > SDK),下拉框选择HarmonyOS,勾选并下载Platforms下的System-image和Tools下的Emulator资源。
图1 下载System-image资源
图2 下载Emulator资源
2.单击菜单栏的Tools > Device Manager,在Local Emulator页签,单击Edit设置本地模拟器的存储路径Local emulator location,默认存储在C:\Users\Users\AppData\Local\Huawei\HarmonyOSEmulator\deployed目录(macOS为:/Users/用户/.Huawei/HarmonyOSEmulator/deployed)。
3.在Local Emulator页签中,单击右下角的New Emulator按钮,创建一个本地模拟器。
4.在创建模拟器界面,可以选择一个默认的设备;同时也可以单击New Hardware或默认设备后的克隆图标,添加一个新设备,以便自定义设备的相关参数,如尺寸、分辨率、内存等参数。
创建New Hardware时,可以修改设备的名称、尺寸、分辨率、内存等参数。
5.选择需要创建的Hardware,单击Next,可以看到模拟器的镜像信息,如API、Version、CPU/ABI等信息。
6.单击Next,核实确定需要创建的模拟器信息,同时也可以在该界面修改模拟器信息,然后单击Finish创建本地模拟器
7.在设备管理器页面,单击启动模拟器。
8.单击DevEco Studio的Run > Run'模块名称'或,或使用默认快捷键Shift+F10(macOS为Control+R)。
9.DevEco Studio会启动应用/服务的编译构建,完成后应用/服务即可运行在Local Emulator上。首次打开运行速度很慢。需要耐心等待。
5.HelloWorld工程目录
工程的目录结构如下
其中详细如下:
Appscope中存放应用全局所需要的资源文件。
entry是应用的主模块,存放HarmonyOs应用的代码、资源等,
oh_modules是工程的依赖包,存放工程依赖的源文件。
build-profile.json5是工程级配置信息,包括签名、产品配置等。
hvigorfile.ts是工程级编译构建任务脚本,hvigor是基于任务管理机制实现的一款全新的自动化构建工具,主要提供任务注册编排,工程模型管理、配置管理等核心能力。
oh-package.json5是工程级依赖配置文件,用于记录引入包的配置信息。
5.1 Appscope
在AppScope,其中有resources文件夹和配置文件app.json5
app.json5:配置信息文件
resources文件夹:资源文件夹
AppScope>resources>base中包含element和media两个文件夹
其中elemment文件夹主要存故公共的字符串、布局文件等资源.
media存放全局公共的多媒体资源文件。
5.2 entry
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文件
夹中。
5.3 app.json5
AppScope>app.json5是应用的全局的配置文件,用于存放应用公共的配置信息。
其中配置信息如下:
bundleName是包名。
vendor是应用程序供应商。
versionCode是用于区分应用版本。
versionName是版本号
icon对应于应用的显示图标。
label是应用名。
5.4 module.json5
entry>src>main>module.json5是模块的配置文件,包含当前模块的配置信息。
其中module对应的是模块的配置信息,一个模块对应一个打包后的hap包,hap包全称是HarmonyOsAbility Package,其中包含了ability、第三方库、资源和配置文件。其具体属性及其描述可以参照下表1
属性 | 描述 |
name | 该标签标识当前module的名字,module打包成hap后,表示hap的名称,标签值采用字符串表示(最大长度31个字节),该名称在整个应用要唯一。 |
type | 表示模块的类型,类型有三种,分别是entry、feature和har。 |
srcEntry | 当前模块的入口文件路径。 |
description | 当前模块的描述信息。 |
mainElement | 该标签标识hap的入口ability名称或者extension名称,只有配置为mainElement的ability或者extension才允许在服务中心露出。 |
deviceTypes | 该标签标识hap可以运行在哪类设备上,标签值采用字符串数组的表示。 |
deliveryWithinstall | 标识当前Module是否在用户主动安装的时候安装,表示该Module对应的HAP是否跟随应用一起安装。 -true:主动安装时安装。-false:主动安装时不安装。 |
installationFree | 标识当前Module是否支持免安装特性。-true:表示支持免安装特性,且符合免安装约束。-false:表示不支持免安装特性。 |
pages | 对应的是main_pages.json文件,用于配置ability中用到的page信息 |
abilities | 是一个数组,存放当前模块中所有的ability元能力的配置信息,其中可以有多个ability。 |
对于abilities中每一个ability的属性项,其描述信息如下表2.
属性 | 描述 |
name | 该标签标识当前ability的逻辑名,该名称在整个应用要唯一,标签值采用字符串表示(最大长度127个字节)。 |
srcEntry | ability的入口代码路径, |
description | ability的描述信息。 |
icon | ability的图标。该标签标识ability图标,标签值为资源文件的索引。该标签可缺省,缺省值为空。如果ability被配置为MainElement,该标签必须配置。 |
label | ability的标签名。 |
startWindowlcon | 启动页面的图标。 |
startWindowBackground | 启动页面的背景色。 |
visible | ability是否可以被其他应用程序调用,true表示可以被其它应用调用,false表示不可以被其它应用调用, |
skills | 标识能够接收的意图的action值的集合,取值通常为系统预定义的action值,也允许自定义。 |
entities | 标识能够接收Want的Entity值的集合。 |
actions | 标识能够接收的Want的Action值的集合,取值通常为系统预定义的action值,也允许自定义。 |
5.5 main_pages.json
src/main/resources/base/profile/main_pages.json文件保存的是页面page的路径配置信息,所有需要进行路由跳转的page页面都要在这里进行配置。多页面展示需要在里面进行配置。
6.ArkTs的基本组成
6.1 初识ArkTs语言
ArkTS是HarmonyOS优选的主力应用开发语言。ArkTS围绕应用开发在TypeScrip(简称TS)生态基础上做了进一步扩展,继承了TS的所有特性,是TS的超集。因此,在学习ArkTS语言之前,建议开发者具备TS语言开发能力。
当前,ArkTS在TS的基础上主要扩展了如下能力:
基本语法:ArKTS定义了声明式UI描述、自定义组件和动态扩展UI元素的能力,再配合ArkUl开发框架中的系统组件及其相关的事件方法、属性方法等共同构成了Ui开发的主体。
状态管理:ArkTS提供了多维度的状态管理机制。在UI开发框架中,与UI相关联的数据可以在组件内使用,也可以在不同组件层级间传递,比如父子组件之间、爷孙组件之间,还可以在应用全局范围内传递或跨设备传递。另外,从数据的传递形式来看,可分为只读的单向传递和可变更的双向传递。开发者可以灵活地利用这些能力来实现数据和UI的联动。
渲染控制:ArkTS提供了渲染控制的能力。条件染可根据应用的不同状态,渲染对应状态下的UI内容。循环渲染可从数据源中迭代获取数据,并在每次迭代过程中创建相应的组件。数据懒加载从数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。
未来,ArKTS会结合应用开发/运行的需求持续演进,逐步提供并行和并发能力增强、系统类型增强、分布式开发范式等更多特性。
6.2 基本语法
基本语法概述
在初步了解了ArkTS语言之后,我们以一个具体的示例来说明ArkTS的基本组成
装饰器: 用于装饰类、结构、方法以及变量,并赋予其特殊的含义。如上述示例中@Entry、@Component和@State都是装饰器,@Component表示自定义组件,@Entry表示该自定义组件为入口组件,@State表示组件中的状态变量,状态变量变化会触发UI刷新。
UI描述:以声明式的方式来描述UI的结构,例如build()方法中的代码块。
自定义组件:可复用的UI单元,可组合其他组件,如上述被@Component装饰的struct Hello。
系统组件:ArkUI框架中默认内置的基础和容器组件,可直接被开发者调用,比如示例中的Column、Text、Divider、Button。
属性方法:组件可以通过链式调用配置多项属性,如fontSize()、width()、height()、backgroundColor()等。
事件方法:组件可以通过链式调用设置多个事件的响应逻辑,如跟随在Button后面的onClick()。
系统组件、属性方法、事件方法具体使用可参考基于ArkTS的声明式开发范式。
除此之外,ArkTS扩展了多种语法范式来使开发更加便捷:
@Builder/@BuilderParam:特殊的封装UI描述的方法,细粒度的封装和复用UI描述。
@Extend/@Styles:扩展内置组件和封装属性样式,更灵活地组合内置组件。
stateStyles:多态样式,可以依据组件的内部状态的不同,设置不同样式。
实例:
//@Entry装饰器 入口组件
@Entry
//@Component装饰器 自定义组件(一个一个独立的模块)
@Component
struct Index { //自定义组件 Index组件的名称
//@State 装饰器 状态变量
//@State 装饰器 message(变量): string(类型) = 'World'(值)
@State message: string = 'World'
//UI描述
build() {
Row() {//容器组件:水平方向布局容器
Column() {//容器组件:垂直方向布局容器
Text(`Hello ${this.message}`)//基础组件:文本
.fontSize(50)//属性:文字大小
.fontWeight(FontWeight.Bold)//属性:文字加粗
Divider() //基础组件:分割线
Button('click me',{type:ButtonType.Normal})
.onClick(()=>{//事件方法
this.message = "ArkUi"
})
.height(50)
.width(100)
.backgroundColor('red')
.margin({top:20})
}
.width('100%')
}
.height('100%')
}
}
7.ArkTs声明式UI描述
7.1 创建组件
声明式UI描述
ArKTS以声明方式组合和扩展组件来描述应用程序的UI,同时还提供了基本的属性、事件和子组件配置方法,帮助开发者实现应用交互逻辑
创建组件
根据组件构造方法的不同,创建组件包含有参数和无参数两种方式,
无参数
如果组件的接口定义没有包含必选构造参数,则组件后面的"0"不需要配置任何内容。例如,Divider组件不包含构造参数:
Column() {
//文字组件 无参数
Text()
//分割线组件 无参数
Divider()
}
有参数
如果组件的接口定义包含构造参数,则在组件后面的“()“配置相应参数。
Image组件的必选参数src。
//图片组件,必须要参数,否则报错
Image('https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png')
Text组件的非必选参数content。
//文字组件 有参数
Text('HELLO')
变量或表达式也可以用于参数赋值,其中表达式返回的结果类型必须满足参数类型要求。
例如,设置变量或表达式来构造lmage和Text组件的参数。
@Entry
@Component
struct Index {
@State title:string ="你好!";
img_src:string= 'https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png';
public p:string ='p'; //公共的
private n:number=10; //私有的
protected flag:boolean = true; //受保护的
//UI描述
build() {
Row() {//容器组件:水平方向布局容器
Column() {//容器组件:垂直方向布局容器
Text(`${this.title}${this.n}`);
Image(this.img_src);
}
.width('100%')
}
.height('100%')
}
}
7.2 配置属性
属性方法以".“链式调用的方式配置系统组件的样式和其他属性,建议每个属性方法单独写一行。
配置Text组件的字体大小。
Text('text')
.fontSize(30)
配置组件的多个属性
Image(this.img_src)
.width(300)
.height(100)
除了直接传递常量参数外,还可以传递变量或表达式
Text('he11o')
.fontSize(this.size)
Image('test.jpg')
.width(this.count%2===0?100:200)//表达式定义宽度
.height(this.offset + 100)
对于系统组件,ArkUl还为其属性预定义了一些枚举类型供开发者调用,枚举类型可以作为参数传递,但必须满足参数类型要求。
例如,可以按以下方式配置Text组件的颜色和字体样式
Text('he11o')
.fontsize(20)
.fontcolor(color.Red)//枚举类型定义颜色
.fontweight(fontweight.Bo1d)//枚举类型加粗
7.3 配置事件
事件方法以”.“链式调用的方式配置系统组件支持的事件,建议每个事件方法单独写一行。
使用箭头函数配置组件的事件方法。
Button('click me')
//箭头函数配置事件,点击按钮改变title变量的值
.onClick(()=>{
this.title = '按钮点击了'
})
使用匿名函数表达式配置组件的事件方法,要求使用bind,以确保函数体中的this指向当前组件
Button('click me')
// 普通函数配置事件,this指向不对会直接写报错,所以需要设置bind属性
.onClick(function(){
this.title = "标题点击"
}.bind(this))
使用组件的成员函数配置组件的事件方法。
普通函数
//函数的定义
myHandler():void{
this.title = "myHandler"
}
...
.onClick(this.myHandler.bind(this))
箭头函数
//函数的定义
myHandler2 = ():void=>{
this.title = "myHandler2"
}
...
.onClick(this.myHandler2)
8.ArkUI-容器组件
8.1 ROW组件
沿水平方向布局容器。可以包含子组件。
Row(value?:{space?:string | number})
参数:
参数名 | 参数类型 | 必填 | 默认值 | 参数描述 |
space | string | number | 否 | 0 | 横向布局元素间距 |
属性:
名称 | 参数类型 | 默认值 | 描述 |
alignItems | VerticalAlign | VerticalAlign.Center | 在垂直方向上子组件的对齐格式, |
justifycontent | FlexAlign | FlexAlign.Start | 设置子组件在水平方向上的对齐格式。 |
示例:
//@Entry装饰器 入口组件
@Entry
//@Component装饰器 自定义组件(一个一个独立的模块)
@Component
struct Index { //自定义组件 Index组件的名称
@State title: string = "你好!";
build(){
//build中的row组件只能写一个,多个写在row内部
// {space:10} 子组件的间距
Row({space:10}){
//添加其他组件
// Button('1')
// Button('2')
// Button('3')
// 嵌套Row()
Row()
.width('30%')
.height(50)
.backgroundColor('red')
Row()
.width('30%')
.height(50)
.backgroundColor('green')
Row()
.width('30%')
.height(50)
.backgroundColor('blue')
}
.width('100%')
.height(200)
.border({width:2})
.alignItems(VerticalAlign.Top)
//垂直方向的对齐格式VerticalAlign.top /VerticalAlign.center /VerticalAlign.bottom
.justifyContent(FlexAlign.SpaceEvenly)
//水平方向的对齐方式
// FlexAlign.Start FlexAlign.center FlexAlign.End 左中右
//FlexAlign.SpaceBetween 两端对齐
//FlexAlign.SpaceAround 左右间距相同
//FlexAlign.SpaceEvenly 间隔相同
}
}
8.2 Column组件
沿垂直方向布局的容器。可以包含子组件。
Column(value?:{space?:string | number})
参数:
参数名 | 参数类型 | 必填 | 默认值 | 参数描述 |
space | string | number | 否 | 0 | 纵向布局元素间距 |
属性:
名称 | 参数类型 | 默认值 | 描述 |
alignltems | HorizontalAlign | VerticalAlign.Center | 设置子组件在水平方向上的对齐格式 |
justifyContent8+ | FlexAlign | FlexAlign.Start | 设置子组件在垂直方向上的对齐格式 |
示例:
@Entry //@Entry装饰器 入口组件
@Component ///@Component装饰器 自定义组件
struct Index { //自定义组件 Index组件的名称
@State title: string = "hello world";
//UI描述
build(){
Column({space:10}){//space:10 设置子组件的间距
//添加子组件
Button('click1')
Button('click2')
Button('click3')
}
.width('100%')
.height(200)
.border({width:2})
// 添加对齐方式(水平方向对齐)
// 左对齐
.alignItems(HorizontalAlign.Start)
// 居中对齐(默认值)
// .alignItems(HorizontalAlign.Center)
// 右对齐
// .alignItems(HorizontalAlign.End)
//添加对齐方式-垂直方向对齐
// 上对齐
.justifyContent(FlexAlign.Start)
// 居中对齐
.justifyContent(FlexAlign.Center)
// 下对齐
.justifyContent(FlexAlign.End)
// 两端对齐
.justifyContent(FlexAlign.SpaceBetween)
// 元素左右外边距相等
.justifyContent(FlexAlign.SpaceAround)
// 元素所有间距相同
.justifyContent(FlexAlign.SpaceEvenly)
}
}
8.3 Flex组件
以弹性方式布局子组件的容器组件。
子组件 可以包含子组件。
接口
Flex(value?:{ direction?: FlexDirection, wrap?: Flexwrap, justifycontent?.FlexAlign,alignItems?: ItemAlign,aligncontent?: FlexAlign })
参数:
参数名 | 参数类型 | 必填 | 默认值 | 参数描述 |
direction | FlexDirection | 否 | FlexDirection.Row | 子组件在Flex容器上排列的方向,即主轴的方向。 |
wrap | FlexWrap | 否 | FlexWrap.NoWrap | Flex容器是单行/列还是多行列排列。 |
justifyContent | FlexAlign | 否 | FlexAlign.Start | 子组件在Flex容器主轴上的对齐格式。 |
alignltems | ItemAlign | 否 | ltemAlign.Stretch | 子组件在Flex容器交叉轴上的对齐格式。 |
alignContent | FlexAlign | 否 | FlexAlign.Start | 交叉轴中有额外的空间时多行内容的对齐方式。仅在wrap为Wrap或WrapReverse下生效 |
8.3.1 direction
设置子组件在flex容器中的排列方式
语法:
Flex({direction:FlexDirection.属性值}){
}
属性值:
Row:水平方向排列
Column:垂直方向排列
RowReverse:水平方向逆向排列
ColumnReverse:垂直方向逆向排列
示例:
@Entry //@Entry装饰器 入口组件
@Component ///@Component装饰器 自定义组件
struct Index { //自定义组件 Index组件的名称
@State title: string = "hello world";
//UI描述
build(){
Column({space:10}) {
//子组件在Flex容器上排列的方向,即主轴的方向
// direction
// direction:FlexDirection.Row 横向排列
// direction:FlexDirection.Column 纵向排列
Flex({direction:FlexDirection.Row}){
Text('1')
.width('20%')
.height(50)
.backgroundColor('red')
Text('2')
.width('20%')
.height(50)
.backgroundColor(0x904523)
Text('3')
.width('20%')
.height(50)
.backgroundColor('green')
Text('4')
.width('20%')
.height(50)
.backgroundColor(0x123458)
}
.width('90%')
.height(100)
// 单词
.backgroundColor('red')
// #16进制
.backgroundColor('#F00')
// 0X + 16进制
.backgroundColor(0X0F0)
}
.width('100%')
.margin({top:10})
}
}
8.3.2 wrap
设置子组件在flex容器中的排列方式
语法:
Flex({wrap:FlexWrap.属性值}){
}
属性值:
Warp:换行
NoWarp:不换行
WrapReverse 换行逆向
示例:
@Entry //@Entry装饰器 入口组件
@Component ///@Component装饰器 自定义组件
struct Index { //自定义组件 Index组件的名称
@State title: string = "hello world";
//UI描述
build(){
Column({space:10}) {
// warp 设置flex容器单行/多行排列
// wrap:FlexWrap.Wrap 换行
//wrap:FlexWrap.NoWrap 不换行
// wrap:FlexWrap.WrapReverse 换行逆向
Flex({wrap:FlexWrap.WrapReverse}){
Text('1')
.width('50%')
.height(50)
.backgroundColor('red')
Text('2')
.width('20%')
.height(50)
.backgroundColor(0x904523)
Text('3')
.width('20%')
.height(50)
.backgroundColor('green')
Text('4')
.width('20%')
.height(50)
.backgroundColor(0x123458)
}
.width('90%')
.height(100)
// 单词
.backgroundColor('red')
// #16进制
.backgroundColor('#F00')
// 0X + 16进制
.backgroundColor(0X0F0)
}
.width('100%')
.margin({top:10})
}
}
8.3.3 justifyContent
设置子组件在flex容器中的主轴的对齐方式
语法:
Flex({justifyContent:FlexAlign.属性值}){
}
属性值:
Start 顶部对齐
Center 居中对齐
End 尾部对齐
SpaceBetween 两端对齐
SpaceAround 左右外边距相同
SpaceEvenly 间距相同
示例:
@Entry //@Entry装饰器 入口组件
@Component ///@Component装饰器 自定义组件
struct Index { //自定义组件 Index组件的名称
@State title: string = "hello world";
//UI描述
build(){
Column({space:10}) {
// justifyContent 设置flex容器在主轴的对齐方式
// justifyContent:FlexAlign.Start 顶部对齐
// justifyContent:FlexAlign.Center 居中对齐
// justifyContent:FlexAlign.End 尾部对齐
// justifyContent:FlexAlign.SpaceBetween 两端对齐
// justifyContent:FlexAlign.SpaceAround 左右外边距相同
// justifyContent:FlexAlign.SpaceEvenly 间距相同
Flex({justifyContent:FlexAlign.SpaceEvenly}){
Text('1')
.width('20%')
.height(50)
.backgroundColor('red')
Text('2')
.width('20%')
.height(50)
.backgroundColor(0x904523)
Text('3')
.width('20%')
.height(50)
.backgroundColor('green')
Text('4')
.width('20%')
.height(50)
.backgroundColor(0x123458)
}
.width('90%')
.height(100)
// 单词
.backgroundColor('red')
// #16进制
.backgroundColor('#F00')
// 0X + 16进制
.backgroundColor(0X0F0)
}
.width('100%')
.margin({top:10})
}
}
8.3.4 alignltems
设置子组件在flex容器中的交叉轴的对齐方式
语法:
Flex({alignItems:ItemAlign.属性值}){
}
属性值:
Start 顶部对齐
Center 居中对齐
End 底部对齐
Stretch 伸展,拉伸到容器尺寸
示例:
@Entry //@Entry装饰器 入口组件
@Component ///@Component装饰器 自定义组件
struct Index { //自定义组件 Index组件的名称
@State title: string = "hello world";
//UI描述
build(){
Column({space:10}) {
// alignltems 设置子组件在flex容器中的交叉轴的对齐方式
// alignItems:ItemAlign.Start 顶部对齐
// alignItems:ItemAlign.Center 居中对齐
// alignItems:ItemAlign.End 底部对齐
// alignItems:ItemAlign.Stretch 伸展,拉伸到容器尺寸
Flex({alignItems:ItemAlign.Stretch}){
Text('1')
.width('20%')
.height(30)
.backgroundColor('red')
Text('2')
.width('20%')
.height(40)
.backgroundColor(0x904523)
Text('3')
.width('20%')
.height(50)
.backgroundColor('green')
}
.width('90%')
.height(100)
// 单词
.backgroundColor('red')
// #16进制
.backgroundColor('#F00')
// 0X + 16进制
.backgroundColor(0X0F0)
}
.width('100%')
.margin({top:10})
}
}
8.4 List
列表包含一系列相同宽度的列表项。适合连续、多行呈现同类数据,例如图片和文本。
子组件
包含ListItem子组件
接口
List(value?:{space?: number | string, initialIndex?: number, scroller?:Scro11er})
8.5 ListItem
8.6 Tabs
通过页签进行内容视图切换的容器组件,每个页签对应一个内容视图。
子组件
包含子组件Tabcontent。
接口说明
Tabs(value?: {barPosition?: BarPosition, index?: number, controller?:Tabscontro1ler})
参数:
参数名 | 参数类型 | 必填 | 默认值 | 参数描述 |
barPosition | BarPosition | 否 | BarPosition.Start | 指定页签位置来创建Tabs容器组件。 |
index | number | 否 | 0 | 指定初次初始页签索引。 |
controller | TabsController | 否 | - | 设置Tabs控制器。 |
BarPosition枚举说明
名称 | 描述 |
Start | vertical属性方法设置为true时,页签位于容器左侧;vertical属性方法设置为false时,页签位于容器顶部。 |
End | vertical属性方法设置为true时,页签位于容器右侧;vertical属性方法设置为false时,页签位于容器底部。 |
属性
不支持触摸热区设置。
名称 | 参数类型 | 默认值 | 描述 |
vertical | boolean | false | 设置为false是为横向Tabs,设置为true时为纵向Tabs。 |
scrollable | BarMode | true | 设置为true时可以通过滑动页面进行页面切换,为false时不可滑动切换页面。 |
barMode | BarMode | BarMode.Fixed | TabBar布局模式,具体描述见BarMode枚举说明。 |
barWidth | Length | - | TabBar的宽度值。 |
barHeight | Length | - | TabBar的高度值。 |
animationDuration | number | 200 | TabContent滑动动画时长, |
BarMode枚举说明
名称 | 描述 |
Scrollable | TabBar使用实际布局宽度,超过总长度后可滑动。 |
Fixed | 所有TabBar平均分配宽度。 |
事件
名称 | 描述 |
onChange(event: (index: number)=> void) | Tab页签切换后触发的事件。-index:tab标签的索引值。 |
TabsController
Tabs组件的控制器,用于控制Tabs组件进行页签切换。
导入对象
controller:TabsController = new TabsController()
changeindex
控制Tabs切换到指定页签
changelndex(value: number): void
8.7 TabContent
仅在Tabs中使用,对应一个切换页签的内容视图。
接口
Tabcontent()
属性
名称 | 参数类型 | 描述 |
tabBar | string | Resource | {icon?:string | Resource,text?:string | Resource} | CustomBuilder8+ | 设置TabBar上显示内容。CustomBuilder:构造器,内部可以传入组件(API8版本以上适用)。说明:如果icon采用svg格式图源,则要求svg图源删除其自有宽高属性值。如采用带有自有宽高属性的svg图源,icon大小则是svg本身内置的宽高属性值大小。 |
8.8 Swiper
滑块视图容器,提供子组件滑动轮播显示的能力。
子组件
可以包含子组件。
接口
Swiper(controller?:SwiperController)
参数
参数名 | 参数类型 | 必墳 | 参数描述 |
controller | SwiperController | 否 | 给组件绑定一个控制器,用来控制组件翻页 |
属性
名称 | 参数类型 | 描述 |
index | number | 设置当前在容器中显示的子组件的索引值。默认值:0 |
autoPlay | boolean | 子组件是否自动播放,自动播放状态下导航点不可操作。默认值:false |
interval | number | 使用自动播放时播放的时间间隔,单位为毫秒。默认值:3000 |
indicator | boolean | 是否启用导航点指示器。默认值:true |
loop | boolean | 是否开启循环。设置为true时表示开启循环,在LazyForEach懒循环加载模式下加载的组件数量建议大于5个。默认值:true |
duration | number | 子组件切换的动画时长,单位为毫秒。默认值:400 |
vertical | boolean | 是否为纵向滑动。默认值:false |
itemSpace | number string | 设置子组件与子组件之间间隙。默认值:0 |
displayMode | SwiperDisplayMode | 设置子组件显示模式。默认值:SwiperDisplayMode.Stretch |
cachedCount8+ | number | 设置预加载子组件个数。默认值:1 |
disableSwipe8+ | boolean | 禁用组件滑动切换功能,默认值:false |
displayCount8+ | number|string | 设置一页中显示子组件的个数,设置为“auto”时等同于 SwiperDisplayMode.AutoLinear的显示效果。默认值:1 |
effectMode8+ | EdgeEffect | 设置滑动到边缘时的显示效果。默认值:EdgeEffect.Spring |
curve8+ | Curve|string | 设置Swiper的动画曲线,默认为淡入淡出曲线 |
indicatorStyle8+ | {left?: Length,top?:Length,right?:Length,bottom?: Length,size?:Length,mask?: boolean,color?:ResourceColor,selectedColor?:ResourceColor} | 设置导航点样式:-left: 设置导航点距离Swiper组件左边的距离。- top: 设置导航点距离Swiper组件顶部的距离。-right: 设置导航点距离Swiper组件右边的距离。-bottom:设置导航点距离Swiper组件底部的距离。-size:设置导航点的直径。-mask: 设置是否显示导航点蒙层样式。一color: 设置导航点的颜色。-selectedcolor:设置选中的导航点的颜色 |
8.9 Grid
网格容器,由“行”和“列”分割的单元格所组成,通过指定”项目”所在的单元格做出各种各样的布局。
子组件
包含GridItem子组件。
接口
Grid(scroller?:scroller)
参数:
参数名 | 参数类型 | 必填 | 参数描述 |
scroller | Scroller | 否 | 可滚动组件的控制器,用于与可滚动组件进行绑定 |
属性
名称 | 参数类型 | 描述 |
columnsTemplate | string | 设置当前网格布局列的数量,不设置时默认1列。例如,"1fr 1fr 2fr'是将父组件分3列,将父组件允许的宽分为4等份,第一列占1份,第二列占1份,第三列占2份。默认值:'1fr' |
rowsTemplate | string | 设置当前网格布局行的数量,不设置时默认1行。例如,"1fr 1fr 2fr”是将父组件分三行,将父组件允许的高分为4等份,第一行占1份,第二行占一份,第三行占2份。默认值:'1fr |
columnsGap | Length | 设置列与列的间距。默认值:0 |
rowsGap | Length | 设置行与行的间距,默认值:0 |
scrollBar | BarState | 设置滚动条状态。默认值:BarState.0ff |
scrollBarColor | string | number | Color | 设置滚动条的颜色。 |
scrollBarWidth | string | number | 设置滚动条的宽度。 |
cachedCount | number | 设置预加载的Gridltem的数量,具体使用可参考减少应用白块说明。默认值:1 |
editMode8+ | boolean | 是否进入编辑模式,进入编辑模式可以拖拽Grid组件内部Gridltem。默认值:false |
8.10 GridItem
9.ArkUI-基础组件
9.1 Text组件
显示一段文本的组件,可以包含Span子组件
接口
Text(content?:string | Resource)
属性:
名称 | 参数类型 | 描述 |
textAlign | TextAlign | 设置多行文本的文本对齐方式。默认值:TextAlign.Start |
textOverflow | {overflow: TextOverfow} | 设置文本超长时的显示方式。默认值:{overflow: TextOverflow.Clip} 说明:文本截断是按字截断。 例如:英文以单词为最小单位进行截断,若需要以字母为单位进行截断,可在字母间添加零宽空格:\u200B。 |
maxLines | number | 设置文本的最大行数。默认值:Infinity |
lineHeight | string l number l Resource | 设置文本的文本行高,设置值不大于0时,不限制文本行高,自适应字体大小,Length为number类型时单位为fp。 |
decoration | {type:TextDecorationType,color?:ResourceColor} | 设置文本装饰线样式及其颜色。 默认值:{type: TextDecorationType.None,color:Color.Black} |
baselineOffset | number l string | 设置文本基线的偏移量。 |
letterSpacing | number | string | 设置文本字符间距。 |
minFontSize | number | string | Resource | 设置文本最小显示字号。 |
maxFontSize | number | string | Resource | 设置文本最大显示字号. |
textCase | TextCase | 设置文本大小写。默认值: TextCase.Normal |
9.1.1 fontSize尺寸
语法:
Text('hello world')
.fontSize(40)
9.1.2 fontWeight粗细
语法:需要使用枚举FontWeight设置属性
Text('hello world')
.fontWeight(FontWeight.属性)
属性值:
Bold 加粗
Normal 不加粗
9.1.3 color文本颜色
语法:
Text('hello world')
.fontColor(颜色)
颜色属性:
单词:red
16进制:#16进制代码
0X:0X + 16进制代码
枚举color:Color.颜色
示例:
Text('hello world')
.fontColor('red')
.fontColor('#f00')
.fontColor(0Xf12345)
.fontColor(Color.Orange)
9.1.4 内外间距
内间距
Text('hello world')
.padding({top:10,left:20})
外间距
Text('hello world')
.margin({top:20})
9.1.5 border边框
语法:
Text('hello world')
.border({width:1}) //边框宽度
.border({width:1,color:0X654321,style:BorderStyle.Solid})//边框宽度 边框颜色 边框实线
.border({width:1,color:0X654321,style:BorderStyle.Dotted})//边框宽度 边框颜色 边框虚线
.border({width:1,color:0X654321,style:BorderStyle.Dashed})//边框宽度 边框颜色 边框点线
9.1.6 lineHeight 行高
语法:
Text('hello world')
.lineHeight(100)
9.1.7 decoration 修饰线
语法:
Text('hello world')
.decoration({type:TextDecorationType.属性,color:'颜色'})
//LineThrough 删除线
// Underline 下划线
// Overline 上划线
属性值:
LineThrough 删除线
Underline 下划线
Overline 上划线
示例:
Text('hello world')
//设置文本装饰线
//LineThrough 删除线
// Underline 下划线
// Overline 上划线
.decoration({type:TextDecorationType.Underline,color:"red"})
baselineOffset
9.1.8 baselineOffset 偏移量
语法:
Text('hello world')
.baselineOffset(50)
9.1.9 letterSpacing 文字间隔
语法:
Text('hello world')
.letterSpacing(10)
9.1.10 TextCase 设置大小写
语法:
Text('hello world')
.textCase(TextCase.属性)
示例:
Text('hello world')
//TextCase.UpperCase 大写
.textCase(TextCase.UpperCase)
//TextCase.UpperCase 小写
.textCase(TextCase.LowerCase)
//TextCase.Normal 默认
.textCase(TextCase.Normal)
9.1.11 多行文本
9.1.11.1 textAlign对齐方式
语法:
Text('英文以单词为最小单位进行截断,若需要以字母为单位进行截断,可在字母间添加零宽空格')
.fontSize(40)
.fontWeight(FontWeight.Bold)
// 多行文本对齐方式textAlign
//左
.textAlign(TextAlign.Start)
// 中
.textAlign(TextAlign.End)
//右
.textAlign(TextAlign.Center)
9.1.11.2 maxLines 最大行数
语法:
Text('英文以单词为最小单位进行截断,若需要以字母为单位进行截断,可在字母间添加零宽空格')
.fontSize(40)
.fontWeight(FontWeight.Bold)
.maxLines(3)
9.1.11.2 textOverflow 截取方式
语法:
Text('英文以单词为最小单位进行截断,若需要以字母为单位进行截断,可在字母间添加零宽空格')
.fontSize(40)
.fontWeight(FontWeight.Bold)
.maxLines(3)
// 文本截断方式
//直接截断
.textOverflow({overflow:TextOverflow.Clip})
//截断部分...
.textOverflow({overflow:TextOverflow.Ellipsis})
9.2 TextInput组件
输入框组件,文本框 ,密码框,数字框,邮箱框
语法:
TextInput({placeholder:"请输入"})
属性:
输入框类型:type(InputType.类型)
类型:Normal 默认文本 Number 数字 PhoneNumber 电话 Email 邮箱 Password 密码
提示语颜色:placeholderColor(0X999999)
最大输入长度:maxLength(6)
事件:
TextInput({placeholder:"请输入用户名"})
.onChange(data=>{//输入的内容发生变化时,触发回调,data代表输入的内容
this.user = data;
console.log('user:'+data)
})
9.3TextinputController8+
9.4 Button
按钮组件,可快速创建不同样式的按钮
方法1:
Button(options?: {type?: ButtonType, stateEffect?: boolean})
参数:
参数名 | 参数类型 | 必填 | 参数描述 |
type | ButtonType | 否 | 描述按钮显示样式。默认值:ButtonType.Capsule |
stateEffect | boolean | 否 | 按钮按下时是否开启按压态显示效果,当设置为false时,按压效果关闭。默认值:true |
方法2
Button(label?: ResourceStr, options?: { type?: ButtonType, stateEffect?: boolean })
使用文本内容创建相应的按钮组件,此时Button无法包含子组件。
参数名 | 参数类型 | 必填 | 参数描述 |
label | ResourceStr | 否 | 按钮文本内容。 |
options | {type?: ButtonType,stateEffect?: boolean} | 否 | 见方法1参数说明。 |
9.5 Image
图片组件,支持本地图片和网络图片的渲染展示,
接口
Image(src:string | pixelMap | Resource)
参数:
参数名 | 参数类型 | 必填 | 默认值 | 参数描述 |
src | string PixelMap Resource | 是 | - | 图片的路径,支持本地图片和网络图片 图片格式包括png、jpg、bmp、svg和gif。 使用相对路径引用图片资源 例如Image("common/test.jpg") 不支持该lmage组件被跨包/跨模块调用,建议使用$r方式来管理需全局使用的图片资源 - 支持Base64字符串6+。格式为data:image/[png | jpeg | bmp | webp];base64, [base64 data], 其中[base64 data]为Base64字符串数据。 - 支持dataability://的路径前缀,用于访问通过data ability提供的图片路径。 |
引入网络图片示例
Image('https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png')
本地资源图片放置到 entey>main>resource>base>media中,可以看到icon.png文件,引入该图片地址
Image($r('app.media.icon'))
图片缩放
当图片的原始大小与 Image 组件不同时,可通过 objectFit()方泛来设置图片的显示效果。该方法的参数类型为 ImageFit 枚举类型,可选的枚举值如下
ImageFit.None:保持原有尺寸显示,不做任何缩放,超出显示区域的部分不显示
ImageFit.contain:保持宽高比进行缩小或者放大,使得显示区或刚好包含整个图片
ImageFit.Cover:保持宽高比进行缩小或者放大,使得图片刚好完成覆盖显示区域。
ImageFit.Fill:不保持宽高比进行放大缩小,使得图片充满显示区域
ImageFit.ScaleDown:保持宽高比进行缩小或不变(不会放大),使得图片完全显示在显示区域内。
ImageFit.Auto:自适应显示
示例
Image(item.img)
.objectFit(ImageFit.Cover)//对图片进行剪切
.aspectRatio(1.3) //设置宽高比
10.购物应用APP实战
10.1 项目需求
1.一共4个模块,登录页,首页,精选,我的
2.单击登录跳转到首页展示,首页模块有轮播、导航、列表
3.精选商城,显示列表。
4.单击我的,显示“誉天教育’信息及列表、返回登录页按钮。
支持设备:华为手机或运行在DevEco Studio上的华为手机设备模拟器
支持:API version9,需要使用DevEco Studio进行边距运行
10.2 项目环境搭建
如果你是首次打开DevEco Studio,那么首先进入欢迎页
点击File>New>Create Project
选择空的模板
给项目取一个名称
点击finish创建完成。
10.3 分析项目模块
10.3.1 设置UI页面
打开文件夹目录Shpping>entry>src>main>ets>pages
创建登录模块:LoginPage.ets
创建首页:MainPage.ets
首页
@Entry
@Component
struct Main {
@State message: string = '首页'
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.width('100%')
}
.height('100%')
}
}
登录页
@Entry
@Component
struct Login {
@State message: string = '登录'
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.width('100%')
}
.height('100%')
}
}
10.3.2 设置页面跳转
打开目录Shpping>entry>src>main>resource>base>profile>main_pages.json
{
"src": [
"pages/LoginPage",
"pages/MainPage"
]
}
10.4 开发登录页模块
1. 基础组件-Image组件添加图片
Image($r('app.media.icon'))
2.像素单位
提供了4中像素单位,框架采用vp为基准数据单位
名称 | 描述 |
px | 屏幕物理像素单位、 |
vp | 屏幕密度相关像素,根据屏幕像素密度转换为屏幕物理像素,当数值不带单位时,默认单位vp。 |
fp | 字体像素,与vp类似适用屏幕密度变化,随系统字体大小设置变化。 |
lpx | 视窗逻辑像素单位,Ipx单位为实际屏幕宽度与逻辑宽度(通过designWidth配置)的比值。如配置designWidth为720时,在实际宽度为1440物理像素的屏幕上,1lpx为2px大小 |
3.像素单位转换
提供其他单位与px单位互相转换的方法
名称 | 描述 |
vp2px(value :number) :number | 将vp单位的数值转换为以px为单位的数值 |
px2vp(value :number):number | 将px单位的数值转换为以vp为单位的数值 |
fp2px(value :number):number | 将fp单位的数值转换为以px为单位的数值 |
px2fp(value :number):number | 将px单位的数值转换为以fp为单位的数值 |
lpx2px(value :number):number | 将Ipx单位的数值转换为以px为单位的数值 |
px2lpx(value :number):number | 将px单位的数值转换为以Ipx为单位的数值 |
4.设置文本
@Entry
@Component
struct Login {
@State message: string = '欢迎登录'
build() {
Row() {
Column() {
//logo
Image($r('app.media.icon'))
.width('80vp')
.height('80vp')
.margin({bottom:'20vp'})
//欢迎登录
Text(this.message)
.fontSize('30fp')
.fontWeight(FontWeight.Bold)
}
.width('100%')
}
.height('100%')
}
}
5.用户名和密码输入框
@State user:string =''
@State password:string =''
//用户名和密码
TextInput({placeholder:"请输入用户名"})
.placeholderColor(0X999999)
.maxLength(6)
.padding('12vp')
.margin('10vp')
.onChange(data=>{//输入的内容发生变化时,触发回调,data代表输入的内容
this.user = data;
console.log('user:'+data)
})
TextInput({placeholder:"请输入密码"})
.type(InputType.Password)//输入框类型
.placeholderColor(0X999999)
.maxLength(6)
.padding('12vp')
.margin('10vp')
.onChange(data=>{//输入的内容发生变化时,触发回调,data代表输入的内容
this.password = data;
console.log('password:'+data)
})
6.文字提示
Row(){
Text('短信验证码登录')
.fontSize('18fp')
.fontColor(Color.Blue)
Text('忘记密码')
.fontSize('18fp')
.fontColor(Color.Blue)
}
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
7.登录按钮
//登录按钮
//type设置状态:Circle 圆形 Capsule 胶囊 Normal 默认
//stateEffect 按压效果去除 默认为true
Button('登陆',{type:ButtonType.Capsule,stateEffect:false})
.width('90%')
.margin('10vp')
.onClick(()=>{
//具体逻辑
})
8.其他内容
Row(){
Text('其他登录方式')
.fontSize('18fp')
.fontColor(Color.Blue)
}
.width('100%')
.justifyContent(FlexAlign.Center)
9.3个按钮
Button('微信登陆',{type:ButtonType.Circle,stateEffect:false})
.width('80vp')
.height('80vp')
Button('QQ',{type:ButtonType.Circle,stateEffect:false})
.width('80vp')
.height('80vp')
Button('手机',{type:ButtonType.Circle,stateEffect:false})
.width('80vp')
.height('80vp')
}
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
.margin({top:'20vp'})
10.5 @Extend修饰器
我们写好的登陆了页面存在很多重复的样式代码。针对重复的代码可以使用装饰器进行优化
@Extend修饰器:扩展组件样式
语法:
@Extend(组件描述名称) function 样式名称(){}
示例:
@Entry
@Component
struct Login {
@State message: string = '欢迎登录';
@State user:string ='';
@State password:string ='';
build() {
Row() {
Column() {
//logo
Image($r('app.media.icon'))
.width('80vp')
.height('80vp')
.margin({bottom:'20vp'})
//欢迎登录
Text(this.message)
.fontSize('30fp')
.fontWeight(FontWeight.Bold)
//用户名和密码
TextInput({placeholder:"请输入用户名"})
.inputStyle()
.onChange(data=>{//输入的内容发生变化时,触发回调,data代表输入的内容
this.user = data;
console.log('user:'+data)
})
TextInput({placeholder:"请输入密码"})
.type(InputType.Password)//输入框类型
.inputStyle()
.onChange(data=>{//输入的内容发生变化时,触发回调,data代表输入的内容
this.password = data;
console.log('password:'+data)
})
//文字提示
Row(){
Text('短信验证码登录')
.fontSize('18fp')
.fontColor(Color.Blue)
Text('忘记密码')
.fontSize('18fp')
.fontColor(Color.Blue)
}
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
//登录按钮
//type设置状态:Circle 圆形 Capsule 胶囊 Normal 默认
//stateEffect 按压效果去除 默认为true
Button('登陆',{type:ButtonType.Capsule,stateEffect:false})
.width('90%')
.margin('10vp')
.onClick(()=>{
//具体逻辑
})
//其他内容
Row(){
Text('其他登录方式')
.fontSize('18fp')
.fontColor(Color.Blue)
}
.width('100%')
.justifyContent(FlexAlign.Center)
//3个按钮
Row(){
Button('微信登陆',{type:ButtonType.Circle,stateEffect:false})
.btnWidthStyle()
Button('QQ',{type:ButtonType.Circle,stateEffect:false})
.btnWidthStyle()
Button('手机',{type:ButtonType.Circle,stateEffect:false})
.btnWidthStyle()
}
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
.margin({top:'20vp'})
}
.width('100%')
}
.height('100%')
}
}
//添加公共的样式函数
// @Extend 装饰器:扩展组件样式
// @Extend(组件描述名称) function 样式名称(){}
@Extend(TextInput) function inputStyle() {
.placeholderColor(0X999999)
.maxLength(6)
.padding('12vp')
.margin('10vp')
}
@Extend(Button) function btnWidthStyle() {
.width('80vp')
.height('80vp')
}
10.6 ForEach循环
登录页面的三个按钮除了文本不一样,其他都是一样的。所以可使用ForEach循环
语法
ForEach('',()=>{},()=>{})
示例:
private otherDate:Array<string> = ["微信","QQ","手机"]
Row(){
ForEach(this.otherDate,(item:string)=>{
Button(item,{type:ButtonType.Circle,stateEffect:false})
.btnWidthStyle()//公共样式
},(item:string)=>JSON.stringify(item))
// Button('微信',{type:ButtonType.Circle,stateEffect:false})
// .btnWidthStyle()
// Button('QQ',{type:ButtonType.Circle,stateEffect:false})
// .btnWidthStyle()
// Button('手机',{type:ButtonType.Circle,stateEffect:false})
// .btnWidthStyle()
}
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
.margin({top:'20vp'})
10.7 @ohos.router页面路由
ohos:翻译表示”OpenHarmonyOS“,使用需要在页面顶部引入路由
语法:
import router from '@ohos.router';
router.pushUrl({
url:'pages/LoginPage'
}).catch((error:Error)=>{
//错误提示
})
示例:
import router from '@ohos.router';
@Entry
@Component
struct Main {
@State message: string = '首页'
build() {
Row() {
Column() {
Text(this.message)
.fontSize('30fp')
.fontWeight(FontWeight.Bold)
Button('返回登录页',{type:ButtonType.Capsule,stateEffect:false})
.width('90%')
.margin('10vp')
.onClick(()=>{
//具体逻辑
//路由跳转
router.pushUrl({
url:'pages/LoginPage'
}).catch((error:Error)=>{
//错误提示
})
})
}
.width('100%')
}
.height('100%')
}
}
10.8 @ohos.promptAction 弹窗
点击登录按钮的逻辑判断:没有输入用户名和密码,不允许跳转。添加判断条件。
语法:
import promptAction from '@ohos.promptAction';
promptAction.showToast({
message:"用户名或密码不能为空",//显示内容 必填
duration:3000, //持续时间 选填
bottom:'60vp' //弹窗离底部的距离 选填
})
示例:
import promptAction from '@ohos.promptAction';
Button('登陆',{type:ButtonType.Capsule,stateEffect:false})
.width('90%')
.margin('10vp')
.onClick(()=>{
//判断
if(this.user=='' || this.password==''){
//不能跳转,同时给出弹窗提示
promptAction.showToast({
message:"用户名或密码不能为空",
duration:3000, //持续时间
bottom:'60vp' //弹窗离底部的距离
})
}else{
//路由跳转
router.pushUrl({
url:'pages/MainPage'
}).catch((error:Error)=>{
//错误提示
})
}
})
10.9 Tabs底部导航
示例
@Entry
@Component
struct Main {
@State message: string = '首页'
//Tabs组件的控制器,用于控制Tabs组件进行页签切换。
private controller:TabsController = new TabsController()
build() {
Row() {
// BarPosition.Start 左侧或顶部
// .vertical(true) true 页签位于左侧 false 页签位于顶部
// BarPosition.End 右侧或底部
// .vertical(true) true 页签位于右侧 false 页签位于底部
Tabs({barPosition:BarPosition.End}){
TabContent(){
Column()
.width('100%')
.height('100%')
.backgroundColor(Color.Red)
}.tabBar('首页')
TabContent(){
Column()
.width('100%')
.height('100%')
.backgroundColor(Color.Pink)
}.tabBar('精选')
TabContent(){
Column()
.width('100%')
.height('100%')
.backgroundColor(Color.Orange)
}.tabBar('我的')
}
.vertical(false)
.barWidth('100%') //设置宽度
.barHeight(100)//设置高度
.animationDuration(400)
}
.height('100%')
}
}
10.10 if...else条件渲染
创建首页,精选,我的页面
在pages同级别创建文件夹view
首页:HomePage
精选:ChoicePage
我的:MinePage
首页
@Component
// 导出
export default struct Home {
@State message: string = '首页'
build() {
Row() {
Text(this.message)
}
.height('100%')
}
}
精选
@Component
// 导出
export default struct Choice {
@State message: string = '精选'
build() {
Row() {
Text(this.message)
}
.height('100%')
}
}
我的
@Component
// 导出
export default struct Mine {
@State message: string = '我的'
build() {
Row() {
Text(this.message)
}
.height('100%')
}
}
优化tabbar使用forEach获取元素,使用if判断索引,显示不同的page
// 导入组件
import Home from '../view/HomePage'
import Choice from '../view/ChoicePage'
import Mine from '../view/MinePage'
@Entry
@Component
struct Main {
@State message: string = '首页'
//Tabs组件的控制器,用于控制Tabs组件进行页签切换。
private controller: TabsController = new TabsController()
private items:Array<{title:string}> = [
{title:"首页"},
{title:"精选"},
{title:"我的"}
]
build() {
Row() {
// BarPosition.Start 左侧或顶部
// .vertical(true) true 页签位于左侧 false 页签位于顶部
// BarPosition.End 右侧或底部
// .vertical(true) true 页签位于右侧 false 页签位于底部
Tabs({barPosition:BarPosition.End}){
ForEach(this.items,(item:{title:string},index:number)=>{
TabContent(){
Column() {
// Text(item.title)
//加载不同的组件
if(index===0){
Home()
}else if(index===1){
Choice()
}else {
Mine()
}
}
}.tabBar(item.title)
})
}
.vertical(false)
.barWidth('100%') //设置宽度
.barHeight(100)//设置高度
.animationDuration(400)
}
.height('100%')
}
}
10.11 @Builder自定义构建函数
实现tabbar图标切换
// 导入组件
import Home from '../view/HomePage'
import Choice from '../view/ChoicePage'
import Mine from '../view/MinePage'
@Entry
@Component
struct Main {
@State message: string = '首页'
//Tabs组件的控制器,用于控制Tabs组件进行页签切换。
private controller: TabsController = new TabsController()
private items:Array<{title:string,iconSelected:Resource,iconNormal:Resource}> = [
{title:"首页",iconSelected:$r('app.media.ic_hone_selected'),iconNormal:$r('app.media.ic_home_normal')},
{title:"精选",iconSelected:$r('app.media.ic_discover_selected'),iconNormal:$r('app.media.ic_discover_normal')},
{title:"我的",iconSelected:$r('app.media.ic_mine_selected'),iconNormal:$r('app.media.ic_mine_normal')}
]
@State currentIndex:number =0;//当前索引下标
// @Builder 装饰器 自定义构建函数
@Builder TabBuilder(title,iconSelected,iconNormal,index){
Column(){
Image(this.currentIndex === index?iconSelected:iconNormal)
.width('25vp')
.height('25vp')
Text(title)
.fontColor(this.currentIndex === index?"#007DEE":"#182431")
.fontSize('14fp')
.fontWeight(500)
}
.width('100%')
}
build() {
Row() {
// BarPosition.Start 左侧或顶部
// .vertical(true) true 页签位于左侧 false 页签位于顶部
// BarPosition.End 右侧或底部
// .vertical(true) true 页签位于右侧 false 页签位于底部
Tabs({barPosition:BarPosition.End}){
ForEach(this.items,(item:{title:string,iconSelected:Resource,iconNormal:Resource},index:number)=>{
TabContent(){
Column() {
// Text(item.title)
//加载不同的组件
if(index===0){
Home()
}else if(index===1){
Choice()
}else {
Mine()
}
}
}.tabBar(this.TabBuilder(item.title,item.iconSelected,item.iconNormal,index))
})
}
.vertical(false)
.barWidth('100%') //设置宽度
.barHeight(100)//设置高度
.animationDuration(400)
.onChange((index:number)=>{
this.currentIndex = index;
})
}
.height('100%')
}
}
10.12 首页组件导入导出
创建文件夹components
entry\src\main\ets\view\components
在该目录下创建3个ets文件
SwiperExample.ets 轮播
@Component
// 导出
export default struct SwiperExample {
@State message: string = '轮播组件'
build() {
Row() {
Text(this.message)
}
.height('100%')
}
}
NavExample.ets 导航
@Component
// 导出
export default struct NavExample {
@State message: string = '导航组件'
build() {
Row() {
Text(this.message)
}
.height('100%')
}
}
ListExample.ets 列表
@Component
// 导出
export default struct ListExample {
@State message: string = '列表组件'
build() {
Row() {
Text(this.message)
}
.height('100%')
}
}
在HomePage首页引入三个组件
import SwiperExample from './components/SwiperExample'
import NavExample from './components/NavExample'
import ListExample from './components/ListExample'
@Component
// 导出
export default struct Home {
build() {
Scroll(){
Column(){
//轮播图
SwiperExample()
// 导航
NavExample()
// 列表
ListExample()
}
.width('100%')
}
.backgroundColor('#F1F3F5')
.scrollBar(BarState.Auto) //设置滚动条状态
}
}
10.13 Swiper轮播组件
SwiperExample.ets 轮播
@Component
// 导出
export default struct SwiperExample {
@State SwiperData:Array<Resource> = [
$r('app.media.banner_1'),
$r('app.media.banner_2'),
$r('app.media.banner_3'),
]
build() {
Column(){
Swiper(){
ForEach(this.SwiperData,(item:Resource)=>{
Image(item)
.width('95%')
.height('160vp')
})
}
.index(0) //初始化索引值
.autoPlay(true) //自动播放
.interval(2000)//间隔时间2,默认3s
.loop(true) //是否开启循环 默认true
.duration(2000) //切换动画时长
.itemSpace(0) //图片间隙 默认0
.indicator(true) //是否启动导航点指示器
}
.width('100%')
.margin({top:'5vp'})
}
}
10.14 数据类型接口定义
entry\src\main\ets 创建文件夹mode
在mode中创建文件types.ets 设置定义的文件类型
在types.ets文件中设置接口
// 设置接口,并使用export导出
// ?表示可选的
export interface ItemType {
id?:number;
title?:string | Resource;
img?:string | Resource
}
在NavExample.ets设置导航数据
// 引入类型
import {ItemType} from '../../mode/types'
@Component
// 导出
export default struct NavExample {
@State message: string = '导航组件'
private navData:Array<ItemType> =[
{
title:'喜欢',
img:$r('app.media.ic_like')
},
{
title:'排名',
img:$r('app.media.ic_ranking')
},
{
title:'消息',
img:$r('app.media.ic_msg')
},
{
title:'购物车',
img:$r('app.media.ic_gwc')
},
{
title:'礼物',
img:$r('app.media.ic_gift')
},
{
title:'卡券',
img:$r('app.media.ic_coupons')
},
{
title:'收藏',
img:$r('app.media.ic_collect')
},
{
title:'设置',
img:$r('app.media.ic_setting')
}
]
build() {
Row() {
Text(this.message)
}
.height('100%')
}
}
10.15 Grid组件
在NavExample.ets导航菜单,多行多列,适合使用Grid组件实现数据展示
// 引入类型
import {ItemType} from '../../mode/types'
@Component
// 导出
export default struct NavExample {
@State message: string = '导航组件'
private navData:Array<ItemType> =[
{
title:'喜欢',
img:$r('app.media.ic_like')
},
{
title:'排名',
img:$r('app.media.ic_ranking')
},
{
title:'消息',
img:$r('app.media.ic_msg')
},
{
title:'购物车',
img:$r('app.media.ic_gwc')
},
{
title:'礼物',
img:$r('app.media.ic_gift')
},
{
title:'卡券',
img:$r('app.media.ic_coupons')
},
{
title:'收藏',
img:$r('app.media.ic_collect')
},
{
title:'设置',
img:$r('app.media.ic_setting')
}
]
build() {
Column(){
Grid(){
ForEach(this.navData,(item:ItemType)=>{
GridItem(){
Column(){
Image(item.img)
.width('25vp')
.height('25vp')
Text(item.title)
.fontSize('14fp')
.margin({top:'5vp'})
}
}
})
}
//2行4列
.columnsTemplate('1fr 1fr 1fr 1fr')
.rowsTemplate('1fr 1fr')
.height('120vp')
.rowsGap('12vp')//设置行与行间距
.columnsGap('8vp')//设置列于列间距1
.backgroundColor(Color.White)
.width('95%')
.margin('15vp')
.padding({
top:'10vp',
bottom:'10vp'
})
}
}
}
10.16 List列表组件
在ListExample.ets中,列表适合使用List列表组件
import {ItemType} from '../../mode/types'
@Component
// 导出
export default struct ListExample {
@State message: string = '列表组件'
private listsData:Array<ItemType> =[
{
title:'新品首发',
img:$r('app.media.ic_list1')
},
{
title:'大牌闪购',
img:$r('app.media.ic_list2')
},
{
title:'发现好物',
img:$r('app.media.ic_list3')
},
{
title:'排行榜',
img:$r('app.media.ic_list4')
},
{
title:'宠物市场',
img:$r('app.media.ic_list8')
},
{
title:'反季福利',
img:$r('app.media.ic_list5')
},
{
title:'明星同款',
img:$r('app.media.ic_list6')
},
{
title:'物流特色',
img:$r('app.media.ic_list7')
}
]
build() {
Column() {
Text('列表')
.fontSize('16fp')
.fontWeight(FontWeight.Medium)
.alignSelf(ItemAlign.Start) //文字左对齐
.margin('10vp')
List({space:12}){
ForEach(this.listsData,(item:ItemType)=>{
ListItem(){
Image(item.img)
.objectFit(ImageFit.Cover)//对图片进行剪切
.aspectRatio(1.3) //设置宽高比
Text(item.title)
.fontColor(Color.White)
.fontWeight(FontWeight.Medium)
}
.margin({left:'5vp',right:'5vp'})
})
}
.width('95%')
// .margin('10vp')
.lanes(2)//一行显示几个
}
}
}
10.17 我的页面-退出登录
entry\src\main\ets\view\components目录下创建用户描述
MineUser.ets
@Component
// 导出
export default struct MineUser {
@State message: string = '我的-用户描述'
build() {
Row(){
Text(this.message)
}
}
}
entry\src\main\ets\view\components目录下创建列表
MineList.ets
@Component
// 导出
export default struct MineList {
@State message: string = '我的-列表'
build() {
Column() {
Text(this.message)
}
}
}
在我的页面 MinePage
导入用户描述,导入用户列表
添加顶部标题
添加底部按钮,实现退出登录
import MineUser from './components/MineUser'
import MineList from './components/MineList'
//导入路由
import router from '@ohos.router'
@Component
// 导出
export default struct Mine {
@State message: string = '我的'
build() {
Row() {
Column(){
Text(this.message)
.fontSize('16fp')
.alignSelf(ItemAlign.Start)
.margin({top:"20vp"})
//用户描述
MineUser()
//列表
MineList()
//退出登录
Button('退出登录')
.width('90%')
.height('40vp')
.fontSize('16fp')
.backgroundColor('#e5e8ea')
.fontColor(Color.Red)
.margin({top:'30vp'})
.onClick(()=>{
//跳转
router.pushUrl({
url:'pages/LoginPage' //跳转到登录
}).catch((error:Error)=>{
//异常处理
})
})
}
.width('95%')
.margin('10vp')
.height('100%')
}
.backgroundColor('#f1f3f5')
}
}
运行结果
10.18 我的页面-用户描述信息
整体在一行使用row容器,文本上下结构使用column容器
@Component
// 导出
export default struct MineUser {
@State message: string = '我的-用户描述'
build() {
Row(){
Image($r('app.media.user'))
.height(50)
.width(50)
Column(){
Text('誉天教育')
.fontSize('20fp')
Text('yutian@163.com')
.fontSize('14fp')
.margin({top:'5vp'})
}
.alignItems(HorizontalAlign.Start) //左对齐
.margin({left:'15vp'})
}
.alignItems(VerticalAlign.Center)
.backgroundColor(Color.White)
.width('100%')
.height('100vp')
.margin({top:'20vp',bottom:'12vp'})
.borderRadius('25vp')
.padding({left:'20vp'})
}
}
10.19 我的页面-系统选项列表
1.List设置元素列表
2.使用开关组件Toggle
//导入接口typets
import {ItemType} from '../../mode/types'
// 弹窗
import promptAction from '@ohos.promptAction';
@Component
// 导出
export default struct MineList {
@State message: string = '我的-列表'
private ListsData:Array<ItemType> = [
{
title:'数据推送',
img:$r('app.media.icon_sjts')
},
{
title:'菜单设置',
img:$r('app.media.icon_cdsz')
},
{
title:'服务',
img:$r('app.media.icon_fw')
},
{
title:'收藏',
img:$r('app.media.icon_sc')
},
{
title:'设置',
img:$r('app.media.icon_sz')
},
{
title:'隐私',
img:$r('app.media.icon_ys')
}
]
build() {
List(){
ForEach(this.ListsData,(item:ItemType)=>{
ListItem(){
//两端内容
Row(){
// 左侧内容 图片文字间距
Row({space:10}){
Image(item.img)
.width('22vp')
.height('22vp')
Text(item.title)
.fontSize('16fp')
}
//开关组件
// type:ToggleType.Checkbox 复选框
// type:ToggleType.Switch 开关
// isOn 默认状态,true打开 false 关闭
Toggle({type:ToggleType.Switch,isOn:false})
.onChange((isChange:boolean)=>{
let tip:string = isChange ? item.title+'打开' :item.title+'关闭'
//isChange 打开或者关闭的状态
promptAction.showToast({
message:tip,
duration:3000
})
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding('12vp')
}
.height('50vp')
})
}
.width('100%')
.backgroundColor(Color.White)
.borderRadius('25vp')
.padding({top:'5vp',bottom:'5vp'})
//分割线
.divider({
color:"#ddd",
strokeWidth:'1vp',
startMargin:'46vp',//分割线开始的位置
endMargin:'12vp'
})
}
}
10.20 精选模块
使用list数据渲染
import {ItemType} from '../mode/types'
@Component
// 导出
export default struct Choice {
@State message: string = '精选'
private listsData:Array<ItemType> =[
{
title:'新品首发',
img:$r('app.media.ic_list1'),
num:'¥30'
},
{
title:'大牌闪购',
img:$r('app.media.ic_list2'),
num:'¥30'
},
{
title:'发现好物',
img:$r('app.media.ic_list3'),
num:'¥30'
},
{
title:'新品首发',
img:$r('app.media.ic_list1'),
num:'¥30'
},
{
title:'大牌闪购',
img:$r('app.media.ic_list2'),
num:'¥30'
},
{
title:'发现好物',
img:$r('app.media.ic_list3'),
num:'¥30'
},
{
title:'新品首发',
img:$r('app.media.ic_list1'),
num:'¥30'
},
{
title:'大牌闪购',
img:$r('app.media.ic_list2'),
num:'¥30'
},
{
title:'发现好物',
img:$r('app.media.ic_list3'),
num:'¥30'
}
]
build() {
Column() {
List({space:12}){
ForEach(this.listsData,(item:ItemType)=>{
ListItem(){
Column(){
Image(item.img)
.objectFit(ImageFit.Cover)
.aspectRatio(0.85) //设置宽高比
Text(item.title)
.fontColor('#999')
.fontWeight(FontWeight.Medium)
.alignSelf(ItemAlign.Start)
.height('25vp')
Text(item.num)
.fontColor(Color.Red)
.fontWeight(FontWeight.Medium)
.alignSelf(ItemAlign.Start)
.height('25vp')
}
}
.width('96%')
.margin({top:'10vp',left:'3%'})
.backgroundColor(Color.White)
.borderRadius('10vp')
.padding('5vp')
})
}
.width('100%')
.lanes(2)//一行显示几个
}
.backgroundColor('#F1F3F5')
}
}