ArkTS快速入门----Harmony OS 开发(第四期)
ArkTS快速入门
初始例程解析
- 装饰器:用来装饰结构体,方法,变量,赋予其特殊含义
-
@Entry 标记当前组件是入口组件,即可以作为一个独立页面直接访问,若没有该装饰器,则该组件不能被独立访问,只能作为其他组件的陪衬。一个页面只能有一个@Entry修饰的组件,加载页面时会首先创建并呈现@Entry标记的组件,只有@Entry修饰的组件及其子组件才能被显示在页面上。
-
@Component 标记下面这个是自定义组件----(组件可以理解为模块,可以直接将整体拿来使用)。
-
@State 标记该变量是状态变量,其值的变化会触发与其关联的组件的刷新(即所有调用了该变量的组件的刷新)。
-
@Link 标记的变量可以与父组件的@State变量建立双向数据绑定,任何一方做的修改都会使另一方一同改变。 @Link变量不能在组件内部进行初始化。@Link 标记的变量必须在生命周期函数中完成初始化。
如上图一,在父组件RankPage中通过’$'操作符创建"引用",使父组件中的isSwitchDataSource与子组件TitleComponent中定义的isRefreshData进行双向数据绑定。
5.@Prop与@State有相同的语义,但初始化方式不同。@Prop装饰的变量必须使用其父组件提供的@State变量进行初始化,允许组件内部修改@Prop变量,但更改不会通知给父组件,即@Prop属于单向数据绑定。
如上图,@Prop装饰的变量必须使用其父组件提供的@State变量进行初始化
- @Builder 按引用传递参数时,传递的参数可为状态变量,且状态变量的改变会引起@Builder方法内的UI刷新。
@Builder 就是用于页面构建的装饰器,表示其后面的函数可以用来构建页面(只要函数被调用),函数可以定义在组件外(全局页面构建函数
),也可以定义在组件内部(局部页面构建函数
,此时不用加function关键字
)。
7.@Styles 就是用于页面构建样式的装饰器,表示其后面的函数可以用来构建页面的样式(只要函数被调用---- .函数名()),要求:函数内部的所有样式必须是通用样式。
8.@Extend(组件名a) 就是与@Styles 装饰器类似,只是其后面的函数中的样式不全是通用样式,其中有样式为a的特有样式。注意:@Extern 只能定义在全局,不能定义在组件内。
9.@Provide和@Consume 就是 @Provide修饰的状态变量自动对提供者组件的所有后代组件可用,后代组件通过使用@Consume装饰的变量来获得对提供的状态变量的访问。@Provide作为数据的提供方,可以更新其子孙节点的数据,并触发页面渲染。@Consume在感知到@Provide数据的更新后,会触发当前自定义组件的重新渲染,反之同理,@Consume 修饰的变量不用初始化,但要求变量名与祖先组件一致(方便绑定),也就是说:@Consume
装饰的变量通过相同的属性名绑定其祖先组件内的@Provide
装饰的变量
-
内置组件: ArkUI提供的组件,分为两种:
1.容器组件:用来完成页面布局,如Row(该组件内部的所有元素自动成为一行),Column(该组件内部的所有元素自动成为一列)。
2.基础组件:自带样式和功能的页面元素,如Text。
3.自定义组件: 将上面两种组件组合,可以直接拿来使用。
- 属性方法:设置对应组件的UI样式。
例子:
.fontSize(50) //字体大小
.fontWeight(FontWeight.Bold) //字体宽度
.fontColor("#16B") //字体颜色
.height('100%') //字体高度占满
- 事件方法:设置该组件的触发的事件。
例子:
.onClick(()=>{ //点击事件
//……处理点击事件
this.message="hello ArkTS!"
})
给组件传参数
值传递(默认)
上图就是默认的参数传递----值传递,Text()标签调用本组件内部的变量content,外部调用ToDoItem()组件时要为变量content进行初始化,形式:ToDoItem({变量名:值});
引用传递
上图便是按引用传递,用$$ : {参数名1:参数类型,参数名2:参数类型……}
接收。函数体内要使用参数:$$.参数名
。外部调用组件时,变量初始化的方式与值传递的一致。
关于${……}
${……}
是一种JavaScript中的模板字符串语法,用于在字符串中插入动态的变量或表达式。
生命周期函数
-
自定义组件生命周期:指自定义组件从创建到销毁的整个过程。
提供了两个生命周期回调函数 aboutToAppear(),aboutToDisappear(),前者在创建自定义组件后,执行build组件前自动调用,可用于初始化页面要展示的数据或申请定时器资源。 后者在该自定义组件被销毁时调用,可用于释放不再使用的资源。
- 对于@Entry标记的页面入口组件,系统还单独给出了三个生命周期回调函数:onPageShow(),onBackPress(),onPageHide(),第一个函数在页面每次显示时触发,第二个函数在用户点击返回键回到另一个页面时调用(原页面存在),返回值为true时,由页面处理返回逻辑,页面不返回,返回false时由系统处理返回逻辑(默认),第三个函数在用户删除本页面时调用。
循环渲染
循环渲染:使用ForEach迭代数组,并为每个数组项创建相应组件。
ForEach()有三个参数,参数1为数组,用于存储要展示的所有数据,可以为空数组,为空数组时,不会创建子组件。参数2为数组中的每个元素创建对应的组件,固定参数:item:数组中的数据项,index:数组中的数据项索引(可选),参数3(string类型)为数据源 的每个数组项生成唯一且持久的键值,
函数返回值为开发者自定义的键值生成规则,如果参数未填,则框架默认的键值生成函数为
(item: T, index: number) => { return index + ‘__’ + JSON.stringify(item); }
网络权限申请
凡是涉及到网络,都有提前申请权限。
权限类型分为system_grant(系统授权)和user_grant(用户授权)两种类型。
申请网络权限步骤:
1.官网点击文档—>指南—>开发—>安全—>访问控制授权申请
2.点击应用权限列表,查看你要申请的权限类型,分为system_grant(系统授权)和user_grant(用户授权)两种类型。
3.查看权限属性表,若为system_grant(系统授权),则只需要填写"name"这一个属性,否则这要参考下表看是否填写:
4.回到项目,找到module.json5文件
5.找到下图位置,添加"requestPermissions":[],可以看出其是一个数组,在这个数组以一个{}为一组作为一个申请权限,如下图就代表两个权限。
6.更改完成后,保存文件module.json5即可。
基础组件
Image组件
声明Image组件
1.string 格式,通常用来加载网络图片,需要申请网络访问权限: ohos.permission.INTERNET—权限名
Image('http://xxx.png') //图片网络地址
2.PixelMap 格式,可以加载像素图,常用在图片编辑中
Image(pixelMapObject)
- Resource格式,加载本地图片,推荐使用
Image($r('app.media.图片名'))
//app. 是固定写法,
//media.图片名 是resource文件夹下的base下的图片路径,无后缀
Image($rawfile('图片名.后缀')) //resource文件夹下
//的rawfile下的图片路径
Image组件属性
.width(100) //图片宽度
.height(120) //图片高度
.borderRadius(10) //边框圆角(让图片边缘变圆滑)
.backgroundColor(0xCCCCCC) //设置图片背景色为灰色
.interpolation(ImageInterpolation.High) //图片插值,
//消除锯齿
设置缩放类型
//设置图片的缩放类型,objectFit的参数类型为ImageFit
.objectFit(ImageFit.Cover)
//ImageFit包含以下几种类型:
//Contain:保持宽高比进行缩小或者放大,使得图片完全显示在显示边界内
//Cover(默认值):保持宽高比进行缩小或者放大,使得图片
//两边都大于或等于显示边界
//Auto:自适应显示
//Fill:不保持宽高比进行放大缩小,使得图片充满显示边界
//ScaleDown:保持宽高比显示,图片缩小或者保持不变
//None:保持原有尺寸显示
Text组件
声明Text组件
-
string格式,直接填写文本内容
Text('图片宽度')
-
Resource格式,读取本地资源文件
Text($r(‘app.string.关键字’))
//这样文本可以根据设备环境的不同而自动改变
//限定词目录 ----en_US(英文),zh_CN(中文):里面存储的是一些文本资源,系统根据设备语言环境,自动选择目录,访问string.json文件,查找匹配的关键字,返回其对应的value。
若几个目录中未找到匹配的关键字,则进入base目录中的string.json文件查找
string.json配置对应文本
- 找到三个目录下的string.json文件
如下图:
- 在文件中,以一个{}为一组,"name"属性就是查找对应文本的关键字,"value"属性就是显示在页面上的文本。
3.按照上面格式创建新的本地文本即可。
Text组件属性
.lineHeight(32) //行高
.fontSize(20) //字体大小
.fontColor('#ff1876f8') //字体颜色
.fontWeight(FontWeight.Medium) //字体粗细,参数类型就是下表中的FontWeight类
.textAlign(TextAlign.Start) //文本对齐,TextAlign类分为
//Start(默认)水平对齐首部,Center水平居中对齐, End水平对齐尾部
设置文本超长显示
//当文本内容较多超出了Text组件范围的时候,
//您可以使用textOverflow设置文本截取方式,
//需配合maxLines使用,单独设置不生效,maxLines用于设置文本显示最大行数。
.maxLines(1) //最大显示一行
.textOverflow({overflow:TextOverflow.Ellipsis})
//overflow的类型为TextOverflow.Ellipsis
//将textOverflow设置为Ellipsis,它将显示不下的文本用 "..." 表示
设置文本装饰线
//使用decoration设置文本装饰线样式及其颜色,decoration包含type和color两个参数,
//其中type用于设置装饰线样式,参数类型为TextDecorationTyp,color为可选参数。
.decoration({ type: TextDecorationType.Underline, color: Color.Black })
//TextDecorationTyp包含以下几种类型:
//None:不使用文本装饰线
//Overline:文字上划线修饰
//LineThrough:穿过文本的修饰线
//Underline:文字下划线修饰
TextInput组件
TextInput组件用于输入单行文本,响应输入事件。
声明TextInput组件
TextInput({ placeholder: '请输入账号' })
TextInput组件属性
TextInput()
.fontColor(Color.Blue) //字体颜色
.fontSize(20) //字体大小
.fontStyle(FontStyle.Italic) //字体形式
.fontWeight(FontWeight.Bold) //字体粗细
.fontFamily('Arial') //字体
设置输入提示文本
提示功能使用placeholder属性,还可以使用placeholderColor和placeholderFont分别设置提示文本的颜色和样式
TextInput({ placeholder: '请输入账号' })
.placeholderColor(0x999999) //设置提示文字颜色
.placeholderFont({ size: 20, weight: FontWeight.Medium,
family: 'cursive', style: FontStyle.Italic })
//设置提示文字样式
设置输入类型
可以使用type属性来设置输入框类型。
TextInput({ placeholder: '请输入密码' })
.type(InputType.Password) //设置输入框为密码框类型
//type的参数类型为InputType
//Normal:基本输入模式。支持输入数字、字母、下划线、空格、特殊字符
//Password:密码输入模式
//Email:e-mail地址输入模式
//Number:纯数字输入模式
获取输入文本
可以给TextInput设置onChange事件,输入文本发生变化时触发回调,实时获取用户输入的文本信息。
.onChange((value: string) => {
this.text = value
})
Button组件
Button组件主要用来响应点击事件,可以包含子组件。
声明Button组件
Button('按钮上显示的文本', { type: ButtonType.Capsule, stateEffect: true })
//第一个参数类型是ButtonType,用于设置按钮显示样式,
//ButtonType 有三种类型:
//Capsule:胶囊型按钮(圆角默认为高度的一半)
//Circle:圆形按钮。
//Normal:普通按钮(默认不带圆角)
//第二个参数类型是boolean,用于按钮按下时是否开启
//按压态显示效果
Button组件属性
.width('90%') //按钮宽度
.height(40) //按钮高度
.fontSize(16) //按钮字体大小
.fontWeight(FontWeight.Medium) //按钮字体粗细
.backgroundColor('#007DFF') //按钮背景颜色
设置按钮点击事件
.onClick(() => {
// 处理点击事件逻辑
})
按钮可以包含子组件
Button({ type: ButtonType.Circle, stateEffect: true }) {
Image($r('app.media.icon_delete'))
.width(30)
.height(30)
//按钮中包含了图片,则这个图片就是按钮。
}
.width(55)
.height(55)
.backgroundColor(0x317aff)
Slider组件(滑块)
声明Slider组件
Slider({max:100,min:0,value:40,step:10,style:SliderStyle.OutSet,direction:Axis.Horizontal,reverse:false})
Slider({参数……})
max 进度条最大值
min 进度条最小值
value 当前滑块所在进度条的位置对应的值
step 步长,每次滑动变化的值
style:SliderStyle.InSet 滑块在进度条内部还是外部
(style:SliderStyle.OutSet)direction:Axis.Horizontal 进度条是水平还是垂直的
(direction:Axis.Vertical)reverse :false 是否反向滑动(即水平的进度条变为max在左,
min在右,垂直的进度条变为max在上,min在下)
OutSet与Inset样式
Slider组件属性
.width('90%') //进度条的宽
.showTips(true) //是否展示value的百分比提示
.blockColor('#36d') //滑块颜色
.onChange((value)=>{
//进度条改变时的响应事件,value就是当前滑块值
})
LoadingProgress组件
LoadingProgress组件用于显示加载进展,比如应用的登录界面,当我们点击登录的时候,显示的“正在登录”的进度条状态。
LoadingProgress组件声明与属性
LoadingProgress()
.color(Color.Blue)
.height(60)
.width(60)
Video组件
Video组件可以帮助 实现视频的播放功能并控制其播放状态,常见的视频播放场景包括观看网络上的较为流行的短视频,也包括查看我们存储在本地的视频内容。
声明Video组件
Video({src,currentProgressRate,previewUri,controller})
其中包含四个可选参数:src、currentProgressRate、previewUri和controller。
- src表示视频播放源的路径,可以支持本地视频路径和网络路径,使用网络地址,需要注意的是需要在module.json5文件中申请网络权限:
"ohos.permission.INTERNET"
。 - currentProgressRate表示视频播放倍速,其参数类型为
PlaybackSpeed
,取值支持
(PlaybackSpeed.Speed_Forward_1_00_X),
Speed_Forward_0_75_X,Speed_Forward_2_00_X…………,
默认值为1.0倍速。 - previewUri表示视频未播放时的预览图片路径
- controller表示视频控制器
Video组件属性
除了支持组件的尺寸设置、位置设置等通用属性外,还有私有的一些属性:
.muted() //是否静音。默认值:false
.autoPlay() //是否自动播放。默认值:false
.controls() //控制视频播放的控制栏是否显示,默认值:true
.objectFit() //设置视频显示模式。默认值:Cover
.loop() //是否单个视频循环播放。默认值:false
属性objectFit 中视频显示模式包括Contain、Cover、Auto、Fill、ScaleDown、None 6种模式,默认情况下使用ImageFit.Cover(保持宽高比进行缩小或者放大,使得图片两边都大于或等于显示边界),其他效果(如自适应显示、保持原有尺寸显示、不保持宽高比进行缩放等)可以根据具体使用场景/设备来进行选择。
Video组件回调事件介绍
事件名称 | 功能描述 |
---|---|
onStart(event:() => void) | 播放时触发该事件。 |
onPause(event:() => void) | 暂停时触发该事件。 |
onFinish(event:() => void) | 播放结束时触发该事件。 |
onError(event:() => void) | 播放失败时触发该事件。 |
onPrepared(callback:(event?: { duration: number }) => void) | 视频准备完成,还未开始播放时触发该事件,通过duration可以获取视频总时长,单位为s。 |
onSeeking(callback:(event?: { time: number }) => void) | 操作进度条过程时上报时间信息,单位为s,参数event中的成员变量time用于存放播放时间。 |
onSeeked(callback:(event?: { time: number }) => void) | 操作进度条完成后,上报播放时间信息,单位为s,参数event中的成员变量time用于存放播放时间。 |
onUpdate(callback:(event?: { time: number }) => void) | 播放进度变化时触发该事件,单位为s,更新时间间隔为250ms,参数event中的成员变量time用于存放播放时间。 |
onFullscreenChange(callback:(event?: { fullscreen: boolean }) => void) | 在全屏播放与非全屏播放 |
例子:
Video({ ... })
.onUpdate((event) => {
this.currentTime = event.time;
this.currentStringTime =
changeSliderTime(this.currentTime); //更新事件
})
.onPrepared((event) => {
prepared.call(this, event); //准备事件
})
.onError(() => { //prompt为系统自带,直接调用
prompt.showToast({
duration: COMMON_NUM_DURATION, //播放失败事件
message: MESSAGE
//视频播放失败后要展示给用户的信息。
});
...
})
其中,onUpdate更新事件在播放进度变化时触发,从event中可以获取当前播放进度,从而更新进度条显示事件(changeSliderTime()用户自定义的方法),比如视频播放时间从24秒更新到30秒。onError事件在视频播放失败时触发,在CommonConstants.ets中定义了常量类MESSAGE,所以在视频播放失败时会显示“请检查网络”。const MESSAGE: string = '请检查网络'
自定义控制器的组成与实现
(样式没有特殊要求的话,建议直接用原生的控制器)
Video组件的原生控制器样式相对固定,当我们对页面的布局色调的一致性有所要求,或者在拖动进度条的同时需要显示其百分比进度时,原生控制器就无法满足需要了。
- 首先要模拟出上图这种进度条效果。
@Component
export struct VideoSlider {
...
build() {
Row(...) {
Image(...) //开始还是暂停播放的图片
Text(...) //已经视频播放了的时长
Slider(...) //进度条
Text(...) //视频总时长
}
...
}
}
- 两个Text组件显示的时长是由Slider组件的
onChange((value: number, mode: SliderChangeMode) => void)
回调事件来进行传递的(详细请见上面的Slider组件介绍),而Text组件的数值与视频播放进度数值value则是通过@Provide
与@Consume
装饰器进行的数据联动。
这里需要把以上的自定义的播放栏写成一个组件,作为整体页面的一个子组件。
自定义播放栏(子组件)
/*
* 自定义播放栏的实现
*
* 自定义播放栏组件
* */
@Component
export struct VideoSlider{
private controller: VideoController;
//系统自带的类,用于对视频的控制
//视频是否在播放
@Consume isPlay:boolean;//默认值是false
//显示视频时长
@Consume durationTime:number;
// 数字类型的视频时长总时间(数值形式,用于接收数据)
@Consume durationStringTime:string;
//字符类型的视频时长总时间(字符串形式,用于界面展示数据)
//当前视频进度时间
//(数值形式,用于接收数据,字符串形式,用于界面展示数据)
@Consume currentTime:number;
@Consume currentStringTime:string;
//通过控制滑动条拖动,来控制视频视频进度
sliderOnchange(value: number, mode: SliderChangeMode)
{
this.currentTime =
Number.parseInt(value.toString());
//获得当前的进度条的滑块值
this.controller.setCurrentTime(Number.parseInt(
value.toString()), SeekMode.Accurate);
//将当前滑块的值指定为视频播放的进度位置
并指定跳转模式为SeekMode.Accurate(类型为SeekMode)
}
build(){
Row({space:12}){
//播放图标
Image(this.isPlay ? $r("app.media.pause") :
$r("app.media.start")) //播放暂停按钮
.onClick(() =>{
if(!this.isPlay){
this.controller.start(); //视频开始播放
this.isPlay = true
} else {
this.controller.pause(); //视频暂停
this.isPlay = false
}
})
.width(20)
.height(20)
//显示当前播放时间
Text(this.currentStringTime)
.fontColor('#ff0099ff')
//播放进度条
Slider({
value:this.currentTime,
min:0,
max:this.durationTime,
//进度条长度就是视频的总长度
step:1,
})
.width('55%')
.trackColor(Color.Orange)//设置滑轨的背景颜色
.blockColor(Color.Red)//滑块的颜色
.selectedColor('#ff0099ff')
//滑块的已滑动部分颜色
.showSteps(true)
//展示出播放进度
.onChange((value: number,
mode: SliderChangeMode) => {
this.sliderOnchange(value, mode);
//执行自定义的滑块拖动函数
})
//视频的总时间
Text(this.durationStringTime)
}
.backgroundColor('#ff7d6e80')
.width('100%')
.height('6%')
}
}
自定义播放栏与视频结合(父组件)
/*
* 注意:预览器测试看不了视频,视频只能在模拟器上正常测试预览
* 视频 video组件的使用
* */
//导入自定义播放控制栏
import { VideoSlider } from './VideoSlider'
@Entry
@Component
struct viedoPage{
@State videoSrc: Resource = $rawfile('video.mp4')
//视频路径
@State playSpeed:PlaybackSpeed =
PlaybackSpeed.Speed_Forward_1_75_X //播放速度
@State previewUri: Resource =
$r('app.media.previewUriVideoo')
//视频未播放时的预览图片路径,默认不显示图片。
//定义一个controller对象,用于自定义播放控制栏
private controller = new VideoController();
@Provide isPlay:boolean = false; //默认值是false
//显示视频总时长
@Provide durationTime:number = 0;
@Provide durationStringTime:string = '00:00'
//当前视频进度时间
@Provide currentTime:number = 0;
@Provide currentStringTime:string = '00:00'
//视频准备完成时触发该事件
prepared(event){
this.durationTime = event.duration;
//获取视频时长 单位:秒
let second:number = event.duration % 60;
//求出总时长的秒数
let min:number = parseInt((event.duration
/ 60).toString()) ;
//求出总时长的分钟数
let head = min < 10 ? `${0}${min}` :min;
let end = second < 10 ? `${0}${second}` : second;
this.durationStringTime = `${head}:${end}`;
//分 : 秒 显示
}
//获取当前播放进度时间
changeSliderTime(value){
let second:number = value % 60;
let min:number = parseInt((value
/ 60).toString()) ;
let head = min < 10 ? `${0}${min}` :min;
let end = second < 10 ? `${0}${second}` : second;
let nowTime = `${head}:${end}`;
return nowTime;
}
build(){
Column(){
//自定义播放栏
Video({
src:this.videoSrc,
previewUri:this.previewUri,
currentProgressRate:this.playSpeed,
controller:this.controller
}).height('94%')
.width('100%')
//控制视频播放的控制栏不显示
.controls(false)
//视频准备完成时触发该事件
.onPrepared((event) =>{
//调用对象
this.prepared(event)
})
//获取当前播放进度时间
.onUpdate((event:any) =>{
this.currentTime = event?.time;
this.currentStringTime =
this.changeSliderTime(this.currentTime)
})
//播放结束时触发该事件
.onFinish(() =>{
//切换播放图标
this.isPlay = false;
promptAction.showToast({
message:'播放完成',
duration:2000
})
})
//播放失败触发该事件
.onError(() =>{
//显示文本提示框
promptAction.showToast({
message:'播放失败',
duration:2000
})
})
//调用自定义播放栏组件
VideoSlider({controller:this.controller})
}
}
}
private controller = new VideoController();
使用资源引用类型
将资源文件(字符串、图片、音频等)统一存放于resources目录下,便于开发者统一维护。
与上面Text()组件,string.json文件配置文本一样,我们同样可以在resources目录下的base目录,en_US目录,zh_CN目录下定义float.json文件,color.json文件等,配置相应的"name"与"value"。查找路径为$r{app.float.关键字}
,$r{app.color.关键字}
,一切都可以由开发者自己来定义。
容器组件
ArkTS提供容器组件来布局页面
Column和Row组件的使用
Column表示沿垂直方向布局的容器(组件内部的所有子组件自动排成一列)
Row表示沿水平方向布局的容器(组件内部的所有子组件自动排成一行)
主轴和交叉轴概念
- 主轴:在Column容器中的子组件是按照从上到下的垂直方向布局的,其主轴的方向是垂直方向;在Row容器中的组件是按照从左到右的水平方向布局的,其主轴的方向是水平方向。
- 交叉轴:与主轴垂直相交的轴线。
浅色为主轴,深色为交叉轴。
属性介绍
属性调用的例子:(交叉轴同理)
Row() {
……………………
}
.justifyContent(FlexAlign.SpaceBetween) //主轴对齐
.width('100%')
- 主轴方向的对齐(justifyContent)
子组件在主轴方向上的对齐使用属性justifyContent来设置,其参数类型是FlexAlign,FlexAlign定义了以下几种类型:
- Start:元素在主轴方向首端对齐,第一个元素与行首对齐,同时后续的元素与前一个对齐。
- Center:元素在主轴方向中心对齐,第一个元素与行首的距离以及最后一个元素与行尾距离相同。
- End:元素在主轴方向尾部对齐,最后一个元素与行尾对齐,其他元素与后一个对齐。
- SpaceBetween:元素在主轴方向均匀分配弹性元素,相邻元素之间距离相同。 第一个元素与行首对齐,最后一个元素与行尾对齐。
- SpaceAround:元素在主轴方向均匀分配弹性元素,相邻元素之间距离相同。 第一个元素到行首的距离和最后一个元素到行尾的距离是相邻元素之间距离的一半。
- SpaceEvenly:元素在主轴方向等间距布局,无论是相邻元素还是边界元素到容器的间距都一样。
- 交叉轴方向的对齐(alignItems)
子组件在交叉轴方向上的对齐方式使用alignItems的属性来设置
Column容器的属性alignItems(),其参数类型为HorizontalAlign(水平对齐),HorizontalAlign定义了以下几种类型:
- Start:设置子组件在水平方向上按照起始端对齐。
- Center(默认值):设置子组件在水平方向上居中对齐
- End:设置子组件在水平方向上按照末端对齐
Row容器的属性alignItems(),其参数类型为VerticalAlign(垂直对齐),VerticalAlign定义了以下几种类型:
- Top:设置子组件在垂直方向上居顶部对齐
- Center(默认值):设置子组件在竖直方向上居中对齐
- Bottom:设置子组件在竖直方向上居底部对齐
Column和Row容器的接口都有一个可选参数space,表示子组件在主轴方向上的间距。
Column(value?:{space?: string | number}){
………………
}
Row(value?:{space?: string | number}){
………………
}
List组件的使用
列表(List)是一个复杂的容器组件,具有以下特点:
- 列表项(ListItem)数量过多超过屏幕后,会自动提供滚动功能。
- 列表项(ListItem)既可以纵向排列,也可以横向排列。
例子:
List({space:10}) //space控制子组件(表项)的间距
{
ForEach([1,2,3,4],(item) =>{
ListItem(){ //列表项(ListItem)不是容器组件,
Text("999") //只是一种约束。
} //表项中只能包含一个根组件
//(如果有多个组件,则可以用一个容器组件"打包")
})
}
.width('100%') //整个列表宽度
.listDirection(Axis.Vertical) //列表方向,默认纵向
// Axis.Vertical 纵向
//Axis.Horizontal 横向
设置列表分割线
List组件子组件ListItem之间默认是没有分割线的,部分场景子组件ListItem间需要设置分割线,这时候您可以使用List组件的divider属性。divider属性包含四个参数:
- strokeWidth: 分割线的线宽。
- color: 分割线的颜色。
- startMargin:分割线距离列表侧边起始端的距离。
- endMargin: 分割线距离列表侧边结束端的距离。
List列表滚动事件监听
List组件提供了一系列事件方法用来监听列表的滚动,您可以根据需要,监听这些事件来做一些操作:
- onScroll:列表滑动时触发,返回值scrollOffset为滑动偏移量(相较于滑动前的位置),scrollState为当前滑动状态。
- onScrollIndex:列表滑动时触发,返回值分别为滑动起始位置索引值与滑动结束位置索引值。
- onReachStart:列表到达起始位置时触发。
- onReachEnd:列表到底末尾位置时触发。
- onScrollStop:列表滑动停止时触发。
List({ space: 10 }) {
ForEach(this.arr, (item) => {
ListItem() {
Text(`${item}`)
...
}
}, item => item)
}
.onScrollIndex((firstIndex: number, lastIndex: number)
=> { //两个参数分别用来接收滑动开始的索引位置和
//结束的索引位置
console.info('first' + firstIndex)
console.info('last' + lastIndex)
})
.onScroll((scrollOffset: number, scrollState: ScrollState)
=> { //两个参数分别用来接收滑动的相对偏移量
//和结束的索引位置
console.info('scrollOffset' + scrollOffset)
console.info('scrollState' + scrollState)
})
.onReachStart(() =>
{
console.info('onReachStart')
})
.onReachEnd(() =>
{
console.info('onReachEnd')
})
.onScrollStop(()
=> {
console.info('onScrollStop')
})
子组件ListItem属性
.swipeAction({end:……})
用于设置ListItem的划出组件。
参数:
-
start: ListItem向右划动时item左边的组件(List垂直布局时)或ListItem向下划动时item上方的组件(List水平布局时)。
-
end: ListItem向左划动时item右边的组件(List垂直布局时)或ListItem向上划动时item下方的组件(List水平布局时)。
-
edgeEffect: 滑动效果(可选参数)。
start和end参数至少要存在一个,且两者后面都是@builder装饰的函数。
start和end对应的@builder函数中顶层必须是单个组件,不能是if/else、ForEach、LazyForEach语句。
Grid组件的使用(网格布局)
Grid组件为网格容器,Grid组件一般和子组件GridItem一起使用,Grid列表中的每一个条目对应一个GridItem组件。(与List组件类似)
Grid组件属性
.columnsTemplate('1fr 1fr 1fr 1fr')
//网格布局的列数,4个'1fr'就代表4列
.rowsTemplate('1fr 1fr 1fr 1fr')
//网格布局的行数,4个'1fr'就代表4行
.columnsGap(10)
//网格布局的列间距
.rowsGap(10)
//网格布局的行间距
.height(300)
//网格布局的高
.width("100%")
//网格布局的宽
此外,Grid像List一样也可以使用onScrollIndex来监听列表的滚动。
Tabs组件的使用(页签)
ArkUI开发框架提供了一种页签容器组件Tabs,开发者通过Tabs组件可以很容易的实现内容视图的切换。
Tabs组件仅可包含子组件TabContent,每一个页签对应一个内容视图即TabContent组件。(与List的子组件Listitem类似)
Tabs({ barPosition: BarPosition.Start,
controller: this.controller }) {
TabContent() {
该页签对应的页面上的内容
}
.tabBar('green')
}
代码运行效果如下:
TabContent的tabBar属性用于设置TabBar的显示内容(页签上显示的内容)
- TabContent组件不支持设置通用宽度属性,其宽度默认撑满Tabs父组件。
- TabContent组件不支持设置通用高度属性,其高度由Tabs父组件高度与TabBar组件高度决定。
.barWidth('100%') // 设置TabBar宽度
.barHeight(60) // 设置TabBar高度(TabBar代表页签)
.width('100%') // 设置Tabs组件宽度
.height('100%') // 设置Tabs组件高度
.backgroundColor(0xF5F5F5) // 设置Tabs组件背景颜色
设置TabBar(页签)布局模式
BarMode.Fixed:所有TabBar平均分配barWidth宽度(纵向时平均分配barHeight高度),页签不可滚动。
BarMode.Scrollable:每一个TabBar均使用实际布局宽度,超过总长度(横向Tabs的barWidth,纵向Tabs的barHeight)后可滑动。调用: .barMode(BarMode.Scrollable)
设置TabBar(页签)位置和排列方向
barPosition的值可以设置为BarPosition.Start(默认值)
和BarPosition.End
vertical属性用于设置页签的排列方向,当vertical的属性值为false(默认值)时页签横向排列,为true时页签纵向排列。
BarPosition.Start, vertical属性方法设置为false(默认值)时,页签位于容器顶部。vertical属性方法设置为true时,页签位于容器左侧。
Tabs({ barPosition: BarPosition.Start }) {
...
}
.vertical(false)
.barWidth('100%')
.barHeight(60)
BarPosition.End,vertical属性方法设置为false时,页签位于容器底部。vertical属性方法设置为true时,页签位于容器右侧。
Tabs({ barPosition: BarPosition.End}) {
...
}
.vertical(true)
.barWidth(100)
.barHeight(200)
自定义TabBar样式
TabBar的默认显示效果如下所示:
TabContent的tabBar属性除了支持string类型,还支持使用@Builder装饰器修饰的函数。
您可以使用@Builder装饰器,构造一个生成自定义TabBar(页签)样式的函数,实现下面的底部页签效果。
@Entry
@Component
struct TabsExample {
@State currentIndex: number = 0;
private tabsController: TabsController =
new TabsController();
@Builder TabBuilder(title: string,
targetIndex: number, selectedImg: Resource,
normalImg: Resource) {
Column() {
Image(this.currentIndex ===
targetIndex ? selectedImg : normalImg)
.size({ width: 25, height: 25 })
Text(title)
.fontColor(this.currentIndex ===
targetIndex ? '#1698CE' : '#6B6B6B')
}
.width('100%')
.height(50)
.justifyContent(FlexAlign.Center)
.onClick(() => {
this.currentIndex = targetIndex;
this.tabsController.changeIndex(this.currentIndex);
})
}
build() {
Tabs({ barPosition: BarPosition.End,
controller: this.tabsController }) {
TabContent() {
页面内容1
}
.tabBar(this.TabBuilder('首页', 0,
$r('app.media.home_selected'),
$r('app.media.home_normal')))
TabContent() {
网页内容2
}
.tabBar(this.TabBuilder('我的', 1,
$r('app.media.mine_selected'),
$r('app.media.mine_normal')))
}
.barWidth('100%')
.barHeight(50)
.onChange((index: number) => { //获得该页面的id
this.currentIndex = index;
})
}
}
Tabs组件的第二个参数tabsController为控制Tab页面切换的类。
上面代码解释:
private tabsController: TabsController = new TabsController();
TabsController类是系统自带的一个类,用于在Tabs组件中控制页面的切换
使用@Builder修饰TabBuilder函数,生成由Image和Text组成的页签。同时也给Tabs组件设置了TabsController控制器。
当点击某个页签时,调用changeIndex方法通过每个页面的唯一id来进行不同页面切换。每次点击页签,就会调用该页签对应的渲染函数(@Builder修饰的函数)。
最后还需要给Tabs添加onChange事件,Tab页签切换后触发该事件,这样当我们左右滑动内容视图的时候,页签样式也会跟着改变。
Swiper组件(轮播图)
Swiper声明
Swiper(){……}
Swiper属性
.index(0) //默认显示显示第几页,默认值为 0。
.autoPlay(true) //是否自动播放,默认值为 false
.interval(3000) //设置自动轮播时,播放的时间间隔,
//单位毫秒,默认是 3000。
.indicator(true) //是否显示导航点指示器,默认显示。
.loop(true) //是否开启循环显示,也就是说
//当翻页到最后一页再往下翻页是否会回到第一页,默认开启
也可以通过SwiperController这个控制器而不使用autoPlay()自动轮播函数,而是借助于定时器来实现轮播图的效果。
Swiper(this.controller){……}
绑定轮播控制器
然后再定义一个函数,通过定时器setInterval() 在函数内部借助swiper.showNext() 来实现翻页效果,然后我们设置定时器每隔3000毫秒执行一次:
在aboutToAppear()函数中调用上面的启动轮播的函数:
在aboutToDisappear()函数中关闭定时器:
轮播图响应事件
需求:点击不同的轮播图需要跳转到不同的页面。
onChange(Index:number):当轮播图页面切换时会回调当前方法,显示当前第几页,即放回页面id。
可以先借助onChange()
函数来获取到当前页面的索引,再在onClick()
函数中根据页面索引不同而跳转到不同的页面。
Stack组件(堆叠组件)
Stack组件为容器组件,容器内可包含各种子组件。其中子组件默认进行居中堆叠(像栈一样,后面元素覆盖在前面元素的上面)。子元素被约束在Stack下,进行自己的样式定义以及排列。
Stack({ alignContent: Alignment.Center}) {……}
- 参数alignContent : 设置子组件在容器内的对齐方式。默认值:
Alignment.Center
其他常用
定时器setInterval与Timeout
setInterval() 方法可按照指定的周期(以毫秒计)来调用函数或计算表达式。
setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的参数。
提示: 1000 毫秒= 1 秒。
提示: 如果你只想执行一次可以使用setTimeout() 方法。
setInterval(function, milliseconds, param1, param2, …)
返回值: 返回一个 ID(数字),可以将这个ID传递给clearInterval(),clearTimeout() 以取消执行。
Dialog 组件 (弹窗)
弹窗是一种模态窗口,通常用来展示用户当前需要的或用户必须关注的信息或操作。在弹出框消失之前,用户无法操作其他界面内容。
警告弹窗(AlertDialog)
警告弹窗AlertDialog由以下三部分区域构成,对应下面的示意图:
- 标题区:为可选的。
- 内容区:显示提示消息。
- 操作按钮区:用户做”确认“或者”取消“等操作。
例程代码:
Button('点击显示弹窗')
.onClick(() => {
AlertDialog.show( //显示出警告弹窗
{
title: '删除联系人', // 标题
message: '是否需要删除所选联系人?', // 内容
autoCancel: false, // 点击弹窗外的区域时,
//是否关闭弹窗。
alignment: DialogAlignment.Bottom,
// 弹窗在竖直方向的对齐方式
offset: { dx: 0, dy: -20 },
// 弹窗相对alignment位置的偏移量
primaryButton: { //取消按钮
value: '取消', //内容可更改
action: () => { //响应的点击事件
console.info('777');
}
},
secondaryButton: { //删除按钮
value: '删除', //内容可更改
fontColor: '#D94838',
action: () => { //响应的点击事件
console.info('666');
}
},
cancel: () => { // 点击遮障层(弹窗外的区域)
//关闭dialog时的回调
console.info('Closed callbacks');
}
}
)
})
此外,您还可以使用AlertDialog,构建只包含一个操作按钮的确认弹窗,使用confirm响应操作按钮回调。
即将上面代码中两个按钮的部分(删除按钮和取消按钮)替换为下面的代码:
confirm: {
value: '确认', //内容可更改
action: () => {
console.info('888');
}
},
选择类弹窗—文本选择弹窗
例程代码:
@Entry
@Component
struct TextPickerDialogDemo {
@State select: number = 2;
private fruits: string[] = ['苹果', '橘子',
'香蕉', '猕猴桃', '西瓜']; //存储所有选项的内容
build() {
Column() {
Button('TextPickerDialog')
.margin(20)
.onClick(() => {
TextPickerDialog.show({ //显示出文本弹窗
range: this.fruits,
//设置文本选择器的选择范围
selected: this.select,
//设置初始选中项的索引值,这里是2,表示香蕉。
onAccept: (value: TextPickerResult) =>
{ // 点击弹窗中的“确定”按钮时触发该回调。
// 设置select为按下确定按钮时候的选中项index,
//这样当弹窗再次弹出时显示选中的是上一次确定的选项
this.select = value.index;
console.info("TextPickerDialog:onAccept()"
+ JSON.stringify(value));
},
onCancel: () => {
// 点击弹窗中的“取消”按钮时触发该回调。
console.info("TextPickerDialog
:onCancel()");
},
onChange: (value: TextPickerResult) => {
// 滑动弹窗中的选择器使当前选中项改变时触发该回调。
console.info('TextPickerDialog:onChange
()' + JSON.stringify(value));
}
})
})
}
.width('100%')
}
}
效果:
选择类弹窗—日期选择弹窗
当我们需要输入个人出生日期的时候,就可以使用DatePickerDialog。
例程代码:
@Entry
@Component
struct DatePickerDialogDemo {
selectedDate: Date = new Date('2010-1-1');
build() {
Column() {
Button("DatePickerDialog")
.margin(20)
.onClick(() => {
DatePickerDialog.show({ //显示日期弹窗
start: new Date('1900-1-1'),
// 设置选择器的起始日期
end: new Date('2023-12-31'),
// 设置选择器的结束日期
//(当前时间 new Date())
selected: this.selectedDate,
// 设置当前选中的日期
lunar: false, //日期是否是农历
onAccept: (value: DatePickerResult) => {
// 点击弹窗中的“确定”按钮时触发该回调
// 通过Date的setFullYear方法设置按下确定按钮时
//的日期,这样当弹窗再次弹出时显示选中的是上一次
//确定的日期
this.selectedDate
.setFullYear(value.year, value.month, value.day)
console.info('999')
},
onCancel: () => {
// 点击弹窗中的“取消”按钮时触发该回调
console.info('555')
},
onChange: (value: DatePickerResult) => {
// 滑动弹窗中的滑动选择器使当前选中项改变时触发该回调
console.info('333')
}
})
})
}
.width('100%')
}
}
效果:
选择类弹窗—自定义弹窗
自定义弹窗的界面可以通过装饰器@CustomDialog定义的组件来实现,然后结合CustomDialogController来控制自定义弹窗的显示和隐藏。
通过CustomDialogController类显示自定义弹窗。使用弹窗组件时,可优先考虑自定义弹窗,便于自定义弹窗的样式与内容。
自定义弹窗声明:
CustomDialogController(value:{builder: CustomDialog, cancel?: () => void, autoCancel?: boolean, alignment?: DialogAlignment, offset?: Offset, customStyle?: boolean, gridCount?: number})
参数名 | 参数类型 | 必填 | 参数描述 |
---|---|---|---|
builder | CustomDialog | 是 | 自定义弹窗内容构造器。 |
cancel | () => void | 否 | 点击遮障层退出时的回调。 |
autoCancel | boolean | 否 | 是否允许点击遮障层退出。默认值:true |
alignment | DialogAlignment | 否 | 弹窗在竖直方向上的对齐方式。默认值:DialogAlignment.Default |
offset | Offset | 否 | 弹窗相对alignment所在位置的偏移量。 |
customStyle | boolean | 否 | 弹窗容器样式是否自定义。默认值:false,弹窗容器的宽度根据栅格系统自适应,不跟随子节点;高度自适应子节点,最大为窗口高度的90%;圆角为24vp。 |
gridCount8+ | number | 否 | 弹窗宽度占栅格宽度的个数。默认为按照窗口大小自适应,异常值按默认值处理,最大栅格数为系统最大栅格数。 |
代码例程:
@CustomDialog //该装饰器表示下面组件是一个弹窗
struct CustomDialogExample {
@Link textValue: string
@Link inputValue: string
controller: CustomDialogController
//用于自动接收对象自己的Controller,
//便于后面对本弹窗的控制
// 若尝试在CustomDialog中传入多个其他的Controller,
//以实现在CustomDialog中打开另一个或另一些CustomDialog,
//那么此处需要将指向自己的controller放在最后。
cancel: () => void //用于接收函数
confirm: () => void //用于接收函数
build() { //用于渲染弹窗内容
Column() {
Text('Change text')
.fontSize(20)
.margin({ top: 10, bottom: 10 })
TextInput({ placeholder: '',
text: this.textValue})
.height(60)
.width('90%')
.onChange((value: string) => { //获取输入框内容
this.textValue = value
})
Text('Whether to change a text?')
.fontSize(16)
.margin({ bottom: 10 })
Flex({ justifyContent: FlexAlign.SpaceAround }) {
//表示该组件内部的子组件主轴对齐(横向)
Button('cancel') //取消按钮
.onClick(() => {
this.controller.close() //关闭本弹窗
this.cancel() //执行响应函数
})
.backgroundColor(0xffffff)
.fontColor(Color.Black)
Button('confirm') //确定按钮
.onClick(() => {
this.inputValue = this.textValue
this.controller.close() //关闭本弹窗
this.confirm() //执行响应函数
})
.backgroundColor(0xffffff)
.fontColor(Color.Red)
}
.margin({ bottom: 10 })
}
// dialog默认的borderRadius为24vp,
//如果需要使用border属性,
//请和borderRadius属性一起使用。
}
}
@Entry
@Component
struct CustomDialogUser {
@State textValue: string = ''
@State inputValue: string = 'click me'
//构建弹窗控制器
dialogController: CustomDialogController =
new CustomDialogController({
builder: CustomDialogExample({
//自定义的弹窗构造器,用于生成弹窗的内容。
cancel: this.onCancel,
confirm: this.onAccept,
textValue: $textValue,
inputValue: $inputValue
}),
cancel: this.existApp,
//点击弹窗外区域后退出的回调函数
autoCancel: true,
//是否允许点击弹窗外区域后退出
alignment: DialogAlignment.Bottom,
//弹窗的位置
offset: { dx: 0, dy: -20 },
//弹窗位置的偏移量
gridCount: 4,
customStyle: false //弹窗样式是否自定义
})
// 在自定义组件即将析构销毁时将dialogController置空
aboutToDisappear() {
this.dialogController = undefined
// 将dialogController置空
}
onCancel() {
console.info('222')
}
onAccept() {
console.info('111')
}
existApp() {
console.info('444')
}
build() {
Column() {
Button(this.inputValue)
.onClick(() => {
if (this.dialogController != undefined) {
this.dialogController.open()
}
})
.backgroundColor(0x317aff)
}.width('100%').margin({ top: 5 })
}
}
动画
属性动画
属性动画通过设置组件的animation属性来给组件添加动画,当组件的width,height,Opacity(透明度),backgroundColor,scale(缩放比例),rotate,translate(平移的距离) 等属性变更时,可以实现渐变过渡效果(动画效果)。
animation参数:
例程代码:
Text(">_<")
.position({ //组件位置(页面左上角为原点)
x:10, //x轴坐标
y:0 //y轴坐标
})
.rotate({
angle:0, //旋转角度
centerX:'50%', //旋转中心横坐标
centerY:'50%' //旋转中心纵坐标
})
.animation({
duration:1000,
curve:Curve.EaseInOut
})
当Text组件的属性发生变化(因此要单独添加响应事件来控制属性变化)时,就会自动产生渐变的效果(即动画效果),.animation()属性必须定义在所有要发生变化的属性的后面才能起作用。
显式动画
显式动画通过全局animateTo函数来修改组件属性,实现属性变化时的渐变效果(动画效果)。
例程代码:
animateTo({参数1},()=>{
//该函数用于修改与属性相关联的状态变量,
//即控制组件的属性变化
}) //参数1即动画参数,与属性动画参数相同
组件转场动画
组件内转场主要通过transition属性配置转场参数,在组件插入和删除时显示过渡动效果。
属性的参数:
if(this.isShow) //boolean状态变量,
//用于控制组件的出现与消失(插入与删除)
{
Text(">_<")
.transition({
opacity:1,
rotate:{angle:-360},
scale:{x:0,y:0} //放缩比例,表示插入的起点,
//删除的终点,0表示缩到最小
})
}
组件转场动画必须配合显示动画(用来控制状态变量)来使用。