注:建议学习完:Java、Vue/React、JS高级、CSS3在来学习会很简单
一、工具的安装 DevEco Student
官网HarmonyOS应用开发_应用开发平台和工具 - HarmonyOS应用开发官网
按照黑马讲师总结的笔记步骤来即可
二、ArkUI组件的学习
注:ArkTS语言要先去了解,可以查看官网,大概了解一遍即可
初识ArkTS语言-学习ArkTS语言-入门-HarmonyOS应用开发
大概 就是这样的写法:
Image组件
Image为图片组件,常用与在应用中显示图片。
Image的参数:
参数名:src
参数类型:Image支持加载string、PixelMap和Resources类型的数据源。
-
string格式,通常用来加载网络图片,需要申请网络访问权限:访问控制授权申请-访问控制-安全-开发-HarmonyOS应用开发
Image('https://xxx.png')
-
PixelMap格式,可以加载像素图,常用来图片编辑中
Image(pixeMapObject)
-
Resource格式,加载本地图片,推荐使用
有两种写法 Image($r('app.media.文件名')) //此写法$r('app.media.xxx')前面为固定写法 后面文件名可以省略后缀,其文件地址为:src/main/resources/medis/xxx Image($rawfile('文件名.png')) //此写法也为固定写法 后面文件名不能省略后缀 地址为: src/main/resources/rawfile/xxx.*
Image的属性(常用):
注:属性的详细使用可以参考显示图片(Image)-显示图形-基于ArkTS的声明式开发范式-UI开发-开发-HarmonyOS应用开发。本组件除了支持通用属性尺寸设置-通用属性-组件通用信息-组件参考(基于ArkTS的声明式开发范式)-ArkTS API参考-HarmonyOS应用开发还支持以下属性:
-
alt属性:
-
参数类型:String | Resources
-
作用:加载时显示的占位图,支持本地图片(png、jpg、bmp、svg和gif类型),但不支持网络图片;默认值为:null
-
-
objeact属性:
-
参数类型:ImageFit(Contain、Cover、Auto、Fill、ScaleDowm、None)
-
作用:设置图片填充效果;默认为ImageFit.Cover
-
ImageFit类型表:
名称 描述 Contain 保持宽高比进行缩小或者放大,使得图片完全显示在显示边界内。 Cover 保持宽高比进行缩小或者放大,使得图片两边都大于或等于显示边界。 Auto 自适应显示 Fill 不保持宽高比进行放大缩小,使得图片充满显示边界。 ScaleDown 保持宽高比显示,图片缩小或者保持不变。 None 保持原有尺寸显示。
-
-
objectRepeat
-
参数类型:ImageRepeat
-
作用:设置图片的重复样式。从中心点向两边重复,剩余空间不足放下一张图片时会截断。
默认值:ImageRepeat.NoRepeat;注:svg类型图源不支持该属性。
-
ImageRepeat参数类型表:
名称 描述 X 只在水平轴上重复绘制图片。 Y 只在竖直轴上重复绘制图片。 XY 在两个轴上重复绘制图片。 NoRepeat 不重复绘制图片。
-
-
interpolation
-
参数类型:ImageInterpolation
-
作用:设置图片的插值效果,即减轻低清晰度图片在放大显示时出现的锯齿问题。
默认值:ImageInterpolation.None 说明:svg类型图源不支持该属性。PixelMap资源不支持该属性。
-
ImageInterpolation参数类型表:
名称 描述 None 不使用图片插值。 High 高图片插值,插值质量最高,可能会影响图片渲染的速度。 Medium 中图片插值。 Low 低图片插值。
-
Image事件:
-
onComplete
-
作用:图片数据加载成功和解码成功时均触发该回调,返回成功加载的图片尺寸。
-
参数表:
参数名 类型 说明 width number 图片的宽。单位:像素 height number 图片的高。单位:像素 componentWidth number 组件的宽。单位:像素 componentHeight number 组件的高。单位:像素 loadingStatus number 图片加载成功的状态值。说明:返回的状态值为0时,表示图片数据加载成功。返回的状态值为1时,表示图片解码成功。
-
-
onError
-
作用:图片加载异常时触发该回调。
-
参数表:
参数名 类型 说明 componentWidth number 组件的宽。单位:像素 componentHeight number 组件的高。单位:像素 message9+ string 报错信息。
-
-
onFinish
-
作用:当加载的源文件为带动效的svg格式图片时,svg动效播放完成时会触发这个回调。如果动效为无限循环动效,则不会触发这个回调。注意:该事件仅仅支持svg格式的图片
-
大概使用:
@Entry
@Component
// struct Index 自定义组件:可复用的UI单元
struct Index {
@State message: string = 'Hello World'
@State text: string = ''
controller: TextInputController = new TextInputController()
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.fontColor("#36D")
.onClick(()=>{
this.message = "HarmonyOS"
})
Image($r("app.media.icon"))
.width(250) //如果里面是以数字的形式来设置宽度 会自动按宽度进行缩放 而且默认单位为vp 虚拟像素 会自动按设备来分配像素
.interpolation(ImageInterpolation.High)
.objectFit(ImageFit.Contain)
.onComplete((msg: { width: number,height: number })=>{
this.message = msg.width.toString()
})
}
.width('100%')
}
.height('100%')
}
}
Text组件
显示一段文本的组件。
Text的参数
-
参数名:content
-
参数类型:string|Resources
//string类型:直接传入字符串 Text('Hello world') //Resources选择 resources下的资源 Text($r('app.string.xxx')) //注意:默认是选择resources/base/element/string.json 下的值 获取值方式为 app.string.xxx(键) //但是如果在en_US 或 zh_CN 目录下的string.json 有设置相应的键值对 会优先获取里面的值。至于选择en_US还是zh_CN看设备语言 //也要注意一点,在en_US或zh_CN目录下设置键值对 也要在base/element/string.json 文件下也设置一样的。
-
例子
//base/element/string.json { "name": "width_label", "value": "Image_Width" } //en_US/element/string.json { "name": "width_label", "value": "Image_Width" } //zn_CN/element/string.json { "name": "width_label", "value": "图片宽度" } //index.ets @Entry @Component struct Index { controller: TextInputController = new TextInputController() build() { Row() { Column() { Text($r('app.string.width_label')) .fontSize(20) .fontWeight(FontWeight.Bold) .fontColor('red') } .width('100%') } .height('100%') } }
Text()的属性
-
textAlign属性
-
属性参数:TextAlign
名称 描述 Start 水平对齐首部。 Center 水平居中对齐。 End 水平对齐尾部。 -
作用:设置文本段落在水平方向的对齐方式
默认值:TextAlign.Start
说明:
文本段落宽度占满Text组件宽度。
可通过align属性控制文本段落在垂直方向上的位置,此组件中不可通过align属性控制文本段落在水平方向上的位置,即align属性中Alignment.TopStart、Alignment.Top、Alignment.TopEnd效果相同,控制内容在顶部。Alignment.Start、Alignment.Center、Alignment.End效果相同,控制内容垂直居中。Alignment.BottomStart、Alignment.Bottom、Alignment.BottomEnd效果相同,控制内容在底部。结合TextAlign属性可控制内容在水平方向的位置。
-
-
textOverflow
-
属性参数: overflow
名称 描述 Clip 文本超长时进行裁剪显示。 Ellipsis 文本超长时显示不下的文本用省略号代替。 None 文本超长时不进行裁剪。 -
作用: 设置文本超长时的显示方式。
默认值:overflow: TextOverflow.Clip
-
-
maxLines
-
参数类型:number
-
作用:文本最大行数
-
TextInput
-
声明TextInput组件
TextInput({ placeholder?: ResourceStr,text?:ResourceStr})
-
placeholder:提示文本
-
text:文本内容
-
-
属性与事件:
名称 参数类型 描述 type InputType 设置输入框类型。默认值:InputType.Normal placeholderColor ResourceColor 设置placeholder文本颜色。 placeholderFont Font 设置placeholder文本样式。 enterKeyType EnterKeyType 设置输入法回车键类型。默认值:EnterKeyType.Done caretColor ResourceColor 设置输入框光标颜色。 maxLength number 设置文本的最大输入字符数。 inputFilter8+ {value: ResourceStr,error?: (value: string) => void} 正则表达式,匹配表达式的输入允许显示,不匹配的输入将被过滤。目前仅支持单个字符匹配,不支持字符串匹配。- value:设置正则表达式。- error:正则匹配失败时,返回被过滤的内容。 copyOption9+ CopyOptions 设置输入的文本是否可复制。设置CopyOptions.None时,当前TextInput中的文字无法被复制或剪切,仅支持粘贴。 showPasswordIcon9+ boolean 密码输入模式时,输入框末尾的图标是否显示。默认值:true style9+ TextInputStyle 设置输入框为默认风格或内联输入风格。默认值:TextInputStyle.Default textAlign9+ TextAlign 设置输入文本在输入框中的对齐方式。默认值:TextAlign.Start 名称 功能描述 onChange(callback: (value: string) => void) 输入内容发生变化时,触发该回调。value:输入的文本内容。触发该事件的条件:1、键盘输入。2、粘贴、剪切。3、键盘快捷键Ctrl+v。 onSubmit(callback: (enterKey: EnterKeyType) => void) 按下输入法回车键触发该回调,返回值为当前输入法回车键的类型。enterKeyType:输入法回车键类型。具体类型见EnterKeyType枚举说明。 onEditChanged(callback: (isEditing: boolean) => void)(deprecated) 输入状态变化时,触发该回调。从API Version8开始,建议使用onEditChange。 onEditChange(callback: (isEditing: boolean) => void)8+ 输入状态变化时,触发该回调。isEditing为true表示正在输入。 onCopy(callback:(value: string) => void)8+ 长按输入框内部区域弹出剪贴板后,点击剪切板复制按钮,触发该回调。value:复制的文本内容。 onCut(callback:(value: string) => void)8+ 长按输入框内部区域弹出剪贴板后,点击剪切板剪切按钮,触发该回调。value:剪切的文本内容。 onPaste(callback:(value: string) => void)8+ 长按输入框内部区域弹出剪贴板后,点击剪切板粘贴按钮,触发该回调。value:粘贴的文本内容。 -
补充:输入框类型
TextInput({placeholder: '请输入图片的大小'}) .type(InputType.Number)
名称 功能描述 Normal 基本输入模式。这次输入数字、字母、下划线、空格、特殊字符 Password 密码输入模式。 Email 邮箱地址输入模式。 Number 纯数字输入模式。 PhoneNumber 电话号码输入模式。
Button
-
实例:
Button("点我")
-
自定义按钮
Button(){ Image($r('app.media.icon')).width(20).margin(10) }
-
属性
名称 参数类型 描述 type ButtonType 设置Button样式。默认值:ButtonType.Capsule从API version 9开始,该接口支持在ArkTS卡片中使用。 stateEffect boolean 按钮按下时是否开启按压态显示效果,当设置为false时,按压效果关闭。默认值:true从API version 9开始,该接口支持在ArkTS卡片中使用。 -
type属性有三种类型
名称 描述 Capsule 胶囊型按钮(圆角默认为高度的一半) Circle 圆形按钮 Normal 普通按钮(默认不带圆角) -
事件:支持通用事件
Slider
-
滑动条组件实例
Slider(options?:SliderOptions)
-
参数
参数名 参数类型 必填 参数描述 value number 否 当前进度值。默认值:参数min min number 否 设置最小值。默认值:0 max number 否 设置最大值。默认值:100说明:min >= max异常情况,min取默认值0,max取默认值100。value不在[min, max]范围之内,取min/max,靠近min取min,靠近max取max。 step number 否 设置Slider滑动步长。默认值:1取值范围:[0.01, max]说明:设置小于0或百分比的值时,按默认值显示。 style SliderStyle 否 设置Slider的滑块与滑轨显示样式。默认值:SliderStyle.OutSet direction8+ Axis 否 设置滑动条滑动方向为水平或竖直方向。默认值:Axis.Horizontal reverse8+ boolean 否 设置滑动条取值范围是否反向,横向Slider默认为从左往右滑动,竖向Slider默认为从上往下滑动。默认值:false -
属性:
名称 参数类型 描述 blockColor ResourceColor 设置滑块的颜色。从API version 9开始,该接口支持在ArkTS卡片中使用。 trackColor ResourceColor 设置滑轨的背景颜色。从API version 9开始,该接口支持在ArkTS卡片中使用。 selectedColor ResourceColor 设置滑轨的已滑动部分颜色。从API version 9开始,该接口支持在ArkTS卡片中使用。 showSteps boolean 设置当前是否显示步长刻度值。默认值:false从API version 9开始,该接口支持在ArkTS卡片中使用。 showTips boolean 设置滑动时是否显示百分比气泡提示。默认值:false从API version 9开始,该接口支持在ArkTS卡片中使用。说明:当direction的属性值为Axis.Horizontal时,tip显示在滑块正上方。值为Axis.Vertical时,tip显示在滑块正左边。tip的绘制区域为Slider自身节点的overlay。Slider不设置边距,或者边距比较小时,tip会被截断。 trackThickness Length 设置滑轨的粗细。默认值:当参数style的值设置SliderStyle.OutSet 时为 4.0vp,SliderStyle.InSet时为20.0vp。从APIversion9开始,该接口支持在ArkTS卡片中使用。说明:设置为小于0的值时,按默认值显示。
三、页面布局
1、线性布局组件
上面使用的Row()与Column()就是线性布局
Row()就是行容器、Column()就是列容器
如下:
在页面中可以看见行与列的区别在于主轴与交叉轴的位置不同。这关乎着属性与样式的设置。
属性 | 作用 | 参数 |
---|---|---|
justifyContent | 设置子元素在主轴方向的对齐格式 | FlexAlign枚举 |
alignItems | 设置子元素在交叉轴方向的对齐格式 | Row容器使用VerrticalAlign枚举Column容器使用HorizontalAlign枚举 |
-
justifyContent中FlexAlign对齐方式
-
主轴对齐
-
Row容器
-
Column容器
交叉轴对齐
-
Row容器
-
Column容器
-
-
注:布局容器的大小是默认自适应组件的大小的,所以如果你不修改width和height属性就直接使用布局对齐的话是没有效果的。
实例:
@Entry
@Component
struct Index {
@State imgSize: number = 100
build() {
Column() {
Row(){
Image($r('app.media.icon'))
.width(this.imgSize)
.interpolation(ImageInterpolation.High)
}
.width("100%")
.height("40%")
.justifyContent(FlexAlign.Center)
Row(){
Text("图片宽度:")
.fontSize(20)
.fontWeight(FontWeight.Bold)
TextInput({placeholder: '请输入图片的大小'})
.type(InputType.Number)
.width(250)
.height(50)
.onChange(value => {
this.imgSize = parseInt(value)
})
}
.justifyContent(FlexAlign.SpaceBetween)
Divider()
Row(){
Button("缩小")
.type(ButtonType.Normal)
.width(100)
.onClick(()=>{
if(this.imgSize>=10){
this.imgSize-=10
}
})
Button("放大")
.type(ButtonType.Capsule)
.width(100)
.onClick(()=>{
if(this.imgSize<=300){
this.imgSize+=10
}
})
}
.width('100%')
.height(100)
.justifyContent(FlexAlign.SpaceAround)
Row(){
Slider({
min:30,
max:300,
value:40,
step:10,
style:SliderStyle.InSet
})
.width('90%')
.showTips(true)
.blockColor("#36D")
.trackThickness(20)
.onChange(value=>{
this.imgSize = value
})
}
}
}
}
四、循环控制与List
1、forEach
-
基础写法
实例:
-
ForEach( this.items, (item:Item)=>{ Row(){ Image(item.image) Column(){ Text(item.name) Text(item.price.toFixed(0)) } } } )
-
注:一般keyGenerator不用写会默认有,其作用是给每个循环的对象添加一个唯一标识,减少渲染所耗的时间与内存】
2、List
forEach的时候循环打印了所有的数据,你会一个Column容器根本装不下所有的数据。因此为了实现滚动查看多条信息的功能,我们使用List容器
-
ListItem(列表项)数量过多超出屏幕后,会自动提供滚动功能
-
它既可纵向排列,也可横向排列
注:不过要注意Listitem不是容器,List才是,所以ListItem中只能写一个根组件,如果要使用多个组件的话要用别的容器将它们包裹起来
3.if与else
这个与通常的编程语法类似
五、自定义组件
1、自定义组件-注解方式
//1.创建一个文件夹ets/Compents/xxxCompent
@Component
export struct Header {
private title:ResourceStr;
build() {
Row() {
Text(this.title)
.fontSize(30)
.fontWeight(FontWeight.Bold)
}
}
}
//2. 在需要的地方引入
import { Header } from '../Components/CommonCompents'
@Entry
@Component
struct Product{
build(){
Column(){
Row(){
Header({title:"商品列表"})
}
.width('100%')
.height(50)
}
}
}
2、自定义组件-函数方式
-
全局函数定义方式:在struct外面定义
//定义: @Builder function testBuilder(){ Row(){ Text("这个是全局函数组件") } } @Entry @Component struct Product{ build(){ Column(){ Row(){ testBuilder() } .width('100%') .height(50) } } }
-
局部函数定义:在struct里面定义,只需要将function去掉即可,然后引入要使用this
//定义: @Builder testBuilder(){ Row(){ Text("这个是全局函数组件") } } @Entry @Component struct Product{ build(){ Column(){ Row(){ this.testBuilder() } .width('100%') .height(50) } } }
3、自定义样式
-
使用@Style装饰器,仅可封装组件通用属性,其使用方法与上面自定义组件的函数方式差不多
-
全局公共样式
-
//定义 @Styles function testStyle(){ .width(100) .height(50) } //使用 Text("这个是测试样式") .testStyle()
-
-
局部公共样式
-
//定义 @Styles testStyle(){ .width(100) .height(50) } //使用 Text("这个是测试样式") this.testStyle()
-
-
-
@Extend装饰器,仅可定义在全局,可以设置组件的特有属性
-
//定义 @Extend(Text) function textStylesExtend(){ .fontSize(30) .fontWeight(FontWeight.Bold) .fontColor('#36D') } //使用 Text("这个是测试样式") .testStyle() .textStylsExtend()
-
-
代码与实现效果
@Styles function testStyle(){ .width('100%') .height(150) } @Extend(Text) function textStylesExtend(){ .fontSize(30) .fontWeight(FontWeight.Bold) .fontColor('#36D') } import { Header } from '../Components/CommonCompents' @Entry @Component struct Product{ build(){ Text("这个是测试样式") .testStyle() .textStylesExtend() } }
六、状态管理装饰器
1、状态管理
在声明式UI中,是以状态来驱动视图更新的:
-
状态(State):指驱动视图更新的数据(被装饰器标记的变量)
-
视图(View):基于UI描述渲染得到的用户界面
使用状态管理器是为了,达到页面与状态实时更新的效果。可以用VUE中的双向绑定来理解
2、@State装饰器
说明:
-
@State装饰器标记的变量必须初始化,不能为空值
-
@State装饰器支持Object、class、string、number、boolean、enum类型以及这些类型的数组
-
嵌套类型以及数组中的对象属性无法触发视图更新(类似于Vue中的ref)
使用实例:
@State totalTasks:number = 0; build(){ Column(){ Row(){ Text(this.totalTasks.toString()) } } }
一个简单的任务例子:
实现效果:
实现代码:
//创建一个类
class Task{
static id:number = 1;
name:string = `任务${Task.id++}` //使用静态变量要使用类名引用
flag:boolean = false
}
//设置一个卡片样式
@Styles function card(){
.width('95%')
.padding(20)
.backgroundColor(Color.White)
.borderRadius(15)
.shadow({radius:6,color:"#1F000000",offsetX:2,offsetY:4})
}
//指定Text的样式
@Extend(Text) function finishedTask(){
.decoration({type:TextDecorationType.LineThrough})
.fontColor('#B1B2B1')
}
@Entry
@Component
struct Index2{
//任务总数量
@State totalTasks:number = 0;
//完成任务的数量
@State completeTasks:number = 0;
//任务数组
@State tasks:Task[] =[]
//更新数据的函数
handleTaskComplete(){
this.totalTasks = this.tasks.length
this.completeTasks = this.tasks.filter(item=>item.flag).length
}
build(){
Column(){
Row(){
Text("任务进度:")
.fontSize(30)
.fontWeight(FontWeight.Bold)
Stack(){
Progress({value:this.completeTasks,total:this.totalTasks,type:ProgressType.Ring})
.width(100)
Row(){
Text(this.completeTasks.toString())
.fontColor('#36D')
.fontSize(25)
Text(" / "+this.totalTasks.toString())
.fontSize(25)
}
}
.margin({left:10})
}
.card()
.margin({top:20})
.justifyContent(FlexAlign.Center)
Row(){
Button("添加任务")
.width(250)
.height(50)
.backgroundColor("#36d")
.fontColor(Color.White)
.margin(20)
.onClick(()=>{
this.tasks.push(new Task())
this.handleTaskComplete()
})
}
List({space:10}){
ForEach(
this.tasks,
(item:Task,index)=>{
ListItem(){
Row(){
if(!item.flag){
Text(item.name)
.fontSize(25)
.fontWeight(FontWeight.Bold)
}else{
Text(item.name)
.fontSize(25)
.fontWeight(FontWeight.Bold)
.finishedTask()
}
Checkbox()
.select(item.flag)
.onChange((val)=>{
item.flag = val
this.handleTaskComplete()
})
}
.margin(10)
.justifyContent(FlexAlign.SpaceBetween)
.card()
}
//指定每个ListItem向左划的效果 : 会出现一个组件(参数也是一个组件)
.swipeAction({end:this.DeleteTask(index)})
}
)
}
.alignListItem(ListItemAlign.Center)
.layoutWeight(1)
.width("100%")
.height("100%")
}
}
//删除组件
@Builder DeleteTask(index){
Row(){
Button("删除")
.backgroundColor(Color.Red)
.type(ButtonType.Circle)
.fontSize(30)
.onClick(()=>{
this.tasks.splice(index,1)
this.handleTaskComplete()
})
}
.margin(10)
}
}
3、@Prop与@Link装饰器
当父组件之间需要数据同步时,可以使用@Prop与@Link装饰器来实现(vue中的父子组件通信O.O)
以下是大概表格描述:
@Prop | @Link | |
---|---|---|
同步类型 | 单向同步 | 双向同步 |
允许装饰的变量类型 | 1、@Prop只支持string、number、boolean、enum类型 2、父组件对象类型,子组件是对象属性 3、不可以是数组、any | 1、父子类型一致:string、number、boolean、enum、object、class、以及他们的数组 2、数组中元素增、删、替换会引起刷新 3、嵌套类型以及数组中的对象属性无法触发视图更新 |
初始化方式 | 不允许子组件被@Prop装饰的变量初始化 | 父组件传递、禁止子组件初始化 |
区别 | 只是把变量传给子组件,直接使用xxxCompent(xxx:this.xxx)即可 | 把引用传给子组件,使用xxxCompent(xxx:$xxx)传递 |
图形理解
(1)、@Prop简单实例:
@Entry
@Component
struct Index2{
//任务总数量
@State totalTasks:number = 0;
//完成任务的数量
@State completeTasks:number = 0;
build(){
Column(){
//在组件中传递
TaskComponent({totalTasks:this.totalTasks,completeTasks:this.completeTasks}
}
}
}
//任务栏组件 一般这个要放在外面 放在这里为了演示
@Component
struct TaskComponent{
//任务总数量 @Prop只实现了单向绑定 即父组件值的修改会影响到子组件 但子组件的修改不会影响到父组件
@Prop totalTasks:number
//完成任务的数量
@Prop completeTasks:number
build(){
Row(){
Text("任务进度:")
.fontSize(30)
.fontWeight(FontWeight.Bold)
Stack(){
Progress({value:this.completeTasks,total:this.totalTasks,type:ProgressType.Ring})
.width(100)
Row(){
Text(this.completeTasks.toString())
.fontColor('#36D')
.fontSize(25)
Text(" / "+this.totalTasks.toString())
.fontSize(25)
}
}
.margin({left:10})
}
.card()
.margin({top:20})
.justifyContent(FlexAlign.Center)
}
}
(2)、@Link简单实例
//父组件
@Entry
@Component
struct Index2{
//任务总数量
@State totalTasks:number = 0;
//完成任务的数量
@State completeTasks:number = 0;
build(){
Column(){
TaskLisiComponent({totalTasks:$totalTasks,completeTasks:$completeTasks})
}
}
}
//子组件
//任务栏列表组件
@Component
struct TaskLisiComponent{
//任务总数量
@Link totalTasks:number //实现了双向绑定
//完成任务的数量
@Link completeTasks:number
//任务数组
@State tasks:Task[] =[]
//更新数据的函数
handleTaskComplete(){
this.totalTasks = this.tasks.length
this.completeTasks = this.tasks.filter(item=>item.flag).length
}
build(){
Column(){
Row(){
Button("添加任务")
.width(250)
.height(50)
.backgroundColor("#36d")
.fontColor(Color.White)
.margin(20)
.onClick(()=>{
this.tasks.push(new Task())
this.handleTaskComplete()
})
}
List({space:10}){
ForEach(
this.tasks,
(item:Task,index)=>{
ListItem(){
Row(){
if(!item.flag){
Text(item.name)
.fontSize(25)
.fontWeight(FontWeight.Bold)
}else{
Text(item.name)
.fontSize(25)
.fontWeight(FontWeight.Bold)
.finishedTask()
}
Checkbox()
.select(item.flag)
.onChange((val)=>{
item.flag = val
this.handleTaskComplete()
})
}
.margin(10)
.justifyContent(FlexAlign.SpaceBetween)
.card()
}
//指定每个ListItem向左划的效果 : 会出现一个组件(参数也是一个组件)
.swipeAction({end:this.DeleteTask(index)})
}
)
}
.alignListItem(ListItemAlign.Center)
.layoutWeight(1)
.width("100%")
.height("100%")
}
}
//删除组件
@Builder DeleteTask(index){
Row(){
Button("删除")
.backgroundColor(Color.Red)
.type(ButtonType.Circle)
.fontSize(30)
.onClick(()=>{
this.tasks.splice(index,1)
this.handleTaskComplete()
})
}
.margin(10)
}
}
(3)、@Prop与@Link装饰器综合实例
实现效果
实现代码
//创建一个类
class Task{
static id:number = 1;
name:string = `任务${Task.id++}` //使用静态变量要使用类名引用
flag:boolean = false
}
//设置一个卡片样式
@Styles function card(){
.width('95%')
.padding(20)
.backgroundColor(Color.White)
.borderRadius(15)
.shadow({radius:6,color:"#1F000000",offsetX:2,offsetY:4})
}
//指定Text的样式
@Extend(Text) function finishedTask(){
.decoration({type:TextDecorationType.LineThrough})
.fontColor('#B1B2B1')
}
@Entry
@Component
struct Index2{
//任务总数量
@State totalTasks:number = 0;
//完成任务的数量
@State completeTasks:number = 0;
build(){
Column(){
TaskComponent({totalTasks:this.totalTasks,completeTasks:this.completeTasks})
TaskLisiComponent({totalTasks:$totalTasks,completeTasks:$completeTasks})
}
}
}
//任务栏组件
@Component
struct TaskComponent{
//任务总数量
@Prop totalTasks:number
//完成任务的数量
@Prop completeTasks:number
build(){
Row(){
Text("任务进度:")
.fontSize(30)
.fontWeight(FontWeight.Bold)
Stack(){
Progress({value:this.completeTasks,total:this.totalTasks,type:ProgressType.Ring})
.width(100)
Row(){
Text(this.completeTasks.toString())
.fontColor('#36D')
.fontSize(25)
Text(" / "+this.totalTasks.toString())
.fontSize(25)
}
}
.margin({left:10})
}
.card()
.margin({top:20})
.justifyContent(FlexAlign.Center)
}
}
//任务栏列表组件
@Component
struct TaskLisiComponent{
//任务总数量
@Link totalTasks:number
//完成任务的数量
@Link completeTasks:number
//任务数组
@State tasks:Task[] =[]
//更新数据的函数
handleTaskComplete(){
this.totalTasks = this.tasks.length
this.completeTasks = this.tasks.filter(item=>item.flag).length
}
build(){
Column(){
Row(){
Button("添加任务")
.width(250)
.height(50)
.backgroundColor("#36d")
.fontColor(Color.White)
.margin(20)
.onClick(()=>{
this.tasks.push(new Task())
this.handleTaskComplete()
})
}
List({space:10}){
ForEach(
this.tasks,
(item:Task,index)=>{
ListItem(){
Row(){
if(!item.flag){
Text(item.name)
.fontSize(25)
.fontWeight(FontWeight.Bold)
}else{
Text(item.name)
.fontSize(25)
.fontWeight(FontWeight.Bold)
.finishedTask()
}
Checkbox()
.select(item.flag)
.onChange((val)=>{
item.flag = val
this.handleTaskComplete()
})
}
.margin(10)
.justifyContent(FlexAlign.SpaceBetween)
.card()
}
//指定每个ListItem向左划的效果 : 会出现一个组件(参数也是一个组件)
.swipeAction({end:this.DeleteTask(index)})
}
)
}
.alignListItem(ListItemAlign.Center)
.layoutWeight(1)
.width("100%")
.height("100%")
}
}
//删除组件
@Builder DeleteTask(index){
Row(){
Button("删除")
.backgroundColor(Color.Red)
.type(ButtonType.Circle)
.fontSize(30)
.onClick(()=>{
this.tasks.splice(index,1)
this.handleTaskComplete()
})
}
.margin(10)
}
}
4、@Provide和@Consume
这个类似于Vue中 provide / inject
其作用是:可以跨组件提供类似于@State和@Link的双向同步
优点:不需要在子/孙组件中声明,只需要在父组件中要传递的变量前加上@Provide装饰器;在子/孙组件中接收的变量上加上@Consume装饰器
缺点:内部资源消耗比较大
结构图为:
简单实例:
//父组件
@Entry
@Component
struct Index2{
//这个是测试@Provide
@Provide text:number = 1;
build(){
Column(){
TaskComponent()
}
}
}
//子/孙组件
@Component
struct TaskComponent{
//接收测试
@Consume text:number
build(){
Row(){
Text(this.text.toString())
}
}
5、@ObjectLink与@Observed装饰器
说明:这两个装饰器用于涉及嵌套对象或数组元素为对象的场景进行双向数据同步(可以理解为:上面的都是类似vue中的ref,这两个装饰器为reactive)
使用步骤:
1、在元素类型中加上@Observed装饰器,如果有嵌套的都要加上
2、在需要同步更新的变量加上@ObjectLink装饰器 注意:一般在数组/对象嵌套中是不能将里面的变量设置装饰器的,所以一般会在声明一个子组件来使用@ObjectLink装饰器
2.1、对象设置@Observed/@ObjectLink装饰器案例
2.2、数组设置@Observed/@ObjectLink装饰器案例
实现案例:
效果:
实现代码:
//创建一个类 使用@Observed装饰器
@Observed
class Task{
static id:number = 1;
name:string = `任务${Task.id++}` //使用静态变量要使用类名引用
flag:boolean = false
}
//设置一个卡片样式
@Styles function card(){
.width('95%')
.padding(20)
.backgroundColor(Color.White)
.borderRadius(15)
.shadow({radius:6,color:"#1F000000",offsetX:2,offsetY:4})
}
//指定Text的样式
@Extend(Text) function finishedTask(){
.decoration({type:TextDecorationType.LineThrough})
.fontColor('#B1B2B1')
}
@Entry
@Component
struct Index2{
//任务总数量
@State totalTasks:number = 0;
//完成任务的数量
@State completeTasks:number = 0;
build(){
Column(){
TaskComponent({totalTasks:this.totalTasks,completeTasks:this.completeTasks})
TaskLisiComponent({totalTasks:$totalTasks,completeTasks:$completeTasks})
}
}
}
//任务栏组件
@Component
struct TaskComponent{
//任务总数量
@Prop totalTasks:number
//完成任务的数量
@Prop completeTasks:number
build(){
Row(){
Text("任务进度:")
.fontSize(30)
.fontWeight(FontWeight.Bold)
Stack(){
Progress({value:this.completeTasks,total:this.totalTasks,type:ProgressType.Ring})
.width(100)
Row(){
Text(this.completeTasks.toString())
.fontColor('#36D')
.fontSize(25)
Text(" / "+this.totalTasks.toString())
.fontSize(25)
}
}
.margin({left:10})
}
.card()
.margin({top:20})
.justifyContent(FlexAlign.Center)
}
}
//任务栏列表组件
@Component
struct TaskLisiComponent{
//任务总数量
@Link totalTasks:number
//完成任务的数量
@Link completeTasks:number
//任务数组
@State tasks:Task[] =[]
//更新数据的函数
handleTaskComplete(){
this.totalTasks = this.tasks.length
this.completeTasks = this.tasks.filter(item=>item.flag).length
}
build(){
Column(){
Row(){
Button("添加任务")
.width(250)
.height(50)
.backgroundColor("#36d")
.fontColor(Color.White)
.margin(20)
.onClick(()=>{
this.tasks.push(new Task())
this.handleTaskComplete()
})
}
List({space:10}){
ForEach(
this.tasks,
(item:Task,index)=>{
ListItem(){
//这里将每个任务子模块以及处理完成任务的函数也传递过去
// 注意:在函数中使用this调用 如果直接传递函数过去 在调用函数过程中函数里面的this是子组件的(因为是子组件调用的)
// ,所以需要使用bind()指定this指向
ItemChild({item:item,onChangeTick:this.handleTaskComplete.bind(this)})
}
//指定每个ListItem向左划的效果 : 会出现一个组件(参数也是一个组件)
.swipeAction({end:this.DeleteTask(index)})
}
)
}
.alignListItem(ListItemAlign.Center)
.layoutWeight(1)
.width("100%")
.height("100%")
}
}
//删除组件
@Builder DeleteTask(index){
Row(){
Button("删除")
.backgroundColor(Color.Red)
.type(ButtonType.Circle)
.fontSize(30)
.onClick(()=>{
this.tasks.splice(index,1)
this.handleTaskComplete()
})
}
.margin(10)
}
}
//任务子模块
@Component
struct ItemChild{
//这里将上面的各个任务的子模块抽取出来
//各个任务的变量 使用 @ObjectLink 装饰器
@ObjectLink item:Task
//接收父组件中的修改函数
onChangeTick:()=>void
build(){
Row(){
if(!this.item.flag){
Text(this.item.name)
.fontSize(25)
.fontWeight(FontWeight.Bold)
}else{
Text(this.item.name)
.fontSize(25)
.fontWeight(FontWeight.Bold)
.finishedTask()
}
Checkbox()
.select(this.item.flag)
.onChange((val)=>{
this.item.flag = val
this.onChangeTick()
})
}
.margin(10)
.justifyContent(FlexAlign.SpaceBetween)
.card()
}
}
七、页面路由
1、前言
页面路由是指在应用程序中实现不同页面之间的跳转和数据传递
页面路由中是使用页面栈来存储页面的,所以遵循栈先进后出的原则。先进来的页面被压到栈里,一层一层拿出来。
但页面栈的最大容量上限为32个,所以有时候就需要使用router.clear()
来清空栈,以免栈满,释放内存。
页面栈理解:
2、Router的两种跳转方式
-
router.pushUrl()
:目标页面不会替换当前页,而是压入页面栈,所以可以用router.back()
返回当前页 -
router.replaceUrl()
:目标页替换当前页,当前页会被销毁并释放资源,无法返回当前页
3、Router两种页面实例模式
-
Standard
:标准化实例模式,每次跳转都会新建一个目标页并压入栈顶。默认就是这种方式 -
Single
:单实例模式,如果目标页已经在栈中,则离栈顶最近的同Url页面会被移动到栈顶并重新加载
4、简单使用
5、具体使用
注:在使用url路由中,要在resources/base/profile
里面配置
建议在新建页面的时候,直接用倒数第二个的page
,会自动添加
代码:
resources/base/profile
{
"src": [
"pages/Index",
"pages/IndexPage",
"pages/Index2",
"pages/Product"
]
}
Header
import router from '@ohos.router';
@Component
export struct Header {
private title:ResourceStr;
build() {
Row() {
Button("返回")
Text(this.title)
.fontSize(30)
.fontWeight(FontWeight.Bold)
}
.onClick(()=>{
router.showAlertBeforeBackPage({
message:"确定要返回上一页码" })
router.back()
})
}
}
首页
import router from '@ohos.router'
class RouteInfo {
pageUrl:string
title:string
constructor(pageUrl:string,title:string) {
this.pageUrl = pageUrl
this.title = title
}
}
@Entry
@Component
struct IndexPage {
@State message: string = 'Hello World'
routesInfo:RouteInfo[] = [
new RouteInfo("pages/Index","控制图片"),
new RouteInfo("pages/Index2","任务列表"),
new RouteInfo("pages/Product","商品列表"),
]
build() {
Row() {
Column() {
Text("路由测试")
.fontSize(30)
ForEach(
this.routesInfo,
(item:RouteInfo,index)=>{
Row(){
Text(index.toString())
.fontSize(30)
.fontWeight(FontWeight.Bold)
.fontColor(Color.White)
Text(item.title)
.fontSize(30)
.fontWeight(FontWeight.Bold)
.fontColor(Color.White)
}
.justifyContent(FlexAlign.SpaceBetween)
.width("90%")
.height(100)
.backgroundColor("#36D")
.margin(20)
.padding(10)
.borderRadius(20)
.onClick(()=>{
router.pushUrl({
url:item.pageUrl,
params:{title:item.title}
},
router.RouterMode.Standard,
err=>{
if(err){
console.log(`路由失败:错误代码-${err.code} 错误信息-${err.message}`);
}
}
)
})
}
)
}
.width('100%')
}
.height('100%')
}
}
任务列表(这里就给一个页面):
import { Header } from '../Components/CommonCompents'
//创建一个类
@Observed
class Task{
static id:number = 1;
name:string = `任务${Task.id++}` //使用静态变量要使用类名引用
flag:boolean = false
}
//设置一个卡片样式
@Styles function card(){
.width('95%')
.padding(20)
.backgroundColor(Color.White)
.borderRadius(15)
.shadow({radius:6,color:"#1F000000",offsetX:2,offsetY:4})
}
//指定Text的样式
@Extend(Text) function finishedTask(){
.decoration({type:TextDecorationType.LineThrough})
.fontColor('#B1B2B1')
}
@Entry
@Component
struct Index2{
//任务总数量
@State totalTasks:number = 0;
//完成任务的数量
@State completeTasks:number = 0;
build(){
Column(){
TaskComponent({totalTasks:this.totalTasks,completeTasks:this.completeTasks})
TaskLisiComponent({totalTasks:$totalTasks,completeTasks:$completeTasks})
}
}
}
//任务栏组件
@Component
struct TaskComponent{
//任务总数量
@Prop totalTasks:number
//完成任务的数量
@Prop completeTasks:number
build(){
Column(){
Row(){
Header({title:"返回首页"})
}
Row(){
Text("任务进度:")
.fontSize(30)
.fontWeight(FontWeight.Bold)
Stack(){
Progress({value:this.completeTasks,total:this.totalTasks,type:ProgressType.Ring})
.width(100)
Row(){
Text(this.completeTasks.toString())
.fontColor('#36D')
.fontSize(25)
Text(" / "+this.totalTasks.toString())
.fontSize(25)
}
}
.margin({left:10})
}
.card()
.margin({top:20})
.justifyContent(FlexAlign.Center)
}
}
}
//任务栏列表组件
@Component
struct TaskLisiComponent{
//任务总数量
@Link totalTasks:number
//完成任务的数量
@Link completeTasks:number
//任务数组
@State tasks:Task[] =[]
//更新数据的函数
handleTaskComplete(){
this.totalTasks = this.tasks.length
this.completeTasks = this.tasks.filter(item=>item.flag).length
}
build(){
Column(){
Row(){
Button("添加任务")
.width(250)
.height(50)
.backgroundColor("#36d")
.fontColor(Color.White)
.margin(20)
.onClick(()=>{
this.tasks.push(new Task())
this.handleTaskComplete()
})
}
List({space:10}){
ForEach(
this.tasks,
(item:Task,index)=>{
ListItem(){
//这里将每个任务子模块以及处理完成任务的函数也传递过去
// 注意:在函数中使用this调用 如果直接传递函数过去 在调用函数过程中函数里面的this是子组件的(因为是子组件调用的)
// ,所以需要使用bind()指定this指向
ItemChild({item:item,onChangeTick:this.handleTaskComplete.bind(this)})
}
//指定每个ListItem向左划的效果 : 会出现一个组件(参数也是一个组件)
.swipeAction({end:this.DeleteTask(index)})
}
)
}
.alignListItem(ListItemAlign.Center)
.layoutWeight(1)
.width("100%")
.height("100%")
}
}
//删除组件
@Builder DeleteTask(index){
Row(){
Button("删除")
.backgroundColor(Color.Red)
.type(ButtonType.Circle)
.fontSize(30)
.onClick(()=>{
this.tasks.splice(index,1)
this.handleTaskComplete()
})
}
.margin(10)
}
}
//任务子模块
@Component
struct ItemChild{
//这里将上面的各个任务的子模块抽取出来
//各个任务的变量
@ObjectLink item:Task
//接收父组件中的修改函数
onChangeTick:()=>void
build(){
Row(){
if(!this.item.flag){
Text(this.item.name)
.fontSize(25)
.fontWeight(FontWeight.Bold)
}else{
Text(this.item.name)
.fontSize(25)
.fontWeight(FontWeight.Bold)
.finishedTask()
}
Checkbox()
.select(this.item.flag)
.onChange((val)=>{
this.item.flag = val
this.onChangeTick()
})
}
.margin(10)
.justifyContent(FlexAlign.SpaceBetween)
.card()
}
}
6、内容回顾
八、动画
1、属性动画
属性动画是通过设置组件的animation
属性来给组件添加动画,当组件的width
、height
、opacity
、backgroundColor
、scale
、rotate
、translate
等属性变更时,可以实现渐变过渡效果。
相应实现属性:
名称 | 参数类型 | 必填 | 描述 |
---|---|---|---|
duration | number | 否 | 设置动画时长。默认值:1000单位:毫秒从API version 9开始,该接口支持在ArkTS卡片中使用。说明:- 在ArkTS卡片上最大动画持续时间为1000毫秒。- 设置浮点型类型的值时,向下取整;设置值为1.2,按照1处理。 |
tempo | number | 否 | 动画播放速度。数值越大,动画播放速度越快,数值越小,播放速度越慢。值为0时,表示不存在动画。默认值:1 |
curve | string | Curve | ICurve9+ | 否 | 设置动画曲线。默认值:Curve.EaseInOut从API version 9开始,该接口支持在ArkTS卡片中使用。 |
delay | number | 否 | 设置动画延迟执行的时长。默认值:0,不延迟播放。单位:毫秒取值范围:[0, +∞)说明:设置浮点型类型的值时,向下取整。例如,设置值为1.2,按照1处理。 |
iterations | number | 否 | 设置播放次数。默认值:1取值范围:[-1, +∞)说明:设置为-1时表示无限次播放。设置为0时表示无动画效果。 |
playMode | PlayMode | 否 | 设置动画播放模式,默认播放完成后重头开始播放。默认值:PlayMode.Normal从API version 9开始,该接口支持在ArkTS卡片中使用。 |
onFinish | () => void | 否 | 状态回调,动画播放完成时触发。从API version 9开始,该接口支持在ArkTS卡片中使用。说明:当iterations设置为-1时,动画效果无限循环不会停止,所以不会触发此回调。 |
Curve值
从API version 9开始,该接口支持在ArkTS卡片中使用。
名称 | 描述 |
---|---|
Linear | 表示动画从头到尾的速度都是相同的。 |
Ease | 表示动画以低速开始,然后加快,在结束前变慢,CubicBezier(0.25, 0.1, 0.25, 1.0)。 |
EaseIn | 表示动画以低速开始,CubicBezier(0.42, 0.0, 1.0, 1.0)。 |
EaseOut | 表示动画以低速结束,CubicBezier(0.0, 0.0, 0.58, 1.0)。 |
EaseInOut | 表示动画以低速开始和结束,CubicBezier(0.42, 0.0, 0.58, 1.0)。 |
FastOutSlowIn | 标准曲线,cubic-bezier(0.4, 0.0, 0.2, 1.0)。 |
LinearOutSlowIn | 减速曲线,cubic-bezier(0.0, 0.0, 0.2, 1.0)。 |
FastOutLinearIn | 加速曲线,cubic-bezier(0.4, 0.0, 1.0, 1.0)。 |
ExtremeDeceleration | 急缓曲线,cubic-bezier(0.0, 0.0, 0.0, 1.0)。 |
Sharp | 锐利曲线,cubic-bezier(0.33, 0.0, 0.67, 1.0)。 |
Rhythm | 节奏曲线,cubic-bezier(0.7, 0.0, 0.2, 1.0)。 |
Smooth | 平滑曲线,cubic-bezier(0.4, 0.0, 0.4, 1.0)。 |
Friction | 阻尼曲线,CubicBezier(0.2, 0.0, 0.2, 1.0)。 |
简单实现: (具体案例:看下面小鱼案例)
Text('hello Animation') .position({x: this.X, y: this.Y}) //animation 要放在实现属性的后面 一般放在最后面 .animation({ duration: 500 //实现时间 })2、显示动画
显式动画是通过全局animateTo
函数来修改组件属性,实现属性变化时的渐变过渡效果。
提供全局animateTo显式动画接口来指定由于闭包代码导致的状态变化插入过渡动效。
实现参数
参数 | 类型 | 是否必填 | 描述 |
---|---|---|---|
value | AnimateParam | 是 | 设置动画效果相关参数。 |
event | () => void | 是 | 指定显示动效的闭包函数,在闭包函数中导致的状态变化系统会自动插入过渡动画。 |
AnimateParam对象说明
名称 | 类型 | 描述 |
---|---|---|
duration | number | 动画持续时间,单位为毫秒。默认值:1000从API version 9开始,该接口支持在ArkTS卡片中使用。说明:- 在ArkTS卡片上最大动画持续时间为1000毫秒,若超出则固定为1000毫秒。- 设置浮点型类型的值时,向下取整。例如,设置值为1.2,按照1处理。 |
tempo | number | 动画的播放速度,值越大动画播放越快,值越小播放越慢,为0时无动画效果。默认值:1.0 |
curve | Curve | ICurve | string | 动画曲线。默认值:Curve.EaseInOut从API version 9开始,该接口支持在ArkTS卡片中使用。 |
delay | number | 单位为ms(毫秒),默认不延时播放。默认值:0说明:- 设置浮点型类型的值时,向下取整。例如,设置值为1.2,按照1处理。 |
iterations | number | 默认播放一次,设置为-1时表示无限次播放。默认值:1 |
playMode | PlayMode | 设置动画播放模式,默认播放完成后重头开始播放。默认值:PlayMode.Normal从API version 9开始,该接口支持在ArkTS卡片中使用。相关使用约束请参考PlayMode说明。 |
onFinish | () => void | 动效播放完成回调。从API version 9开始,该接口支持在ArkTS卡片中使用。 |
简单实现(来自官网,下面有小鱼案例):
// xxx.ets
@Entry
@Component
struct AnimateToExample {
@State widthSize: number = 250
@State heightSize: number = 100
@State rotateAngle: number = 0
private flag: boolean = true
build() {
Column() {
Button('change size')
.width(this.widthSize)
.height(this.heightSize)
.margin(30)
.onClick(() => {
if (this.flag) {
animateTo({
duration: 2000,
curve: Curve.EaseOut,
iterations: 3,
playMode: PlayMode.Normal,
onFinish: () => {
console.info('play end')
}
}, () => {
this.widthSize = 150
this.heightSize = 60
})
} else {
animateTo({}, () => {
this.widthSize = 250
this.heightSize = 100
})
}
this.flag = !this.flag
})
Button('change rotate angle')
.margin(50)
.rotate({ x: 0, y: 0, z: 1, angle: this.rotateAngle })
.onClick(() => {
animateTo({
duration: 1200,
curve: Curve.Friction,
delay: 500,
iterations: -1, // 设置-1表示动画无限循环
playMode: PlayMode.Alternate,
onFinish: () => {
console.info('play end')
}
}, () => {
this.rotateAngle = 90
})
})
}.width('100%').margin({ top: 5 })
}
}
3、组件动画
转场动画分为:
-
页面间转场
-
组件内转场
-
共享元素转场
此处是组件内转场。
组件转场动画是在组件插入或移除时的过渡动画,通过组件的transition属性来配置。
transition属性的参数是一个JSON对象,可以设置组件动画效果作用于新增还是删除、不透明度的变化值、平移的距离、缩放的比例、旋转的角度等。
因为是转场动画,涉及到入场、离场问题,所以组件是否显示肯定是通过if条件判断一个变量的值来实现的。 这个变量值的修改必须在animateTo函数中进行,需要配合animateTo才能生效,动效时长、曲线、延时跟随animateTo中的配置。
参数列表
名称 | 参数类型 | 参数描述 |
---|---|---|
transition | TransitionOptions | 设置组件插入显示和删除隐藏的过渡效果。默认值:不设置任何过渡效果时,默认有透明度从0到1的过渡效果。若设置了其他过渡效果,以设置的过渡效果为准。说明:所有参数均为可选参数,详细描述见TransitionOptions参数说明。 |
TransitionOptions参数说明
参数名称 | 参数类型 | 必填 | 参数描述 |
---|---|---|---|
type | TransitionType | 否 | 默认包括组件新增和删除。默认值:TransitionType.All说明:不指定Type时说明插入删除使用同一种效果。 |
opacity | number | 否 | 设置组件转场时的透明度效果,为插入时起点和删除时终点的值。默认值:1取值范围: [0, 1]说明:设置小于0或大于1的非法值时,按1处理。 |
translate | {x? : number | string,y? : number | string,z? : number | string} | 否 | 设置组件转场时的平移效果,为插入时起点和删除时终点的值。-x:横向的平移距离。-y:纵向的平移距离。-z:竖向的平移距离。 |
scale | {x? : number,y? : number,z? : number,centerX? : number | string,centerY? : number | string} | 否 | 设置组件转场时的缩放效果,为插入时起点和删除时终点的值。-x:横向放大倍数(或缩小比例)。-y:纵向放大倍数(或缩小比例)。-z:当前为二维显示,该参数无效。- centerX、centerY指缩放中心点,centerX和centerY默认值是"50%"。- 中心点为0时,默认的是组件的左上角。 |
rotate | {x?: number,y?: number,z?: number,angle: number | string,centerX?: number | string,centerY?: number | string} | 否 | 设置组件转场时的旋转效果,为插入时起点和删除时终点的值。-x:横向的旋转向量。-y:纵向的旋转向量。-z:竖向的旋转向量。- centerX,centerY指旋转中心点,centerX和centerY默认值是"50%"。- 中心点为(0,0)时,默认的是组件的左上角。 |
示例:
if(this.isShow) { // 是否显示
Text('hello')
.transition({
opcity: 0, // 不透明度
rotate: {angle: -360}, // 旋转角度
scale: {x: 0, y: 0} // 横向、纵向的缩放倍数
})
}
// 只有用animateTo控制状态变量,才能有动画
animateTo(
{duration: 1000}, // 设置动画的时长
() => {
this.isShow = false // 在animateTo函数中调整状态变量,控制上面的组件是否显示,从而让上面的组件有入场、离场动画
}
)
4、小鱼案例
注: 属性动画直接在要设置动画的组件设置一个animation
属性即可
显示动画比较麻烦,要在控制是否显示动画的地方 使用animationTo
全局事件(自带的)
页面动画是控制组件的是否显示的,要配合animationTo
事件使,还要在被设置动画的组件中设置transition
属性
import router from '@ohos.router'
@Entry
@Component
struct AnimationPage {
// 小鱼图片
@State src: Resource = $r('app.media.fish_R')
// 是否开始游戏
@State isBegin: boolean = false
// 小鱼坐标
@State fishX: number = 200
@State fishY: number = 180
// 小鱼角度
@State angle: number = 0
build() {
Row() {
Column() {
Stack() {
// 返回按钮
Button('返回')
.position({x: 10, y: 10})
.backgroundColor('#20101010')
.onClick(() => {
// 跳转到自己(返回开始状态)
router.replaceUrl({url: 'pages/AnimationPage'})
})
if(!this.isBegin) {
// 开始按钮
//组件切换动画
//transition 要配合AnimationTo使用 直接在要转场的组件下面添加 transition属性即可
Button('开始游戏')
.onClick(() => {
// 只有用animateTo控制状态变量,才能有动画
animateTo({duration:1000},()=>{
this.isBegin = true
})
})
} else {
// 显示小鱼图片
Image(this.src)
.position({x: this.fishX - 23.7, y: this.fishY - 13.8}) // 图片宽高是47.4*27.6,一半就是23.7*13.8
.rotate({angle: this.angle, centerX: '50%', centerY: '50%'})
.width(47.4)
.height(27.6)
.transition({
type:TransitionType.Insert,
opacity:0,
// scale:{x:-250}
})
// 方式一在要设置样式动画的组件 将Animation属性放在最后设置
// .animation({
// duration:500
// })
}
// 操作按钮
Row() {
Button('←')
.backgroundColor('#20101010')
.onClick(()=>{
//方式二 在点击事件中 用AnimationTo全局事件监听
animateTo({duration:1000},()=>{
this.fishX-=20
this.src = $r('app.media.fish_L')
})
})
Column({space: 40}) {
Button('↑')
.backgroundColor('#20101010')
.onClick(()=>{
this.fishY-=20
})
Button('↓')
.backgroundColor('#20101010')
.onClick(()=>{
animateTo({duration:1000},()=>{
this.fishY+=20
})
})
}
Button('→')
.backgroundColor('#20101010')
.onClick(()=>{
animateTo({duration:1000},()=>{
this.fishX+=20
this.src = $r('app.media.fish_R')
})
})
}
.height(240)
.width(240)
.position({x: 35, y: '40%'})
}
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
}
.height('100%')
.width('100%')
.backgroundImage($r('app.media.eatFish_bg'))
.backgroundImageSize(ImageSize.Cover)
}
}
九、Stage模型
1、应用模型
应用模型是HarmonyOS为开发者提供的应用程序所需能力的抽象提炼,它提供了应用程序必备的组件和运行机制。有了应用模型,开发者可以基于一套统一的模型进行应用开发,使应用开发更简单、高效。
HarmonyOS应用模型的构成要素包括:
-
应用组件
-
应用进程模型
-
应用线程模型
-
应用任务管理模型
-
应用配置文件
2、应用模型概况
随着系统的演进发展,HarmonyOS先后提供了两种应用模型:
-
FA(Feature Ability)模型:HarmonyOS早期版本开始支持的模型,已经不再主推。
-
Stage模型:HarmonyOS 3.1 Developer Preview版本开始新增的模型,是目前主推且会长期演进的模型。在该模型中,由于提供了AbilityStage、WindowStage等类作为应用组件和Window窗口的“舞台”,因此称这种应用模型为Stage模型。
3、Stage模型的项目结构
Stage模型概述
编译器中的代码结构
在一个应用内可以创建多个module
。module是应用的基本功能单元。里面包含着源码、配置文件、资源等。每一个moudle都可以独立编译运行
这些module分为两类:
-
Ability module:内部是开发这个应用的核心能力。可以把不同的能力创建成多个Ability module 进行开发
-
Library module:将通用的工具、资源、配置、组件等抽取到的单独module。
创建一个新module只要右键项目new->module
即可(注意观察后缀)
编译打包
在编译期中,整个项目会被打包从一个app安装包。
在Stage模型中,为了降低不同module之间的耦合,每一个module都可以独立编译打包运行。其中:
-
Ability module会被编译成HAP(Harmony Ability Package) 文件,
.hap
后缀 (多个.hap中 只有一个是Entry
入口的.hap) -
Library module会被编译成HSP(Harmony Shared Package)文件或者HAR(Harmony Archive)
HAP包在运行时可以引用依赖HSP包。一个应用中往往会分为多个Ability module,所以会有多个HAP文件。HAP是HarmonyOS应用安装的基本单位。
HAP分为两类(类型在module.json5
文件中配置):
-
Entry HAP:主模块,入口模块。一个应用只能有一个
-
Feature HAP:其他拓展功能的模块,可以有多个,也可以没有。
虽然可以有多个Ability module,但是只能有一个入口模块(例如HelloWorld项目中的entry
模块),主要用于开发项目的入口界面、主能力等信息。
这些HAP最终合并一起称为一个Bundle(即App),这个Bundle需要有一个Bundle name,是整个应用的唯一标识。这个Bundle name即新建项目时配置的Bundle name
,体现在项目的app.json5
文件的bundleName
配置项。最终整个Bundle合并打包成一个.app
后缀的文件。应用在下载安装时,可以选择性的下载安装,例如安装Entry和某几个Feature。
运行期结构
每一个HAP都可以独立运行。HAP在运行时,每一个HAP都会创建一个 AbilityStage 的实例,应用组件的“舞台”。
Stage模型提供UIAbility和ExtensionAbility两种类型的组件,这两种组件都有具体的类承载,支持面向对象的开发方式。
-
UIAbility:
-
UIAbility组件是一种包含UI界面的应用组件,主要用于和用户交互。例如,图库类应用可以在UIAbility组件中展示图片瀑布流,在用户选择某个图片后,在新的页面中展示图片的详细内容。同时用户可以通过返回键返回到瀑布流页面。UIAbility的生命周期只包含创建/销毁/前台/后台等状态,与显示相关的状态通过WindowStage的事件暴露给开发者。
-
-
ExtensionAbility:面向特定场景的应用组件
在HAP中定义的UIAbility,都会在实例化后关联到该HAP的AbilityStage,使用AbilityStage获取该HAP中UIAbility实例的运行时信息。
每个UIAbility类实例都会与一个WindowStage类实例绑定,它包含一个主窗口,该窗口为ArkUI提供了绘制区域。
4、应用配置文件
应用配置文件概述(Stage模型)
每个应用项目必须在项目的代码目录下加入配置文件,这些配置文件会向编译工具、操作系统和应用市场提供应用的基本信息。
在基于Stage模型开发的应用项目代码下,都存在一个app.json5及一个或多个module.json5这两种配置文件。
app.json5主要包含以下内容:
-
应用的全局配置信息,包含应用的包名、开发厂商、版本号等基本信息。
-
特定设备类型的配置信息。
module.json5主要包含以下内容:
-
Module的基本配置信息,例如Module名称、类型、描述、支持的设备类型等基本信息。
-
应用组件信息,包含UIAbility组件和ExtensionAbility组件的描述信息。
-
应用运行过程中所需的权限信息。
module.json5配置文件
module.json5配置文件-应用配置文件(Stage模型)-开发基础知识-入门-HarmonyOS应用开发
app.json5配置文件
app.json5配置文件-应用配置文件(Stage模型)-开发基础知识-入门-HarmonyOS应用开发
5、UIAbility生命周期
概述
当用户打开、切换和返回到对应应用时,应用中的UIAbility实例会在其生命周期的不同状态之间转换。UIAbility类提供了一系列回调,通过这些回调可以知道当前UIAbility实例的某个状态发生改变,会经过UIAbility实例的创建和销毁,或者UIAbility实例发生了前后台的状态切换。
UIAbility的生命周期包括Create、Foreground、Background、Destroy四个状态,如下图所示。
生命周期状态说明
生命周期 | 触发时间 | 操作 |
---|---|---|
Create状态 | UIAbility实例创建完成时 | 可以在该回调中进行应用初始化操作,例如变量定义资源加载等,用于后续的UI界面展示 |
Foreground状态 | UIAbility切换至前台时,UI界面可见之前 | 申请系统需要的资源 |
Background状态 | UIAbility切换至后台时,UI界面完全不可见之后 | 释放UI界面不可见时无用的资源。执行状态保存等较为耗时的操作 |
Destroy状态 | UIAbility实例销毁时 | 进行系统资源的释放、数据的保存等操作 |
Create状态
Create状态为在应用加载过程中,UIAbility实例创建完成时触发,系统会调用onCreate()回调。可以在该回调中进行应用初始化操作,例如变量定义资源加载等,用于后续的UI界面展示。
WindowStageCreate和WindowStageDestroy状态
UIAbility的状态发生变化,也会影响WindowStage的相关变化:
UIAbility实例创建完成之后,在进入Foreground之前,系统会创建一个WindowStage。WindowStage创建完成后会进入onWindowStageCreate()回调,可以在该回调中设置UI界面加载、设置WindowStage的事件订阅。
WindowStageCreate和WindowStageDestroy状态图:
Foreground和Background状态
Foreground和Background状态分别在UIAbility实例切换至前台和切换至后台时触发,对应于onForeground()回调和onBackground()回调。
onForeground()回调,在UIAbility的UI界面可见之前,如UIAbility切换至前台时触发。可以在onForeground()回调中申请系统需要的资源,或者重新申请在onBackground()中释放的资源。
onBackground()回调,在UIAbility的UI界面完全不可见之后,如UIAbility切换至后台时候触发。可以在onBackground()回调中释放UI界面不可见时无用的资源,或者在此回调中执行较为耗时的操作,例如状态保存等。
例如应用在使用过程中需要使用用户定位时,假设应用已获得用户的定位权限授权。在UI界面显示之前,可以在onForeground()回调中开启定位功能,从而获取到当前的位置信息。
当应用切换到后台状态,可以在onBackground()回调中停止定位功能,以节省系统的资源消耗。
Destroy状态
Destroy状态在UIAbility实例销毁时触发。可以在onDestroy()回调中进行系统资源的释放、数据的保存等操作。
例如调用terminateSelf()方法停止当前UIAbility实例,从而完成UIAbility实例的销毁;或者用户使用最近任务列表关闭该UIAbility实例,完成UIAbility的销毁。
查看生命周期
Entry
模块中的src/main/ets/enteryabiliity/EntryAbility.ts
:
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/Index', (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');
}
}
补充知识:hilog日志打印
导入模块:
import hilog from '@ohos.hilog';
常用方法:
hilog.debug/info/warn/error
debug(domain: number, tag: string, format: string, ...args: any[]) : void
参数解释:
参数 | 描述 |
---|---|
domain | 日志对应的领域标识,范围是0x0~0xFFFF。 |
tag | 指定日志标识,可以为任意字符串 |
format | 格式字符串,用于日志的格式化输出 |
args | 与格式字符串format对应的可变长度参数列表 |
6、组件生命周期
页面以及自定义组件
自定义组件:使用@Component
装饰的UI单元
页面:由@Entry
装饰的自定义组件。一个页面有且只有一个@Entry
,可以由一个或多个自定义组件组成。只有一个被@Entry
装饰的组件才可以调用页面的生命周期
生命周期
生命周期接口 | 触发时间 | 生效的装饰器 | 其他 |
---|---|---|---|
onPageShow | 页面每次显示就触发一次,包括路由过程、应用进入前台等场景 | @Entry | |
onPageHide | 页面每次隐藏时就触发一次,包括路由过程、应用进入后台等场景 | @Entry | |
onBackPress | 当用户点击返回按钮时触发 | @Entry | |
aboutToAppear | 组件即将出现时回调该函数。在创建自定义组件新实例后。执行build()之前 | @Component | |
aboutToDisappear | 自定义组件销毁之前 | @Component | 不允许在该函数中改变状态变量 |
简单实例
mport { Header } from '../Components/CommonCompents';
@Entry
@Component
struct LifePage {
@State message: string = 'Life'
@State Flag:boolean = false
aboutToAppear(){
console.log(this.message+"aboutToAppear");
}
aboutToDisappear(){
console.log(this.message+"aboutToDisappear");
}
onPageShow(){
console.log(this.message+"onPageShow");
}
onBackPress(){
console.log(this.message+"onBackPress");
}
onPageHide(){
console.log(this.message+"onPageHide");
}
build() {
Row() {
Column() {
Header({title:"life"})
Button("显示子组件")
.onClick(()=>this.Flag = !this.Flag)
}
.width('100%')
}
.height('100%')
}
}
7、UIAbility组件启动模式
UIAbility的启动模式是指UIAbility实例在启动时的不同呈现状态。针对不同的业务场景,系统提供了四种启动模式:
-
sigletion启动模式
-
standard启动模式/multion启动模式
-
specificed启动模式
设置启动模式方式
在module.json5配置文件中的"launchType"字段配置即可。
{
"module": {
// ...
"abilities": [
{
"launchType": "singleton",
// ...
}
]
}
}
sigletion
singleton启动模式为单实例模式,也是默认情况下的启动模式。
每次调用startAbility()方法时,如果应用进程中该类型的UIAbility实例已经存在,则复用系统中的UIAbility实例。系统中只存在唯一一个该UIAbility实例,即在最近任务列表中只存在一个该类型的UIAbility实例。
演示效果:
multiton/standard
standard启动模式:每次启动UIAbility都会创建一个新的实例,在任务列表中可能存在一个或多个相同的UIAbility
multition模式一样。区别点是:multiton模式在创建新实例时,旧的实例会被移出.
specified
每个UIAbility实例可以设置Key标识,启动UIAbility时,需要指定key,存在key相同实例直接被拉起(前台显示),不存在则创建新实例
配置specified方法
这节不是很懂,视频地址
26-Stage模型-UIAbility的启动模式_哔哩哔哩_bilibili
十、网络连接
连接方式
HarmonyOS提供了三种连接方式:
-
Http连接方式
-
WebSocket连接方式
-
socket连接
Http连接方式
示例
// 引入包名
import http from '@ohos.net.http';
// 每一个httpRequest对应一个HTTP请求任务,不可复用
let httpRequest = http.createHttp();
// 用于订阅HTTP响应头,此接口会比request请求先返回。可以根据业务需要订阅此消息
// 从API 8开始,使用on('headersReceive', Callback)替代on('headerReceive', AsyncCallback)。 8+
httpRequest.on('headersReceive', (header) => {
console.info('header: ' + JSON.stringify(header));
});
httpRequest.request(
// 填写HTTP请求的URL地址,可以带参数也可以不带参数。URL地址需要开发者自定义。请求的参数可以在extraData中指定
"EXAMPLE_URL",
{
method: http.RequestMethod.POST, // 可选,默认为http.RequestMethod.GET
// 开发者根据自身业务需要添加header字段
header: {
'Content-Type': 'application/json'
},
// 当使用POST请求时此字段用于传递内容
extraData: {
"data": "data to send",
},
expectDataType: http.HttpDataType.STRING, // 可选,指定返回数据的类型
usingCache: true, // 可选,默认为true
priority: 1, // 可选,默认为1
connectTimeout: 60000, // 可选,默认为60000ms
readTimeout: 60000, // 可选,默认为60000ms
usingProtocol: http.HttpProtocol.HTTP1_1, // 可选,协议类型默认值由系统自动指定
}, (err, data) => {
if (!err) {
// data.result为HTTP响应内容,可根据业务需要进行解析
console.info('Result:' + JSON.stringify(data.result));
console.info('code:' + JSON.stringify(data.responseCode));
// data.header为HTTP响应头,可根据业务需要进行解析
console.info('header:' + JSON.stringify(data.header));
console.info('cookies:' + JSON.stringify(data.cookies)); // 8+
// 取消订阅HTTP响应头事件
httpRequest.off('headersReceive');
// 当该请求使用完毕时,调用destroy方法主动销毁
httpRequest.destroy();
} else {
console.info('error:' + JSON.stringify(err));
// 取消订阅HTTP响应头事件
httpRequest.off('headersReceive');
// 当该请求使用完毕时,调用destroy方法主动销毁。
httpRequest.destroy();
}
}
);
也可以使用Promise方式作为异步方法:
let promise = httpRequest.request(
'http://xxxx',
{method: xxxxx}
)
promise.then((resp: http.HttpResponse) => {
if(resp.responseCode === 200) {
// 成功请求后的处理
console.log(resp.result)
}
}).catch((err: Error) => {
// 请求失败时的处理
})
示例
异步调用返回:
// 异步返回一个Promise对象
getShopList(): Promise<ShopInfo[]> {
return new Promise((resolve, reject) => {
let httpRequest = http.createHttp()
httpRequest.request(
'http://xxx.xxx.xxx:8080/myDemoProject/userlist?pageNo=1&pageSize=2',
{
method: http.RequestMethod.GET
}
)
.then(resp => {
if(resp.responseCode === 200) {
console.log('查询成功')
// 将成功的结果封装到Promise的resolve中
resolve(JSON.parse(resp.result.toString()))
} else {
console.log('查询失败')
// 将失败的结果封装到Promise的reject中
reject('查询用户失败')
}
})
.catch(error => {
console.log('查询失败,错误信息:', JSON.stringify(error))
reject('查询用户失败')
})
})
}
通过该方法加载数据:
ShopModel.getShopList() // 得到的是一个Promise<ShopInfo[]>对象
.then(shops => {
shops.forEach(shop => console.log(shop))
})
第三方库axios
ohpm
OHPM CLI 作为鸿蒙生态三方库的包管理工具,支持OpenHarmony共享包的发布、安装和依赖管理。
旧版本的HarmonyOS使用npm作为包管理工具,后来改为ohpm。ohpm的配置、使用方式与npm类似。
安装ohpm
下载ohpm工具包:HUAWEI DevEco Studio和SDK下载和升级 | HarmonyOS开发者
解压文件,进入ohpm/bin
目录,打开命令行工具,输入以下命令初始化ohpm
window
init.bat
linux/mac
./init.bat
配置环境变量
window
在此电脑 > 属性 > 高级系统设置 > 高级 > 环境变量中,将ohpm命令行工具的bin目录配置到系统或者用户的PATH变量中。
安装完成输入,出版本号即为成功
ohpm -v
安装axios
在harmony上找到第三方库OpenHarmony三方库中心仓
在项目目录下安装
ohpm install @ohos/axios
配置权限
示例
axios.get(
'http://xxx.xxx.xxx.xxx',
{
params: {'param1': 'value1'}, // Get请求时的请求参数
data: {'param1': 'value1'} // Post请求时的请求参数
}
).then(resp => {
if(resp.status === 200) {
// 成功请求后的处理
console.log(JSON.stringify(resp.data))
}
}).catch(error => {
// 请求失败时的处理
console.log('查询失败', JSON.stringify(error))
})
案例
//这里来生明接口
// 引入包名
import ShopInfo from '../viewmodel/ShopInfo'
//引入axios
import axios from '@ohos/axios'
class ShopModel {
pageNo: number = 1
getShopList(): Promise<ShopInfo[]> {
// 每一个httpRequest对应一个HTTP请求任务,不可复用
return new Promise((resolve, reject) => {
axios.get(
`http://localhost:3000/shops`,
{
//params 用于拼接地址参数 data 用来携带请求参数 一般在post使用
params:{
pageNo:this.pageNo,pageSize:3
}
}
).then(res=>{
if(res.status===200){
console.log(JSON.stringify(res.data))
resolve(res.data)
}else {
reject("查询失败")
}
})
.catch(err=>{
console.log("获取失败"+err)
reject("查询失败")
})
}
)
}
}
//需要实例化才能调用里面的方法
const shopModel = new ShopModel()
export default shopModel
十一、数据持久化
测试数据持久化需要使用真机调试
1、用户首选项
场景介绍:
用户首选项为应用提供Key-Value键值型的数据处理能力,支持应用持久化轻量级数据,并对其修改和查询。当用户希望有一个全局唯一存储的地方,可以采用用户首选项来进行存储。Preferences会将该数据缓存在内存中,当用户读取的时候,能够快速从内存中获取数据。Preferences会随着存放的数据量越多而导致应用占用的内存越大,因此,Preferences不适合存放过多的数据,适用的场景一般为应用保存用户的个性化设置(字体大小,是否开启夜间模式)等。
运作机制
约束限制
-
Key键为string类型,要求非空且长度不超过80个字节。
-
如果Value值为string类型,可以为空,不为空时长度不超过8192个字节。
-
内存会随着存储数据量的增大而增大,所以存储的数据量应该是轻量级的,建议存储的数据不超过一万条,否则会在内存方面产生较大的开销。
接口说明(来自官网)
以下是用户首选项持久化功能的相关接口,大部分为异步接口。异步接口均有callback和Promise两种返回形式,下表均以callback形式为例,更多接口及使用方式请见用户首选项。
接口名称 | 描述 |
---|---|
getPreferences(context: Context, name: string, callback: AsyncCallback<Preferences>): void | 获取Preferences实例。 |
put(key: string, value: ValueType, callback: AsyncCallback<void>): void | 将数据写入Preferences实例,可通过flush将Preferences实例持久化。 |
has(key: string, callback: AsyncCallback<boolean>): void | 检查Preferences实例是否包含名为给定Key的存储键值对。给定的Key值不能为空。 |
get(key: string, defValue: ValueType, callback: AsyncCallback<ValueType>): void | 获取键对应的值,如果值为null或者非默认值类型,返回默认数据defValue。 |
delete(key: string, callback: AsyncCallback<void>): void | 从Preferences实例中删除名为给定Key的存储键值对。 |
flush(callback: AsyncCallback<void>): void | 将当前Preferences实例的数据异步存储到用户首选项持久化文件中。 |
on(type: 'change', callback: Callback<{ key : string }>): void | 订阅数据变更,订阅的Key的值发生变更后,在执行flush方法后,触发callback回调。 |
off(type: 'change', callback?: Callback<{ key : string }>): void | 取消订阅数据变更。 |
deletePreferences(context: Context, name: string, callback: AsyncCallback<void>): void | 从内存中移除指定的Preferences实例。若Preferences实例有对应的持久化文件,则同时删除其持久化文件。 |
开发步骤
简单案例
在src/main/ets
创建文件夹Common/utils
下创建保存用户首选项文件(可以直接复制)
import preferences from '@ohos.data.preferences';
class PreferenceUtils{
//设置一个Map格式的变量方便存储
preMap:Map<string,preferences.Preferences> = new Map()
//加载Preference
//写法一: 因为getPreferences返回的是一个Promise对象 所以也可以使用async异步标识改造
// loadPreference(content,name:string){
// preferences.getPreferences(content,name)
// .then(pref=>{
// //保存一个pref实例
// this.preMap.set(name,pref)
// console.log('testTag',`加载preference[${name}]成功`)
// })
// .catch(reason=>{
// console.log('testTag',`加载preference[${name}]失败`,JSON.stringify(reason))
// })
// }
//写法二:下面一样
//加载 初始化
async loadPreference(content,name:string){
try {
let pref = await preferences.getPreferences(content, name)
this.preMap.set(name, pref);
console.log('testTag', `加载preference[${name}]成功`)
} catch (e) {
console.log('testTag',`加载preference[${name}]失败`,JSON.stringify(e))
}
}
//写入数据
async putPreferenceValue(name:string,key:string,value:preferences.ValueType){
if(!this.preMap.get(name)){
console.log('testTag',`加载preference[${name}]未初始化`)
return
}
try {
let pref = this.preMap.get(name)
//写入数据
await pref.put(key, value)
//刷新磁盘
await pref.flush()
console.log('testTag', `保存preference[${name}]成功`)
} catch (e) {
console.log('testTag',`保存preference[${name}]失败`,JSON.stringify(e))
}
}
//得到数据
//name:哪个prefMap里面的值 key:具体的key defaultValue:如果没有就加载这个 默认
async getPreferenceValue(name:string,key:string,defaultValue:preferences.ValueType){
if(!this.preMap.get(name)){
console.log('testTag',`加载preference[${name}]未初始化`)
return
}
try {
let pref = this.preMap.get(name)
//读取数据
let value = await pref.get(key,defaultValue);
console.log('testTag', `读取preference[${name}]--[${value}]成功`)
return value
} catch (e) {
console.log('testTag',`读取preference[${name}]失败`,JSON.stringify(e))
}
}
}
const preferenceUtils = new PreferenceUtils();
export default preferenceUtils;
在应用刚启动就加载该首选项
在文件src/main/etc/entryability/EntryAbility.ts
中找到生命周期函数onCreate
加上
//在入口Ability就加载 preference 有多个就加载多个 MyPreference这个要唯一
preferenceUtils.loadPreference(this.context,"MyPreference")
// preferenceUtils.loadPreference(this.context,"MyPreference1")
最后只要写入读出数据的地方使用相应的get/put方法即可
示例:
//使用preferenceUtils
//在build 渲染之间调用 该生命周期函数
async aboutToAppear(){
this.fontSize =await preferenceUtils.getPreferenceValue('MyPreference','IndexFont',16) as number
}
.onChange(val=>{
//修改字体时 需要首选项保存
this.fontSize = val
preferenceUtils.putPreferenceValue('MyPreference','IndexFont',val)
})
2、关系数据库
前言
由于上面的用户首选项只能保留基本数据类型的数据,保留不了类似对象、数组等复杂类型的数据。所以引入了关系数据库来保留数据
这个关系数据库和mysql的数据库类似
场景介绍
关系型数据库基于SQLite组件,适用于存储包含复杂关系数据的场景,比如一个班级的学生信息,需要包括姓名、学号、各科成绩等,又或者公司的雇员信息,需要包括姓名、工号、职位等,由于数据之间有较强的对应关系,复杂程度比键值型数据更高,此时需要使用关系型数据库来持久化保存数据。
基本概念
-
谓词:数据库中用来代表数据实体的性质、特征或者数据实体之间关系的词项,主要用来定义数据库的操作条件。
-
结果集:指用户查询之后的结果集合,可以对数据进行访问。结果集提供了灵活的数据访问方式,可以更方便地拿到用户想要的数据。
运作机制
关系型数据库对应用提供通用的操作接口,底层使用SQLite作为持久化存储引擎,支持SQLite具有的数据库特性,包括但不限于事务、索引、视图、触发器、外键、参数化查询和预编译SQL语句。
图1 关系型数据库运作机制
约束限制
-
系统默认日志方式是WAL(Write Ahead Log)模式,系统默认落盘方式是FULL模式。
-
数据库中连接池的最大数量是4个,用以管理用户的读操作。
-
为保证数据的准确性,数据库同一时间只能支持一个写操作。
-
当应用被卸载完成后,设备上的相关数据库文件及临时文件会被自动清除。
接口说明
以下是关系型数据库持久化功能的相关接口,大部分为异步接口。异步接口均有callback和Promise两种返回形式,下表均以callback形式为例,更多接口及使用方式请见关系型数据库。
接口名称 | 描述 |
---|---|
getRdbStore(context: Context, config: StoreConfig, callback: AsyncCallback<RdbStore>): void | 获得一个相关的RdbStore,操作关系型数据库,用户可以根据自己的需求配置RdbStore的参数,然后通过RdbStore调用相关接口可以执行相关的数据操作。 |
executeSql(sql: string, bindArgs: Array<ValueType>, callback: AsyncCallback<void>):void | 执行包含指定参数但不返回值的SQL语句。 |
insert(table: string, values: ValuesBucket, callback: AsyncCallback<number>):void | 向目标表中插入一行数据。 |
update(values: ValuesBucket, predicates: RdbPredicates, callback: AsyncCallback<number>):void | 根据RdbPredicates的指定实例对象更新数据库中的数据。 |
delete(predicates: RdbPredicates, callback: AsyncCallback<number>):void | 根据RdbPredicates的指定实例对象从数据库中删除数据。 |
query(predicates: RdbPredicates, columns: Array<string>, callback: AsyncCallback<ResultSet>):void | 根据指定条件查询数据库中的数据。 |
deleteRdbStore(context: Context, name: string, callback: AsyncCallback<void>): void | 删除数据库。 |
实现步骤
初始化数据库
导入import relationalStore from '@ohos.data.relationalStore';
使用relationalStore.getRdbStore()
初始化数据库 有三个参数 【应用上下文,rdb配置,配置后的回调函数】
在上面的回调函数中使用rdbStore.executeSql(sql)
执行sql语句 获取执行后的rdbStore
操作数据库
调用相应的API即可(具体参数意思看下面案例)
案例
说明:在使用关系数据库涉及到的文件基本使用.ets文件。因为ts文件不能导入.ets文件的数据(entryAbility也要改为.ets文件)。
配置关系数据库
在model文件夹下创建一个.ets文件 这里创建TaskModel.ets(也可以直接复制把相应数据改改就行)
import relationalStore from '@ohos.data.relationalStore';
import TaskInfo from '../viewmodel/TaskInfo'; //由于taskInfo文件是ets类型的 ts不能导入ets文件 所以该model也要使用ets文件
class TaskModel {
private rdbStore: relationalStore.RdbStore
private tableName: string = 'TASK'
/**
* 初始化任务表
*/
initTaskDB(context){
//初始化rdb配置
const config = {
name:"MyApplication.db", //数据库名字 可以自己取别重复
securityLevel:relationalStore.SecurityLevel.S1 //安全等级 总共有四级 这里用一级就够了
}
//初始化sql语句
const sql = `CREATE TABLE IF NOT EXISTS TASK(
ID INTEGER PRIMARY KEY AUTOINCREMENT,
NAME TEXT NOT NULL,
FINISHED BIT
)`
//获取rdb
relationalStore.getRdbStore(context,config,(err,rdbStore)=>{
if(err){
console.log("testTag","获取rdbStore失败!")
return
}
//执行sql语句
rdbStore.executeSql(sql)
//保存
this.rdbStore =rdbStore
})
}
/**
* 查询任务列表
*/
async getTaskList(){
//查询条件
//创建一个查询条件 predicates可以指定多种条件
let predicates = new relationalStore.RdbPredicates(this.tableName);
//查询 返回的是一个 promise
let result = await this.rdbStore.query(predicates,['ID','NAME','FINISHED']) //第一个参数是指定表 第二个是查询出来的列
//遍历返回值
//定义一个接受返回每一列的数组
let tasks:TaskInfo[] = [];
//遍历返回值 判断是不是最后一行
while(!result.isAtLastRow){
//指针指向下一行
result.goToLastRow()
//获取每一行的数据
let id = result.getLong(result.getColumnIndex('ID')) //id是int 可以使用long来获取 getColumnIndex 可以理解为每列的表头
let name = result.getString(result.getColumnIndex("NAME"));
let finished = result.getLong(result.getColumnIndex("FINISHED")) //因为在数据库中保存boolean类型的是1/0 所以获取的只能是long
tasks.push({id,name,finished:!!finished}) //转化为boolean型
}
console.log("testTag查询数据成功",JSON.stringify(tasks))
return tasks;
}
/**
* 添加一个新的任务
* @param name 任务名称
* @returns 任务id
*/
addTask(name: string): Promise<number>{
return this.rdbStore.insert(this.tableName, {name, finished: false})
}
/**
* 根据id更新任务状态
* @param id 任务id
* @param finished 任务是否完成
*/
updateTaskStatus(id: number, finished: boolean) {
//封装要更新的数据
let data = {finished}
//查询条件
let predicates = new relationalStore.RdbPredicates(this.tableName)
predicates.equalTo('ID',id) // 创建一个ID=id的条件 相当于where ID = #{id}
//执行条件操作
return this.rdbStore.update(data,predicates) // 第一个是修改的数据 第二个是修改的条件 返回单额是一个promise对象
}
/**
* 根据id删除任务
* @param id 任务id
*/
deleteTaskById(id: number){
//查询条件
let predicates = new relationalStore.RdbPredicates(this.tableName)
predicates.equalTo('ID',id) // 创建一个ID=id的条件 相当于where ID = #{id}
//执行条件操作
return this.rdbStore.delete(predicates) // 第一个删除的条件 返回单额是一个promise对象
}
}
let taskModel = new TaskModel();
export default taskModel as TaskModel;
在入口文件中初始化关系数据库
entryability/EntryAbility.ets
onCreate(want, launchParam) {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); //日志
//导入持久化数据库需要改成ets后缀名
//初始化TaskModel
TaskModel.initTaskDB(this.context)
}
部分使用
因为我们上面使用的是返回Promise,所以在使用的时候也要.then()获得
//查
aboutToAppear(){
// 查询任务列表
console.log('testTag', '初始化组件,查询任务列表')
TaskModel.getTaskList().then(tasks=>{
//获取数据
this.tasks = tasks;
//刷新
this.handleTaskChange();
})
}
//增
// 1.新增任务
TaskModel.addTask(name).then(id=>{
this.tasks.push({id,name,finished:false})
//刷新
this.handleTaskChange();
this.dialogController.close();//关闭对话框
console.log("testTag","新增任务成功")
})
// 删
// 删除任务
TaskModel.deleteTaskById(id).then(()=>{
this.tasks.splice(index,1)
console.log("testTag","删除任务成功")
}).catch(err=>{
console.log("testTag","删除任务失败")
})
//改
//更新任务状态
TaskModel.updateTaskStatus(this.item.id,val).then(()=>{
this.item.finished = val
//更新已完成的数量
this.onTaskChange(this.item)
console.log("testTag","更新成功")
}).catch(err=>{
console.log("testTag","更新失败")
})
十二、通知
通知简介
应用可以通过通知接口发送通知消息,终端用户可以通过通知栏查看通知内容,也可以点击通知来打开应用。
通知常见的使用场景:
-
显示接收到的短消息、即时消息等。
-
显示应用的推送消息,如广告、版本更新等。
-
显示当前正在进行的事件,如下载等。
HarmonyOS通过ANS(Advanced Notification Service,通知系统服务)对通知类型的消息进行管理,支持多种通知类型,如基础类型通知、进度条类型通知。
通知业务流程
通知业务流程由通知子系统、通知发送端、通知订阅端组成。一条通知从通知发送端产生,通过IPC通信发送到通知子系统,再由通知子系统分发给通知订阅端。
-
通知发送端:可以是三方应用或系统应用。开发者重点关注。
-
通知订阅端:只能为系统应用,比如通知中心。通知中心默认会订阅手机上所有应用对当前用户的通知。开发者无需关注。
构建一个基础通知
开发步骤
-
导入接口
-
发布通知
-
取消通知
主要是在编写发布基础通知功能时,能了解各个代码的含义即可
基础类型通知主要应用于发送短信息、提示信息、广告推送等,支持普通文本类型、长文本类型、多行文本类型和图片类型。
表1 基础类型通知中的内容分类
类型 | 描述 |
---|---|
NOTIFICATION_CONTENT_BASIC_TEXT | 普通文本类型。 |
NOTIFICATION_CONTENT_LONG_TEXT | 长文本类型。 |
NOTIFICATION_CONTENT_MULTILINE | 多行文本类型。 |
NOTIFICATION_CONTENT_PICTURE | 图片类型。 |
通知发布接口如下表所示,不同发布类型通知由NotificationRequest的字段携带不同的信息。
接口名 | 描述 |
---|---|
publish(request: NotificationRequest, callback: AsyncCallback<void>): void | 发布通知。 |
cancel(id: number, label: string, callback: AsyncCallback<void>): void | 取消指定的通知。 |
cancelAll(callback: AsyncCallback<void>): void; | 取消所有该应用发布的通知。 |
具体代码:
导入模块
import NotificationManager from '@ohos.notificationManager';
构造NotificationRequest对象,并发布通知
let notificationRequest: notificationManager.NotificationRequest = {
id: 1,
content: {
contentType: NotificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT, // 普通文本类型通知 必填(上面可以修改)
normal: {
title: 'test_title', //标题
text: 'test_text', //内容
additionalText: 'test_additionalText',
}
}
}
NotificationManager.publish(notificationRequest, (err) => {
if (err) {
console.error(`[ANS] failed to publish, error[${err}]`);
return;
}
console.info(`[ANS] publish success`);
});
普通文本类型
let notificationRequest: notificationManager.NotificationRequest = { id: 1, content: { contentType: NotificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT, // 普通文本类型通知 必填(上面可以修改) normal: { title: 'test_title', //标题 text: 'test_text', //内容 additionalText: 'test_additionalText', //通知附加内容 } } }
长文本内容:长文本类型通知继承了普通文本类型的字段,同时新增了长文本内容、内容概要和通知展开时的标题。通知默认显示与普通文本相同,展开后,标题显示为展开后标题内容,内容为长文本内容。
let notificationRequest: notificationManager.NotificationRequest = { id: 1, content: { contentType: NotificationManager.ContentType.NOTIFICATION_CONTENT_LONG_TEXT, // 长文本类型通知 longText: { title: 'test_title', text: 'test_text', additionalText: 'test_additionalText', longText: 'test_longText', briefText: 'test_briefText', expandedTitle: 'test_expandedTitle', } } } // 发布通知 NotificationManager.publish(notificationRequest, (err) => { if (err) { console.error(`[ANS] failed to publish, error[${err}]`); return; } console.info(`[ANS] publish success`); });
多行文本内容:多行文本类型通知继承了普通文本类型的字段,同时新增了多行文本内容、内容概要和通知展开时的标题。通知默认显示与普通文本相同,展开后,标题显示为展开后标题内容,多行文本内容多行显示。
let notificationRequest: notificationManager.NotificationRequest = { id: 1, content: { contentType: NotificationManager.ContentType.NOTIFICATION_CONTENT_MULTILINE, // 多行文本类型通知 multiLine: { title: 'test_title', text: 'test_text', briefText: 'test_briefText', longTitle: 'test_longTitle', lines: ['line_01', 'line_02', 'line_03', 'line_04'], } } } // 发布通知 NotificationManager.publish(notificationRequest, (err) => { if (err) { console.error(`[ANS] failed to publish, error[${err}]`); return; } console.info(`[ANS] publish success`); });
图片类型:图片类型通知继承了普通文本类型的字段,同时新增了图片内容、内容概要和通知展开时的标题,图片内容为PixelMap型对象,其大小不能超过2M。
let imagePixelMap: PixelMap = undefined; // 需要获取图片PixelMap信息 let notificationRequest: notificationManager.NotificationRequest = { id: 1, content: { contentType: notificationManager.ContentType.NOTIFICATION_CONTENT_PICTURE, picture: { title: 'test_title', text: 'test_text', additionalText: 'test_additionalText', briefText: 'test_briefText', expandedTitle: 'test_expandedTitle', picture: imagePixelMap } } }; // 发布通知 notificationManager.publish(notificationRequest, (err) => { if (err) { console.error(`Failed to publish notification. Code is ${err.code}, message is ${err.message}`); return; } console.info('Succeeded in publishing notification.'); });
注:
SlotType
系统能力:以下各项对应的系统能力均为SystemCapability.Notification.Notification
名称 | 值 | 说明 |
---|---|---|
UNKNOWN_TYPE | 0 | 未知类型。 |
SOCIAL_COMMUNICATION | 1 | 社交类型。 |
SERVICE_INFORMATION | 2 | 服务类型。 |
CONTENT_INFORMATION | 3 | 内容类型。 |
OTHER_TYPES | 0xFFFF | 其他类型。 |
进度条通知
进度条通知也是常见的通知类型,主要应用于文件下载、事务处理进度显示。HarmonyOS提供了进度条模板,发布通知应用设置好进度条模板的属性值,如模板名、模板数据,通过通知子系统发送到通知栏显示。
开发步骤
-
导入模块
-
查询系统是否可以使用进度条模板,查询结果为支持downloadTemplate模板类通知。
-
构建进度条模板对象,并发布通知
import notify from '@ohos.notificationManager'
async aboutToAppear(){
// 1.判断当前系统是否支持进度条模板
this.isSupport = await notify.isSupportTemplate('downloadTemplate')
}
publishDownloadNotification(){
// 1.判断当前系统是否支持进度条模板
if(!this.isSupport){
// 当前系统不支持进度条模板
return
}
// 2.准备进度条模板的参数
let template = {
name: 'downloadTemplate',
data: {
progressValue: this.progressValue,
progressMaxValue: this.progressMaxValue
}
}
let request: notify.NotificationRequest = {
id: this.notificationId,
template: template,
wantAgent: this.wantAgentInstance,
content: {
contentType: notify.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
normal: {
title: this.filename + ': ' + this.state,
text: '',
additionalText: this.progressValue + '%'
}
}
}
// 3.发送通知
notify.publish(request)
.then(() => console.log('test', '通知发送成功'))
.catch(reason => console.log('test', '通知发送失败!', JSON.stringify(reason)))
}
为通知添加一个行为意图
注:就是点击通知消息会跳转到页面
WantAgent提供了封装行为意图的能力,这里所说的行为意图主要是指拉起指定的应用组件及发布公共事件等能力。HarmonyOS支持以通知的形式,将WantAgent从发布方传递至接收方,从而在接收方触发WantAgent中指定的意图。例如,在通知消息的发布者发布通知时,通常期望用户可以通过通知栏点击拉起目标应用组件。为了达成这一目标,开发者可以将WantAgent封装至通知消息中,当系统接收到WantAgent后,在用户点击通知栏时触发WantAgent的意图,从而拉起目标应用组件。
开发步骤
-
导入模块
-
创建WantAgentInfo信息
-
创建WantAgent。
-
构造NotificationRequest对象。
-
发布WantAgent通知。
-
用户通过点击通知栏上的通知,即可触发WantAgent的动作。
简单实现
import wantAgent, { WantAgent } from '@ohos.app.ability.wantAgent'
//通知的行为意图 就是点击通知会跳转到哪里
wantAgentInstance: WantAgent
async aboutToAppear(){
// 1.判断当前系统是否支持进度条模板
this.isSupport = await notify.isSupportTemplate('downloadTemplate')
// 2.创建拉取当前应用的行为意图
// 2.1.创建wantInfo信息
let wantInfo: wantAgent.WantAgentInfo = {
wants: [
{
bundleName: 'com.example.myapplication',
abilityName: 'EntryAbility',
}
],
requestCode: 0,
operationType: wantAgent.OperationType.START_ABILITY,
wantAgentFlags: [wantAgent.WantAgentFlags.CONSTANT_FLAG]
}
// 2.2.创建wantAgent实例
this.wantAgentInstance = await wantAgent.getWantAgent(wantInfo)
}
publishDownloadNotification(){
// 1.判断当前系统是否支持进度条模板
if(!this.isSupport){
// 当前系统不支持进度条模板
return
}
// 2.准备进度条模板的参数
let template = {
name: 'downloadTemplate',
data: {
progressValue: this.progressValue,
progressMaxValue: this.progressMaxValue
}
}
let request: notify.NotificationRequest = {
id: this.notificationId,
template: template,
wantAgent: this.wantAgentInstance, //这里指定一下 简单实现
content: {
contentType: notify.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
normal: {
title: this.filename + ': ' + this.state,
text: '',
additionalText: this.progressValue + '%'
}
}
}
// 3.发送通知
notify.publish(request)
.then(() => console.log('test', '通知发送成功'))
.catch(reason => console.log('test', '通知发送失败!', JSON.stringify(reason)))
}