目录
简介
1.组件的一些通用属性和事件
1.1 通用事件
1.1.1 点击事件
1.1.2 触摸事件
1.1.3 按键事件
1.1.4 改变事件
1.2 通用属性
1.2.1 尺寸设置
1.2.2 位置设置
2.基本组件
2.1 Column/Row/Flex容器组件
2.1.1 列Column
2.1.2行Row
2.1.3 Flex容器
2.2 Image(图片)组件
2.3 Text(文本)组件
2.4 TextInput(输入框)组件
2.5 Button(按钮)组件
2.6 Slider(滑动条)组件
2.7 Blank(空白填充)组件
2.8 Checkbox(复选框)+CheckboxGroup组件
2.9 DatePicker日期选择器+TimePicker时间选择器
2.11 Tabs+TabContent组件
2.11.1 Tabs组件
2.11.2 TabContent组件
2.11.3 测试代码+实现效果
2.12 Grid(网格)组件
2.13 List(列表)组件
2.14 Progress(进度条)组件(补充)
3.一些重要的属性补充
3.1 layoutWeight属性
3.2 基础语法+装饰器+模块化+组件+属性+事件实现一个待办事项的小demo(补充)
3.3 小demo(升级版:基础知识可查看2-5中的补充部分)(补充)
3.3.1 升级问题解答
3.3.2 升级知识点补充
3.3.3 测试代码
本系列是windows系统下、采用ArkTS语言、ArkUI框架、deveco studio编译器学习纯鸿蒙软件研发,采用API version 9进行。本小节主要了解鸿蒙开发的部分常用组件、属性、事件等。并基于该章结合之前的博客知识点,补充了函数作为参数的传递方式,带有this对象的函数作为参数的传递方式,实现了一个待办事项的Demo及升级。纯小白,一步步学习,记录一下过程便于查询。
本文主要介绍的是部分常用组件、属性、事件等,并不是全部,其余可以查看官网指南。
参考:文档中心下的ArkTS API参考-概述-手机、平板、智慧屏和智能穿戴开发-组件参考(基于ArkTS的声明式开发范式)。
组件是构建页面的核心,通过2-6和2-7中的模块化方式,我们可以将组件封装,重复使用。
(1)组件被点击时触发的事件会触发一个回调函数,执行我们编写的业务代码。语法结构如下:
.onClick(()=>{//简写模式
//业务代码
})
//带参数的点击事件
.onClick((event:ClickEvent)=>{
//业务代码:event带有点击的信息:例如点击的屏幕位置
})
(2)测试代码
Button("Click",{type:ButtonType.Normal})
.width(100).height(50).margin(20)
.onClick((event:ClickEvent)=> {
//每次点击都能获取到一些信息,这些信息都会存储在event中
//我们可以通过event来获取这些信息用于我们的业务代码实现
//event中存储的位置信息:均从左往右为x轴,从上往下为y轴
//screenX和screenY:相对于屏幕左上角为原点的x,y坐标
//x和y:相对于点击的组件的左上角为原点的x,y坐标
this.text='Click point:'+'\n screenX:'+event.screenX+
'\n screenY:'+event.screenY+'\n x:'+ event.x+'\n y:'+event.y
+'\n width:'+event.target.area.width+'\n height:'+event.target.area.height
+'\n timestamp:'+event.timestamp
})
Text(this.text).width('100%').height('100%').fontSize(20).fontColor('#0FFF0F')
(1)触发机制:手指再组件上按下、滑动、抬起,触发的事件为触发一个回调函数,执行我们编写的业务代码。语法结构如下:
.onTouch(()=>{//简写模式
..业务代码
})
.onTouch((event:TouchEvent)=>{
..业务代码:可以使用event中的事件信息
if(event.type===TouchType.Down){//按下
//按下的业务代码
}
else if(event.type===TouchType.Up){//抬起
//抬起的业务代码
}
else if(event.type===TouchType.Move){//滑动
//滑动的业务代码
}
else if(event.type===TouchType.Cancle){//取消触摸事件
//取消触摸事件的业务代码
}
//事件的类型不同event中存储的信息也有所不同
})
(2)测试代码
.onTouch((event:TouchEvent)=>{
//分为三种类型,按下,抬起,滑动,事件取消
//类型变化event中的信息会有变化
if(event.type===TouchType.Down){//按下
this.touchtype='按下了:'
}
else if(event.type===TouchType.Up){//抬起
this.touchtype='抬起了:'
}
else if(event.type===TouchType.Move){//滑动
this.touchtype='滑动:'
}
this.text=this.touchtype+'\n screenX:'+event.touches[0].screenX+
'\n screenY:'+event.touches[0].screenY
+'\n x:'+ event.touches[0].x+'\n y:'+event.touches[0].y
+'\n width:'+event.target.area.width+'\n height:'+event.target.area.height
+'\n timestamp:'+event.timestamp
})
(1)与键盘、遥控器等按键设备交互触发的事件。仅适用于可以获取焦点的事件(之前有接触:Button、TextInput等可以获取焦点,Text等不可以),语法结构如下:
.onKeyEvent(()=>{//简写模式
..业务代码
})
//按键事件
.onKeyEvent((event:KeyEvent)=>{
//分为两种类型:按下、松开,表明案件的按下、松开
//按建码keyCode:按了哪个键,用一个唯一的id绑定上每一个按键
//按键文字keyText:按键本身的文字
if(event.type===KeyType.Up){//抬起、
}
else if(event.type===KeyType.Down){//按下
}
})
(2)测试代码:
//按键事件
Button('KEY').onKeyEvent((event:KeyEvent)=>{
//分为两种类型:按下、松开,表明案件的按下、松开
//按建码keyCode:按了哪个键,用一个唯一的id绑定上每一个按键
//按键文字keyText:按键本身的文字
console.log("进来了~~~~~")
if(event.type===KeyType.Up){//抬起、
this.keytype='抬起:'
}1
else if(event.type===KeyType.Down){//按下
this.keytype='按下:'
}
this.text=this.keytype+'\n'+event.keyCode+event.keyText
+'\n timestamp:'+event.timestamp
})
(1)语法结构:实际是一个监听事件,常用于监听文本框输入的改变等,当文本输入发生变化是,出发回调。语法结构如下:
onChange(callback: (value: string) => void)
(2)测试代码(以TextInput输入框为例):
TextInput({placeholder:"请输入账号"}).type(InputType.Email).width(200).height(40).backgroundColor('#FFF')
.onChange(()=>{
//箭头函数不使用参数时,可以省略所有参数
console.log('输入框1文本改变了');
})
TextInput({placeholder:"请输入账号"}).type(InputType.Number).width(200).height(40).backgroundColor('#FFF')
.onChange(value=>{
//箭头函数只具备一个参数时,可以省略括号,但是当没有参数或者有多个参数时不可以
console.log('输入框2文本改变了,改变后值为:'+value);
})
TextInput({placeholder:"请输入账号"}).type(InputType.PhoneNumber).width(200).height(40).backgroundColor('#FFF')
.onChange((value:string)=>{
console.log('输入框3文本改变了,改变后值为:'+value);
})
(3)实现效果:
尺寸设置包括组件的宽、高、边距等等。(margin外边距+border边框+padding内边距+组件宽width高height)。
(1)测试代码:
@Entry
@Component
struct CommonAttributes{
build() {
Column() {
//尺寸设置:margin外边距+border边框+padding内边距+组件宽width高height
Text('尺寸设置').fontColor('#044444').fontSize(20).width('90%')
Row(){
Text('ArkTS语言').fontColor(Color.White)
}.width('100%').backgroundColor('#444444')
Row(){
Text('ArkTS').fontColor(Color.White).backgroundColor('#444444').height(260)
Row(){
Text("HarmonyOS4.0HarmonyOS4.0HarmonyOS4.0HarmonyOS4.0HarmonyOS4.0HarmonyOS4.0")
.fontColor(Color.White).fontSize(20)
}.width(200).height(200).backgroundColor('#044444')
//内边距
.padding(20)
//边框
.border({width:6,color:'#00FF00'})
//外边距
.margin({top:20,right:20,left:20,bottom:20})
Text('ArkTS').fontColor(Color.White).backgroundColor('#444444').height(260)
}
Row(){
Text('ArkTS语言').fontColor(Color.White)
}.width('100%').backgroundColor('#444444')
}
}
}
(2)实现效果:
位置设置主要包括组件的对齐方式、布局方向和显示位置等。适用于界面多个组件的布局。
(1)测试代码:
//位置设置:组件的对齐方式、布局方向和显示位置
//algin:元素内容在元素绘制区域内的对齐方式:例如Text内的文字再Text中是居中还是左对齐还是右对齐
Column(){
Text('位置设置-ALIGN').fontColor('#044444').fontSize(20).width('90%')
//默认的组件stack:里面放置的组件会默认给落到一起
Stack(){
Text('1').fontSize(20).fontColor(Color.White).height(50).width(40).backgroundColor('#444404')
Text('2').fontSize(20).fontColor(Color.White).width(80).height(30).backgroundColor('#222222')
}.width('90%').height(50).backgroundColor('#044444')
//对齐方式
.align(Alignment.Top)
}
//direction:设置元素水平方向的布局
Column(){
Text('位置设置-DIRECTION').fontColor('#044444').fontSize(20).width('90%')
Row(){
Text('1').fontSize(20).fontColor(Color.White).width('25%').backgroundColor('#444404')
Text('2').fontSize(20).fontColor(Color.White).width('25%').backgroundColor('#222222')
Text('3').fontSize(20).fontColor(Color.White).width('25%').backgroundColor('#777777')
Text('4').fontSize(20).fontColor(Color.White).width('25%').backgroundColor('#555555')
}.width('90%').height(50).backgroundColor('#044444')
//默认为Auto,ltr
.direction(Direction.Rtl)
}
//position:设置是组件相对于父组件的位置,可以直接写数字,也可以写百分比
Column(){
Text('位置设置-POSITION').fontColor('#044444').fontSize(20).width('90%')
Row(){
Text('1').fontSize(20).fontColor(Color.White).width('25%').backgroundColor('#444404').position({x:0,y:0})
Text('2').fontSize(20).fontColor(Color.White).width('25%').backgroundColor('#222222').position({x:20,y:40})
Text('3').fontSize(20).fontColor(Color.White).width('25%').backgroundColor('#777777').position({x:'60%',y:'10%'})
Text('4').fontSize(20).fontColor(Color.White).width('25%').backgroundColor('#555555').position({x:10,y:30})
}.width('90%').height(70).backgroundColor('#044444')
}
//markAnchor:相对于组件原本的位置,xy往左往上为正值,xy可以为数值,同样可以为百分比
Column(){
Text('位置设置-markAnchor').fontColor('#044444').fontSize(20).width('90%')
Stack({alignContent:Alignment.TopStart}){
Row(){}.width(50).height(50).backgroundColor('#044444')
Text('1').fontSize(20).fontColor(Color.White).height(25).width(25).backgroundColor('#444404').markAnchor({x:25,y:0})
Text('2').fontSize(20).fontColor(Color.White).width(25).height(25).backgroundColor('#222222').markAnchor({x:'-200%',y:'-100%'})
Text('3').fontSize(20).fontColor(Color.White).height(25).width(25).backgroundColor('#555555').markAnchor({x:25,y:-30})
}
}
//offset:相对于组件原本的位置,和markAchor相反,xy往右往下为正值,xy可以为数值,同样可以为百分比
Column(){
Text('位置设置-offest').fontColor('#044444').fontSize(20).width('90%')
Row(){
Text('1').fontSize(20).fontColor(Color.White).width('25%').backgroundColor('#444404').offset({x:30,y:20})
Text('2').fontSize(20).fontColor(Color.White).width('25%').backgroundColor('#222222').offset({x:-30,y:30})
Text('3').fontSize(20).fontColor(Color.White).width('25%').backgroundColor('#777777').offset({x:-10,y:-20})
Text('4').fontSize(20).fontColor(Color.White).width('25%').backgroundColor('#555555').offset({x:-50,y:40})
}.width('90%').height(100).backgroundColor('#044444')
}
(2)实现效果:
align:元素内容在元素绘制区域内的对齐方式
direction:设置元素水平方向上的布局
position:设置组件相对于父组件的位置
markAnchor:相对于组件原本的位置,xy往左往上为正值,可为数值和百分比。
offset:相对于组件原本的位置,和markAnchor相反,xy往右往下为正值。
整个效果显示:
★ 注意:markAnchor和offset的百分比是相对于组件本身的宽高的百分比,例如:组件宽200,设置20%就是40,而不是相对于父组件或者相对于屏幕等。
相对于基础组件不考虑布局只是自身样式,容器组件主要用于页面的布局、对齐等,其内部可以包含多种多个基础组件。
(1)相关知识点:
内部组件从上到下垂直排列。
列的布局:列的布局分为水平方向和垂直方向。
水平方向属性为alignItems,属性值为枚举类HorizontalAlign,默认为Center,具有三个值:
垂直方向属性为justifyContent,属性值为枚举类FlexAlign,默认为Start,具有六个值:
(2)测试代码:
Column() {
//容器组件Column/Row/Flex;容器组件
//1.列Column:默认左右居中对齐,布局从上大小依次布局
Column() {
Column().width('50%').height(30).backgroundColor('#044444')
Column().width('60%').height(30).backgroundColor('#00FF00')
Column().width('60%').height(30).backgroundColor('#0000AA')
}.width('90%')
.height(300)
.border({width:2,color:'#000000'})
.margin(20)
//可对子组件在水平方向上的对齐方式进行设计:默认值:HorizontalAlign.Center
// .alignItems(HorizontalAlign.Start)//居左
// .alignItems(HorizontalAlign.Center)//居中
// .alignItems(HorizontalAlign.End)//居右
//可对子组件在垂直方向上的对齐方式进行设计:默认值:FlexAlign.Start顶部
// .justifyContent(FlexAlign.End)//底部
// .justifyContent(FlexAlign.Start)//顶部
.justifyContent(FlexAlign.Center)//居中
// .justifyContent(FlexAlign.SpaceBetween)//充满父组件等距分开
//子组件等距分开,第一个子组件和最后一个子组件与父组件之间距离是等距子组件距离的一半
// .justifyContent(FlexAlign.SpaceAround)
//子组件,子组件距离父组件完全等距分开
// .justifyContent(FlexAlign.SpaceEvenly)
}
(3)实现效果:
(1)相关知识点:
内部组件从左到右水平排列。
行的布局:行的布局同样分为水平方向和垂直方向。
水平方向属性为justifyContent,属性值为枚举类FlexAlign,默认为Start,具有六个值:
垂直方向属性为alignItems,属性值为枚举类VerticalAlign,默认为Center,具有三个值:
(2)测试代码:
//2.行Row:默认水平方向居左对齐,垂直方向居中对齐
Row() {
Row().width('20%').height(70).backgroundColor('#044444')
Row().width('20%').height(60).backgroundColor('#00FF00')
Row().width('20%').height(80).backgroundColor('#0000AA')
}.width('90%')
.height(100)
.border({width:2,color:'#000000'})
.margin(20)
//可对子组件在水平方向上的对齐方式进行设计:默认值:FlexAlign.Start居左
// .justifyContent(FlexAlign.End)//居右
// .justifyContent(FlexAlign.Start)//居左
// .justifyContent(FlexAlign.Center)//居中
// .justifyContent(FlexAlign.SpaceBetween)//充满父组件等距分开
//子组件等距分开,第一个子组件和最后一个子组件与父组件之间距离是等距子组件距离的一半
// .justifyContent(FlexAlign.SpaceAround)
//子组件,子组件距离父组件完全等距分开
.justifyContent(FlexAlign.SpaceEvenly)
//可对子组件在垂直上的对齐方式进行设计:默认值:VerticalAlign.Center
// .alignItems(VerticalAlign.Top)//顶部
// .alignItems(VerticalAlign.Bottom)//底部
.alignItems(VerticalAlign.Center)//居中
(3)实现效果:
(1)相关知识点:
Flex是以弹性方式布局子组件的容器组件。API Version 9以后开始支持。
Flex和Column、Row的区别之一在于:Flex是通过参数设置布局而非属性。具有如下参数:
(2)测试代码:
//3.Flex:以弹性方式布局子组件的容器组件
Flex({
//Flex的主轴方向
direction:FlexDirection.Row,
//子组件在主轴方向的对齐格式
justifyContent:FlexAlign.Start,
//子组件在交叉轴方向的对齐格式
alignItems:ItemAlign.Auto,
//单行/列还是多行/列排列
wrap:FlexWrap.Wrap,
//交叉轴中有额外空间时,多行内容的对齐方式,该设置仅在wrap的值为warp或者wrapReverse时生效
alignContent:FlexAlign.Center
})
{
Text("1").width('5%').height(50).backgroundColor('#044444').fontColor(Color.White)
Text("2").width('35%').height(70).backgroundColor('#555555').fontColor(Color.White)
Text("3").width('20%').height(60).backgroundColor('#FF0000').fontColor(Color.White)
Text("4").width('5%').height(80).backgroundColor('#00FF00').fontColor(Color.White)
Text("5").width('20%').height(50).backgroundColor('#0000FF').fontColor(Color.White)
Text("6").width('5%').height(50).backgroundColor('#000000').fontColor(Color.White)
Text("7").width('30%').height(50).backgroundColor('#ffff00').fontColor(Color.White)
}.height(160).width('90%').backgroundColor('#777777')
(3)实现效果:
alignContent:
alignItems:
direction:
wrap:
注:Flex组件在渲染时存在二次布局过程,所以对性能有严格要求的场景建议使用Column、Row。
(1)语法结构:Image主要是用来显示图片的,语法结构如下:
Image(src:string|PixelMap|Resource)
其中src参数没有?标记,说明该参数为必传参数,不可省略。
(2)三种参数类型分别对应不同的使用场景:
(1)string格式:通常用于加载网络图片,传递图片的网络地址。
形如:Image('https://…….jpg')
注意:该形式需要申请网络访问权限。
(2)PixelMap格式:通常用于加载像素图,适用于图片编辑。
形如:Image(pixelMapObject)
注意:使用需要构建pixelMap对象,相对比较复杂,(没有这需求,我也不会!哈哈哈哈!)
(3)Resource格式:最常用的方式,常用于加载本地图片。
形如:Image($r{'app.media.图片名'}):一般用于加载放置在media中的图片
注意:目录结构app.media是固定的,并且不需要文件格式后缀。
Image($rawfile('图片名.文件后缀')):一般用于加载放置在rawfile文件夹中的图片
注意:该形式需要图片格式后缀。
(3)属性:主要有图片的宽width,高height、border边框,borderRadius边框圆角等通用属性,objectFit图片,interpolation图片插值等特有属性。
(4)测试代码:
Image($r('app.name.food')).width('100%').height(152).backgroundColor('#f1f3f5')
.objectFit(ImageFit.Contain)
(5)实现效果:
(1)语法结构:Text主要作用为显示文本,语法结构如下:
Text(content?:string|Resource)
content即为显示的文本,为可选参数,参数类型为string和Resource的联合类型。
(2)两种参数的使用方式:
(1)string格式:直接填写文本内容
Text('HarmonyOS 4.0')
(2)Resource格式:读取本地资源文件
//①首先在本地资源文件中声明该资源:entry-src-main-resources-base-elements-string.json,如果项目中存在其他的资源模块例如en_US,zh_CN,还需要在这些模块下的string.json文件中进行同名的资源声明。声明方式语法结构如下:
{
"string":[
{
"name":字符串1名称,
"value":字符1串值
},
{
"name":字符串2名称,
"value":字符2串值
}
]
}
//②使用资源,语法结构如下:
Text(Sr('app。string.字符串名称'))
★★★Tip1:注意这里的string.json文件所在的resources目录分为base目录,限定词目录(如en_US,zh_CN),原始文件目录(rawfile)等,$r读取本地资源时,会优先根据当前设备信息读取与之匹配的限定词目录,从中读取所需的资源,如果限定词目录中没有对应的资源,才会读取base目录,该部分在下面链接的5.1中进行了介绍。鸿蒙软件开发2-1 部分基础知识了解_鸿蒙app开发,关于rawfile文件路径的2种方式-CSDN博客
(3)常用属性:fontSize字体大小、fontColor字体颜色、lineHeight行高、fontWeight字体粗细、textAlign文字对齐方式等等。
(4)测试代码:
Text('自定义').fontSize(20)
.fontColor('#000000')
.width('100%').height('100%').border({width:2})
.backgroundColor('#FFFFFF')
.textAlign(TextAlign.Center)
(4)实现效果:
(1)语法结构:TextInput主要用于文本输入,语法结构如下:
TextInput({placeholder?:ResourceStr,text?:ResourceStr})
(2)参数解析:相对于一些组件来说,TextInput的参数是一个键值对组合的对象。参数均为可选,均不写可以直接省略中括号。其中:
(3)常见属性:width宽度,height高度,backgroundColor背景颜色,type输入框类型(值为·InputType枚举类,其中包括如password类型就可以用于密码显示为密文的输入)。
(4)测试代码:
TextInput({placeholder:"请输入账号"}).type(InputType.Normal).width(200).height(40).backgroundColor('#FFF')
//1.Normal类型:基本输入模式,支持输入数字、字母、下划线、空格、特殊字符。
TextInput({text:"admin"}).type(InputType.Normal).width(200).height(40).backgroundColor('#FFF')
//2.Password类型:密码输入模式,支持输入数字、字母、下划线、空格、特殊字符。 会显示为密文。
TextInput({placeholder:"请输入账号"}).type(InputType.Password).width(200).height(40).backgroundColor('#FFF')
//3.Email类型:邮箱地址输入模式,支持输入数字、字母、下划线以及@字符。
TextInput({placeholder:"请输入账号"}).type(InputType.Email).width(200).height(40).backgroundColor('#FFF')
//4.Number类型:纯数字输入模式
TextInput({placeholder:"请输入账号"}).type(InputType.Number).width(200).height(40).backgroundColor('#FFF')
//5.PhoneNumber类型:电话号输入模式,支持输入数字、+、-等
TextInput({placeholder:"请输入账号"}).type(InputType.PhoneNumber).width(200).height(40).backgroundColor('#FFF')
(5)实现效果:
(6)重要的事件:onchange:文本输入框文字发生变化的监听。
(1)语法结构
方式一:Button(options?{type?:ButtonType,stateEffect?:boolean})
方式二:Button(label?:ResourceStr,options?:{type?:ButtonType,stateEffect?:boolean})
其中type是指按钮的类型(Normal直角/Capsule圆角/Circle圆),stateEffect是指点击时是否需要变化效果反馈,例如点击时颜色变化。均为可选参数。
(2)测试代码
//Button组件-创建方式1
Button({type:ButtonType.Normal,stateEffect:false}){
Text("HarmonyOS 4.0").fontSize(20).fontColor(Color.White)
}.backgroundColor("#044444")
//Button组件-创建方式2
Button('HarmonyOS 4.0',{type:ButtonType.Normal,stateEffect:false}).backgroundColor("#044444").margin({top:20})
//错误写法:当Button具有label属性时,不允许有子组件
Button('HarmonyOS 4.0',{type:ButtonType.Normal,stateEffect:false}){
Text("HarmonyOS 4.0").fontSize(20).fontColor(Color.White)
}.backgroundColor("#044444").margin({top:20})
(3)实现效果
★★★Tip2:当Button具有label属性时,不允许有子组件,否则会报错
The Button component with a label parameter can not have any child.
(1)语法结构:通常用于快速调节设置值,例如音量、亮度调节等等。语法结构如下:
Slider(options?:{value?: number, min?: number, max?: number, step?: number, style?: SliderStyle, direction?: Axis, reverse?: boolean})
(2)参数解析:参数采用键值对对象的方式配置,包含以下键值:
(3)常用属性:blockColor滑块颜色、trackColor滑块背景颜色、selectedColor滑轨已滑动颜色、showSteps是否显示步长刻度值、showTips是否显示气泡提示百分比、trackThickness滑轨粗细、maxLabel最大标签、minLabel最小标签。
(4)常用事件:onChange滑动时触发事件回调,语法结构如下:
onChange(callback: (value: number, mode: SliderChangeMode) => void)
--value:当前进度值
--mode:拖动状态,值为SliderChangeMode枚举类,包括:
--Begin:用户开始拖动滑块
--Moving:用户拖动滑块中
--End:用户结束拖动滑块
--Click:用户点击滑动条使滑块位置移动。
(5)测试代码:
Slider({value: 30,min: 0,max: 100,style: SliderStyle.OutSet})
.showTips(true)
.onChange((value: number, mode: SliderChangeMode) => {
console.info('value:' + value + 'mode:' + mode.toString());
})
(6)实现效果:
(1)语法结构:在容器主轴上,空白填充组件具有自动填充容器空余部分的能力。注意仅当父组件为Row/Column时生效。从API 7以后开始支持,语法结构如下:
Blank(min?: number | string)
(2)参数解析:min是指空白填充组件在容器主轴上的最小大小,默认为0。为可选参数。
(3)常用属性:color空白填充的填充颜色,默认值为0xffffff白色。
(1)语法结构
Checkbox(options?:{name?:string,group?:string})
(2)测试代码:
//变量申明
@State hmstate:boolean=false
@State arktsstate:boolean=false
//Checkbox+CheckboxGroup
Text("CheckBox").fontColor('#044444').fontSize(20)
Text("{hmstate:"+this.hmstate+",arktsstate:"+this.arktsstate+"}").fontColor('#044444').fontSize(20)
Row(){
Checkbox({name:'HarmonyOS4.0'}).onChange((value)=>{//注意触发的事件是onchange
console.log(value+"");
this.hmstate=value
})
Text('HarmonyOS4.0').fontColor('#044444').fontSize(20)
Checkbox({name:'ArkTS语言'}).margin({left:50}).onChange((value)=>{
console.log(value+"");
this.arktsstate=value
})
Text('ArkTS语言').fontColor('#044444').fontSize(20)
}.margin({top:10})
if(this.hmstate){
Row(){
CheckboxGroup({group:"boxs"})
Text('华为').fontSize(20)
}
Row(){
Checkbox({group:"boxs"}).margin({left:50,top:10})
Text('国产化').fontSize(20)
}
Row(){
Checkbox({group:"boxs"}).margin({left:30,top:10})
Text('系统').fontSize(20)
}
Row(){
Checkbox({group:"boxs"}).margin({left:30,top:10})
Text('开源').fontSize(20)
}
}
(3)实现效果:
(1)语法结构
DatePicker(options?:(start?:Date,end?:Date,selected:Date))
其中start是可选择的起始日期,end是可选择的终止日期,selected是默认选中的日期。参数均可选。默认start是1970-1-1,end是2100-12-31,selected默认是系统当前日期。
(2)测试代码
//变量申明
@State isLunar:boolean=false//标记是阴历还是阳历
private selected:Date=new Date('2023-12-14')
@State isuseMilitaryTime:boolean=true//标记是否为24小时制
private selectedTime:Date=new Date('2023-12-14T15:34:00')
//DatePicker日期选择器
Text("DatePicker").fontColor('#044444').fontSize(20)
//默认start是1970-1-1,end是2100-12-31,selected默认是系统当前日期
//DatePicker属性lunar,用于标记是阴历还是阳历
DatePicker({start:new Date('1970-1-1'),end: new Date('2100-1-1'),selected:this.selected}).lunar(this.isLunar).margin({top:20}).onChange((value)=>{
console.log(JSON.stringify(value))
//注意:Date中得月份是0-11,而不是1-12
this.selected.setFullYear(value.year,value.month,value.day)
})
//切换阴历阳历
Button('切换阴历阳历').onClick(() => {
this.isLunar=!this.isLunar
}).margin({top:20})
//TimePicker:时间选择器
Text("TimePicker").fontColor('#044444').fontSize(20)
//TimePicker属性useMilitaryTime,用于标记是否采用24小时制
TimePicker({selected:this.selectedTime}).useMilitaryTime(this.isuseMilitaryTime).margin({top:20}).onChange((value)=>{
console.log(JSON.stringify(value))
this.selectedTime.setHours(value.hour,value.minute)
})
//切换24小时制
Button('切换时间格式').onClick(() => {
this.isuseMilitaryTime=!this.isuseMilitaryTime
}).margin({top:20})
(3)实现效果
★bug1:DatePicker设置selected无效:可能原因是需要设置start和end,我的设置不生效,添加上start和end后就生效了。
★bug2:TimePicker设置selected无效:可能原因是Date的字符串写错了,需要写为如下格式(注意日期和时间之间用T隔开)。
private selectedTime:Date=new Date('2023-12-14T15:34:00')
导航条和路由跳转不通,导航条是在同一个界面切换不同的显示内容,并不更换当前界面。
(1)语法结构
Navigation()
.title(value: string | CustomBuilder | NavigationCommonTitle | NavigationCustomTitle)
.hideTotleBar(value:boolean)
.toolBar(value: object | CustomBuilder)
..........
Navigtion具备参数:标题,副标题(当前API版本已弃用),菜单、标题模式、工具栏内容、隐藏标题栏,隐藏工具栏等。
Navigation具备两个回调函数:标题模式改变的回调,导航条显示状态切换的回调。
(2)测试代码:
@Entry
@Component
struct CommonNavigation{
@State currentBarIndex:number=0
//申明导航条的数据:注意Array的'>'和’=‘间需要加上空格,否则会一起被读为大于等于。
@State bars:Array<{text:string,subtitle:string,num:number,img:Resource,content:Resource}> =[
{
text:'Harmony0S4.0',
subtitle:'Harmony0S4.0',
num:0,
img:$r('app.media.icon'),
content:$r('app.media.app_icon')
},
{
text:'ArkTS语言',
subtitle:'ArkTS语言',
num:1,
img:$r('app.media.app_icon'),
content:$r('app.media.icon')
},
{
text:'ArkTS语言',
subtitle:'ArkTS语言',
num:2,
img:$r('app.media.app_icon'),
content:$r('app.media.icon')
}
]
build() {
Row() {
Column() {
//Navigation:导航条,和路由跳转不同,导航条是在同一个界面中显示不同的内容,并不更换界面。
//具备参数:标题、副标题、菜单、标题模式、工具栏内容、隐藏工具栏、隐藏标题栏
//工具栏内容:object:value显示的名称、icon显示的图标资源、action当前选中事件的回调,点击跳转的时候可以确定点击到了哪个页面。。。
//两个回调事件,(1)标题模式改变的回调,(2)导航栏显示状态切换的回调
//简单示例
// Navigation()
// .title('HarmonyOS4.0')//标题
// //.subTitle('ArkTs语言')//副标题:生效但已被弃用
// .hideTitleBar(false)//隐藏标题:false为不隐藏,true为隐藏
// .toolBar({ items:[{
// value:'HarmonyOS4.0',
// icon:$r('app.media.icon')
// },{
// value:'HarmonyOS4.0',
// icon:$r('app.media.app_icon')
// }
// ]})
//优化后的示例:采用自定义组件+数据实现导航布局和切换
Navigation(){
Image(this.bars[this.currentBarIndex].content).width('90%')
}
.title(this.customNavigationTitle())//标题
.hideTitleBar(false)//隐藏标题:false为不隐藏,true为隐藏
.toolBar(this.customNavigationToolBar())
}.width('100%')
.height('100%')
.backgroundColor('#044444')
}
}
//自定义组件
@Builder customNavigationTitle(){
Column(){
Text(this.bars[this.currentBarIndex].text).fontSize(20).fontColor('#ffffff')
//起到类似于副标题的效果
Text(this.bars[this.currentBarIndex].subtitle).fontSize(16).fontColor('#ffffff')
}
}
@Builder customNavigationToolBar(){
Row(){
ForEach(this.bars,item=>{
Column({space:10}){
Image(item.img).width(50).height(50)
.border(this.currentBarIndex===item.num?{width:2,color:'#000000'}:{width:0})
Text(item.text).fontSize(10).fontColor(this.currentBarIndex===item.num?'#FF0000':'#ffffff')
}.height(160).width(130).onClick(() => {
this.currentBarIndex=item.num
})
})
}
}
}
★bug3:注意Array的'>'和’=‘间需要加上空格,否则会一起被读为大于等于。
(3)实现效果:
Tabs选项卡通过页签进行内容视图切换得容器组件,每个页签对应一个内容视图。虽然是容器组件,但是Tabs仅可包含TabContent子组件。
(1)语法结构:
Tabs(value?:{barPosition?:BarPosition,index?:number,controller?:TabsCotroller})
(2)Tabs含有三个参数,含义如下:
(3)常用属性:vertical是否为竖向Tabs,scrollable是否可以通过滑动切换页面,baeMode工具栏的布局模式,barWidth工具栏的宽度,barHeight工具栏的高度,animationDuration页签的滑动动画时长。
(4)常用事件:onChange:Tab页签切换后触发的事件。语法结构如下:
onChange(event: (index: number) => void)
(5)常用对象和方法:
(1)语法结构:仅在Tabs中使用,对应一个切换页签的内容视图。语法结构如下:
TabContent(){
页签内容布局
}
★★★Tip1:TabContent中只支持单个子组件,因此页签一般都采用容器组件包裹布局。
(2)常用属性:tabBar,语法结构如下:
tabBar(string|Resource|{icon?: string | Resource,text?: string|Resource} |CustomBuilder)
//tabBar中支持以单个字符串,单个资源(可为字符串可为图片资源),图片+字符串组合的对象,自定义组件作为参数进行工具栏的样式和布局。
★★★Tip2:TabContent组件不支持设置通用宽度和高度属性,其宽度默认撑满Tabs父组件,高度由Tabs父组件高度与TabBar组件高度决定。
(1)测试代码:代码中涉及到了一些装饰器,自定义组件等相关知识,可以查看该系列之前的博客,有讲解。
@Entry
@Component
struct TabsTabContent{
@State currentBottomTabIndex1:number=0
@State currentBottomTabIndex2:number=0
@State currentTopTabIndex:number=0
build() {
Row() {
Column() {
Tabs({barPosition:BarPosition.Start}){//工具条放置在顶部
TabContent(){
//页签1
Tabs({barPosition:BarPosition.End}){//工具条放置在底部
//1-1
TabContent(){
Column(){}.width('100%').height('100%').backgroundColor('#0000FF')
}
.tabBar(this.bottomBar1('测试1-1',$r('app.media.radioclear'),0))
//1-2
TabContent(){
Column(){
}.width('100%').height('100%').backgroundColor('#000000')
}
.tabBar(this.bottomBar1('测试1-2',$r('app.media.radioclear'),1))
//1-3
TabContent(){
Column(){}.width('100%').height('100%').backgroundColor('#044444')
}
.tabBar(this.bottomBar1('测试1-3',$r('app.media.radioclear'),2))
}
.width('100%')
.height('100%')
.onChange((index)=>{
this.currentBottomTabIndex1=index
})
}.tabBar(this.topBar1('测试1',0))
//页签2
TabContent(){
Tabs({barPosition:BarPosition.End}){
//2-1
TabContent(){}
.tabBar(this.bottomBar2('测试2-1',$r('app.media.radioclear'),0))
.backgroundColor('#000000')
//2-2
TabContent(){}
.tabBar(this.bottomBar2('测试2-2',$r('app.media.radioclear'),1))
.backgroundColor('#044444')
//2-3
TabContent(){}
.tabBar(this.bottomBar2('测试2-3',$r('app.media.radioclear'),2))
.backgroundColor('#0000FF')
}
.onChange((index)=>{
this.currentBottomTabIndex2=index
})
}.tabBar(this.topBar1('测试2',1))
}.width('100%')
.height('100%')
.onChange((index)=>{
this.currentTopTabIndex=index
})
}
}
}
//页签1底部Tabs的工具栏的自定义
@Builder bottomBar1(name:string,icon:Resource,index:number){
Column(){
Image(icon).width(24).height(24).fillColor(this.currentBottomTabIndex1===index?'#044444':'')
Text(name).fontSize(20).fontColor(this.currentBottomTabIndex1===index?'#044444':'#000000').margin(10)
}.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
//页签2底部Tabs的工具栏的自定义
@Builder bottomBar2(name:string,icon:Resource,index:number){
Column(){
Image(icon).width(24).height(24).fillColor(this.currentBottomTabIndex2===index?'#044444':'')
Text(name).fontSize(20).fontColor(this.currentBottomTabIndex2===index?'#044444':'#000000').margin(10)
}.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
//顶部Tabs的工具栏的自定义
@Builder topBar1(name:string,index:number){
Text(name).fontSize(30)
.fontColor(this.currentTopTabIndex===index?'#FFFFFF':'#000000')
.width('100%').height('100%').border({width:2})
.backgroundColor(this.currentTopTabIndex===index?'#000000':'#FFFFFF')
.textAlign(TextAlign.Center)
}
}
(2)实现效果
(1)语法结构:由“行”和“列”分割的单元格所组成,通过指定“项目”所在的单元格做出各种各样的布局。语法结构如下:
Grid(scroller?: Scroller)
(2)属性解析:
(3)和TabContent相同,GridItem也并不是容器组件,只是列表中的项,其内部也只能包含一个根组件。
(4)测试代码:
//1.数据
private foodList:Array =[
{id:0,name:'番茄',kk:15,category:1,img:$r('app.media.tomato'),rl:0,yy:0,zf:0},
{id:1,name:'核桃',kk:646,category:3,img:$r('app.media.walnut'),rl:0,yy:0,zf:0},
{id:2,name:'黄瓜',kk:16,category:1,img:$r('app.media.cucumber'),rl:0,yy:0,zf:0},
{id:3,name:'蓝莓',kk:57,category:2,img:$r('app.media.blueberry'),rl:0,yy:0,zf:0},
{id:4,name:'螃蟹',kk:97,category:4,img:$r('app.media.crab'),rl:0,yy:0,zf:0},
{id:5,name:'冰淇淋',kk:150,category:5,img:$r('app.media.icecream'),rl:0,yy:0,zf:0},
{id:6,name:'洋葱',kk:40,category:1,img:$r('app.media.onion'),rl:0,yy:0,zf:0},
{id:7,name:'蘑菇',kk:20,category:1,img:$r('app.media.mushroom'),rl:0,yy:0,zf:0},
{id:8,name:'猕猴桃',kk:61,category:2,img:$r('app.media.kiwifruit'),rl:0,yy:0,zf:0},
{id:9,name:'火龙果',kk:55,category:2,img:$r('app.media.pitaya'),rl:0,yy:0,zf:0},
{id:10,name:'草莓',kk:32,category:2,img:$r('app.media.strawberry'),rl:0,yy:0,zf:0},
{id:11,name:'牛油果',kk:171,category:2,img:$r('app.media.avocado'),rl:0,yy:0,zf:0}
]
//2.Grid
Grid(){
ForEach(this.foodList,(item)=>{
GridItem(){
foodItem({food:item})
}
})
}.rowsGap(6)
.columnsGap(6)
.columnsTemplate('1fr 1fr')
//3.GridItem自定义组件
//主页单个食物的布局
@Component struct foodItem{
private food:FoodInfo
build(){
Column(){
Image(this.food.img).width('100%').height(152).backgroundColor('#f1f3f5')
.objectFit(ImageFit.Contain)
Row(){
Text(this.food.name).fontSize(16).fontColor(0x3E3E3E)
Blank()
Text(this.food.kk+'千卡').fontSize(16).fontColor(0x3E3E3E)
}.width('100%')
.height(32)
.backgroundColor('#e5e5e5')
.padding({left:12,right:12})
.clip(new Rect({width:'100%',height:'100%',radius:6}))
}.onClick(() => {
router.pushUrl({
url:'common/CommonExampleTool',
params:{
foodInfo:this.food
}
})
})
}
}
(5)实现效果:
(1)语法结构:容器组件,包含一系列相同宽度的列表项。适合连续、多行呈现同类数据。语法结构如下:
List(value?:{space?:number|string,initialIndex?:number,scroller?:Scroller})
(2)参数解析:包含space、initialIndex、scroller参数,含义如下:
(3)和TabContent、GridItem相同,ListItem也并不是容器组件,只是列表中的项,其内部也只能包含一个根组件。
★★★Tip4:列表中列表项(ListItem)数量过多超出屏幕后,会自动提供滚动功能。
★★★Tip5:列表中列表项(ListItem)可以横向排列,也可以纵向排列。
(4)部分属性解析:listDirection列表排列方向:参数值为Axis枚举类。
(5)测试代码:
//1.数据
private foodList:Array =[
{id:0,name:'番茄',kk:15,category:1,img:$r('app.media.tomato'),rl:0,yy:0,zf:0},
{id:1,name:'核桃',kk:646,category:3,img:$r('app.media.walnut'),rl:0,yy:0,zf:0},
{id:2,name:'黄瓜',kk:16,category:1,img:$r('app.media.cucumber'),rl:0,yy:0,zf:0},
{id:3,name:'蓝莓',kk:57,category:2,img:$r('app.media.blueberry'),rl:0,yy:0,zf:0},
{id:4,name:'螃蟹',kk:97,category:4,img:$r('app.media.crab'),rl:0,yy:0,zf:0},
{id:5,name:'冰淇淋',kk:150,category:5,img:$r('app.media.icecream'),rl:0,yy:0,zf:0},
{id:6,name:'洋葱',kk:40,category:1,img:$r('app.media.onion'),rl:0,yy:0,zf:0},
{id:7,name:'蘑菇',kk:20,category:1,img:$r('app.media.mushroom'),rl:0,yy:0,zf:0},
{id:8,name:'猕猴桃',kk:61,category:2,img:$r('app.media.kiwifruit'),rl:0,yy:0,zf:0},
{id:9,name:'火龙果',kk:55,category:2,img:$r('app.media.pitaya'),rl:0,yy:0,zf:0},
{id:10,name:'草莓',kk:32,category:2,img:$r('app.media.strawberry'),rl:0,yy:0,zf:0},
{id:11,name:'牛油果',kk:171,category:2,img:$r('app.media.avocado'),rl:0,yy:0,zf:0}
]
//2.List
List({space:8}){
ForEach(this.foodList,(item)=>{
ListItem(){
foodItem({food:item})
}
})
}
//3.自定义列表项
@Component struct foodItem{
private food:FoodInfo
build(){
Column(){
Image(this.food.img).width('100%').height(152).backgroundColor('#f1f3f5')
.objectFit(ImageFit.Contain)
Row(){
Text(this.food.name).fontSize(16).fontColor(0x3E3E3E)
Blank()
Text(this.food.kk+'千卡').fontSize(16).fontColor(0x3E3E3E)
}.width('100%')
.height(32)
.backgroundColor('#e5e5e5')
.padding({left:12,right:12})
.clip(new Rect({width:'100%',height:'100%',radius:6}))
}
}
}
(6)实现效果:
(1)语法结构:用于显示内容加载或操作处理等进度。语法结构如下:
Progress(options:{value:number,total?:number,style?:ProgressStyle,type?:ProgressType})
(2)参数解析:
(3)属性解析:value当前进度值,color进度条前景色,style组件样式,包括strokeWidth进度条宽度,scaleCount环形进度条总刻度值,scaleWodth环形进度条刻度粗细(scaleWidth>strokeWidth时,刻度粗细为系统默认)。
(4)测试代码:
build(){
Column(){
//Linear:线性的 Progress({value:2,total:5,type:ProgressType.Linear}).value(3).color("#FF0000").style({strokeWidth:20}).width('90%')
//Linear:线性的:高大于宽Progress({value:2,total:5,type:ProgressType.Linear}).value(3).color("#FF0000").style({strokeWidth:20}).width('20%').height('30%')
//Ring:环形无刻度
Progress({value:2,total:5,type:ProgressType.Ring}).value(3).color("#000000").style({strokeWidth:10})
//Eclipse:圆形
Progress({value:2,total:5,type:ProgressType.Eclipse}).value(3).color("#000000").style({strokeWidth:10})
//ScaleRing:环形有刻度的
Progress({value:2,total:5,type:ProgressType.ScaleRing}).value(3).color("#000000").style({strokeWidth:10,scaleCount:10,scaleWidth:5}) Progress({value:2,total:5,type:ProgressType.ScaleRing}).value(3).color("#000000").style({strokeWidth:10})
//Capsule:胶囊样式
Progress({value:2,total:5,type:ProgressType.Capsule}).value(3).color("#000000").style({strokeWidth:10}).width('40%').height(20) Progress({value:2,total:5,type:ProgressType.Capsule}).value(3).color("#000000").style({strokeWidth:10}).width(20).height('20%')
}.width('100%').height('100%')
}
(5)实现效果:
布局权重,在父组件中,如果兄弟组件均设置了该属性,则按比例布设,如果兄弟组件部分布设该属性,则布设该属性的组件会按比例占据除去未布设的兄弟组件本身尺寸之外的所有位置。应用场景举例:①组件按比例布设在父组件中,②组件占据除去某个组件以外的所有位置,例如内容占据除去标题以外的位置。
(1)DEMO需要实现的功能+知识点结构图:
(2)测试代码:
//知识点:封装类
export class Task{
taskName:string;
flag:boolean;
constructor(taskName:string,flag:boolean) {
this.taskName=taskName;
this.flag=flag;
}
}
//知识点--类的引用
import { Task } from '../ViewData/Task';
@Entry
@Component
struct CommonProgress{
//知识点--变量驱动UI刷新,状态管理,装饰器使用
@State task:Array =[];
@State taskFlag:Array=[];
@State now:number=0;
@State total:number=0;
@State flag:boolean=false;
@State addTask:string="";
build() {
//知识点--容器组件
Column({space:4}){
//知识点--容器组件的叠加组件Stack
Stack(){
Column({space:4}){
//知识点--组件的封装+参数传递(父子组件的单向同步:@State+@Prop装饰器使用)
taskProgressCard({now:this.now,total:this.total})
//不封装的写法
// Row(){
// //知识点--基础组件:Text、Blank、Progress、Button通用属性、特殊属性、事件
// Text("今日待办事项").fontSize(26).fontWeight(FontWeight.Bold)
// Blank()
// Stack(){
// Progress({value:this.now,
// total:this.total,
// type:ProgressType.Ring})
// .style({strokeWidth:5})
// Text(`${this.now} / ${this.total}`)
// }.align(Alignment.Center)
// }.width('90%')
//知识点--组件的封装+参数传递(父子组件的双向同步:@State+@Link装饰器使用)
taskListEdit({flag:$flag,task:$task,taskFlag:$taskFlag,now:$now,total:$total})
//未封装写法
// Button("添加任务").width('90%').onClick(()=>{
// this.flag=true;
// })
// //知识点:容器组件List、列表项ListItem、ForEach循环渲染
// List({space:4}){
// ForEach(this.task,(item,index)=>{
// ListItem(){
// //知识点--自定义组件
// this.taskItem(item,index)
// }
// //扩充知识点:ListItem的左滑布局与事件
// //具有一个属性叫做swipeAction:用于设置列表项的滑动效果
// //start表示右滑,end表示左滑
// .swipeAction({
// //左滑,参数时自定义组件
// end:this.deleteTaskView(index)
// })
// })
// }.width('100%').layoutWeight(1).alignListItem(ListItemAlign.Center)
}.width('90%').height("100%").margin({top:4})
//知识点--条件渲染:实现对话框--实现效果
if(this.flag){
// Column(){
Column({space:20}){
Text("添加待办事项").fontColor("#ffffff").fontSize(18).padding({left:10}).fontWeight(FontWeight.Bold).width('100%').backgroundColor("#044444").height(40)
Row(){
//显示对话框,用于添加任务
Text("待办事项:").margin({left:20}).fontColor("#ffffff").fontSize(18)
//知识点--基础组件TextInput
TextInput({placeholder:"请输入待办事项名称"}).type(InputType.Normal).onChange((value)=>{
//获取数据更改变量值。
this.addTask=value;
}).layoutWeight(1).margin({right:20}).backgroundColor("#eee")
}
Button("添加").width('80%').margin({bottom:20}).onClick(()=>{
//向数组中添加数据,total+1
//知识点--类的定义和使用
this.task.push(new Task(this.addTask,false));
//知识点--数组数据的添加
this.taskFlag.push(false);
this.changeView();
this.flag=false;
this.addTask="";
})
}.width("90%")
.backgroundColor('#ff4e4c4c')
.shadow({ radius: 10, color: Color.Gray, offsetX: 20, offsetY: 20 })
}
}.align(Alignment.Center).width('100%').height('100%')
}.width('100%').height('100%')
}
//未封装时使用的内部函数
//知识点--构建函数自定义组件,也是内部带参数函数,
//单个待办的item
@Builder taskItem(value:Task,index){
Row(){
Text(value.taskName).fontSize(20)
.decoration({type:(this.taskFlag[index]?TextDecorationType.LineThrough:TextDecorationType.None)})
.fontColor(this.taskFlag[index]?"#044444":"#000")
Blank()
Checkbox()
.select(value.flag)
.onChange((value)=> {
this.taskFlag[index]=value;
this.task[index].flag=value;
this.changeView();
}
)
}.width('100%')
}
//未封装时使用的内部函数
//单个待办的左滑效果
@Builder deleteTaskView(index:number){
Text("删除")
.fontColor(Color.White)
.fontSize(20)
.backgroundColor("#FF0000")
.width(100)
.height('100%')
.textAlign(TextAlign.Center)
.padding(5)
.onClick(()=>{
//知识点--数组数据的删除
this.taskFlag.splice(index,1);
this.task.splice(index,1);
this.changeView();
})
}
//知识点--内部函数的声明与使用
changeView(){
this.total=this.task.length;
//知识点--数组数据的过滤
this.now=this.task.filter(item=>item.flag).length;
}
}
//知识点--自定义组件,父子组件传值与数据同步的单向:@State+@Prop装饰器的使用
@Component
struct taskProgressCard{
//state和prop的数据单向同步
@Prop now:number;
@Prop total:number;
build(){
Row(){
//知识点--基础组件:Text、Blank、Progress、Button通用属性、特殊属性、事件
Text("今日待办事项").fontSize(26).fontWeight(FontWeight.Bold)
Blank()
Stack(){
Progress({value:this.now,
total:this.total,
type:ProgressType.Ring})
.style({strokeWidth:5})
Text(`${this.now} / ${this.total}`)
}.align(Alignment.Center)
}.width('90%')
}
}
//知识点--自定义组件,父子组件传值与数据同步的双向:@State+@Link装饰器的使用
@Component
struct taskListEdit{
@Link flag:boolean;
@Link task:Array;
@Link taskFlag:Array;
@Link now:number
@Link total:number
build(){
Column(){
Button("添加任务").width('90%').onClick(()=>{
this.flag=true;
})
//知识点:容器组件List、列表项ListItem、ForEach循环渲染
List({space:4}){
ForEach(this.task,(item,index)=>{
ListItem(){
//知识点--自定义组件
Row(){
Text(item.taskName).fontSize(20)
.decoration({type:(this.taskFlag[index]?TextDecorationType.LineThrough:TextDecorationType.None)})
.fontColor(this.taskFlag[index]?"#044444":"#000")
Blank()
Checkbox()
.select(item.flag)
.onChange((value)=> {
this.taskFlag[index]=value;
this.task[index].flag=value;
this.total=this.task.length;
//知识点--数组数据的过滤
this.now=this.task.filter(item=>item.flag).length;
}
)
}.width('100%')
}
//扩充知识点:ListItem的左滑布局与事件
//具有一个属性叫做swipeAction:用于设置列表项的滑动效果
//start表示右滑,end表示左滑
.swipeAction({
//左滑,参数时自定义组件
end:this.deleteTaskView(index)
})
})
}.width('100%').layoutWeight(1).alignListItem(ListItemAlign.Center)
}.width('90%').height("100%").margin({top:4})
}
@Builder deleteTaskView(index:number){
Text("删除")
.fontColor(Color.White)
.fontSize(20)
.backgroundColor("#FF0000")
.width(100)
.height('100%')
.textAlign(TextAlign.Center)
.padding(5)
.onClick(()=>{
//知识点--数组数据的删除
this.taskFlag.splice(index,1);
this.task.splice(index,1);
this.changeView();
})
}
//知识点--内部函数的声明与使用
changeView(){
this.total=this.task.length;
//知识点--数组数据的过滤
this.now=this.task.filter(item=>item.flag).length;
}
}
(3)实现效果:
Tip:思考一下,上述为什么会使用taskFlag这个变量,为什么会在多个自定义组件中重复申明changeView函数?(提示:与装饰器的使用有关,想不到的可以看看2-5补充的9部分),看过该博客的可以尝试自己优化一下再看3.2,没看过的可以看一下再自己优化再看3.2。
(1)上述为什么会使用taskFlag这个变量?
因为task是一个元素为对象的数组,当采用@State+@Link实现操作时,数组中的内部对象的属性修改做不到数据同步.
(2)为什么会在多个自定义组件中重复申明changeView函数?
因为在父组件中声明该函数,在子组件中使用不到。
(1)解决办法时使用@Observed+@ObjectLink装饰器。知识点可参考如下博客:
鸿蒙开发2-5 鸿蒙开发基础语法-部分装饰器使用_builder装饰器-CSDN博客
(2)解决办法是将函数作为参数传递,并且如果传递的函数中有this对象,传递后this代指的对象可能会发生变化,所以需要使用bind接口传递当前的this。
写法如下:
声明传递方:
//函数的声明:带有this对象
changeView(){
this.total=this.taskTest.length;
//知识点--数组数据的过滤
this.now=this.taskTest.filter(item=>item.flag).length;
}
//函数的传递:使用bind将this传递-》传递到taskListEdit中使用
taskListEdit({flag:$flag,taskTest:$taskTest,now:$now,total:$total,onTaskChange:this.changeView.bind(this)})
★★★★★★Tip:注意传递函数作为参数时不要加”()“,否则相当于调用函数,传递的是函数的返回值。
接收使用方:
//接受使用方定义该变量用于接收函数
onTaskChange:()=>void//表明变量类型是无参数无返回值的函数
//接受使用方使用函数
this.onTaskChange()
函数中含有this对象不使用bind可能触发的问题:
Error message: Cannot read property bind of undefined
实现效果和3.2相同。代码如下:
//封装的对象
@Observed//必须加该装饰器,否则元素为该对象的数组,修改内部对象属性时不会数据同步。
export class TaskTest{
taskName:string;
flag:boolean;
constructor(taskName:string,flag:boolean) {
this.taskName=taskName;
this.flag=flag;
}
}
//实现
//知识点--类的引用
import { TaskTest } from '../ViewData/TaskTest';
@Entry
@Component
struct CommonProgressTest{
//知识点--变量驱动UI刷新,状态管理,装饰器使用
@State taskTest:Array =[];
@State now:number=0;
@State total:number=0;
@State flag:boolean=false;
@State addTask:string="";
build() {
//知识点--容器组件
Column({space:4}){
//知识点--容器组件的叠加组件Stack
Stack(){
Column({space:4}){
//知识点--组件的封装+参数传递(父子组件的单向同步:@State+@Prop装饰器使用)
taskProgressCard({now:this.now,total:this.total})
//知识点--组件的封装+参数传递(父子组件的双向同步:@State+@Link装饰器使用
taskListEdit({flag:$flag,taskTest:$taskTest,now:$now,total:$total,onTaskChange:this.changeView.bind(this)})
}.width('90%').height("100%").margin({top:4})
//知识点--条件渲染:实现对话框--实现效果
if(this.flag){
addTaskDialog({addTask:$addTask,taskTest:$taskTest,flag:$flag,onTaskChange:this.changeView.bind(this)})
}
}.align(Alignment.Center).width('100%').height('100%')
}.width('100%').height('100%')
}
changeView(){
this.total=this.taskTest.length;
//知识点--数组数据的过滤
this.now=this.taskTest.filter(item=>item.flag).length;
}
}
//知识点--自定义组件,父子组件传值与数据同步的单向:@State+@Prop装饰器的使用
@Component
struct taskProgressCard{
//state和prop的数据单向同步
@Prop now:number;
@Prop total:number;
build(){
Row(){
//知识点--基础组件:Text、Blank、Progress、Button通用属性、特殊属性、事件
Text("今日待办事项").fontSize(26).fontWeight(FontWeight.Bold)
Blank()
Stack(){
Progress({value:this.now,
total:this.total,
type:ProgressType.Ring})
.style({strokeWidth:5})
Text(`${this.now} / ${this.total}`)
}.align(Alignment.Center)
}.width('90%')
}
}
//知识点--自定义组件,父子组件传值与数据同步的双向:@State+@Link装饰器的使用
@Component
struct taskListEdit{
@Link flag:boolean;
@Link taskTest:Array;
@Link now:number
@Link total:number
onTaskChange:()=>void
build(){
Column(){
Button("添加任务").width('90%').onClick(()=>{
this.flag=true;
})
//知识点:容器组件List、列表项ListItem、ForEach循环渲染
List({space:4}){
ForEach(this.taskTest,(item,index)=>{
ListItem(){
//函数作为参数的传递
//当方法传递时方法中使用了this,this所代指的对象会发生变化:所以需要使用bind接口将当前的this传递过去。
taskListItem({item:item,onTaskChange:this.onTaskChange.bind(this)})
}
//扩充知识点:ListItem的左滑布局与事件
//具有一个属性叫做swipeAction:用于设置列表项的滑动效果
//start表示右滑,end表示左滑
.swipeAction({
//左滑,参数时自定义组件
end:this.deleteTaskView(index)
})
})
}.width('100%').layoutWeight(1).alignListItem(ListItemAlign.Center)
}.width('90%').height("100%").margin({top:4})
}
@Builder deleteTaskView(index:number){
Text("删除")
.fontColor(Color.White)
.fontSize(20)
.backgroundColor("#FF0000")
.width(100)
.height('100%')
.textAlign(TextAlign.Center)
.padding(5)
.onClick(()=>{
this.taskTest.splice(index,1);
this.onTaskChange();
})
}
//知识点--内部函数的声明与使用
}
@Component
struct taskListItem{
//解决问题1:元素为对象的数组,如何对内部对象的属性做数据同步
@ObjectLink item:TaskTest
//解决问题2:在其他组件中定义的函数如何在本组件中使用:
onTaskChange:()=>void//改写法表示该变量是一个无参无返回值的函数
build(){
//知识点--自定义组件
Row(){
Text(this.item.taskName).fontSize(20)
.decoration({type:(this.item.flag?TextDecorationType.LineThrough:TextDecorationType.None)})
.fontColor(this.item.flag?"#044444":"#000")
Blank()
Checkbox()
.select(this.item.flag)
.onChange((value)=> {
this.item.flag=value;
this.onTaskChange();
})
}.width('100%')
}
}
//对话框
@Component
struct addTaskDialog{
@Link addTask:string
@Link flag:boolean
@Link taskTest:Array
onTaskChange:()=>void
build(){
Column({space:20}){
Text("添加待办事项").fontColor("#ffffff").fontSize(18).padding({left:10}).fontWeight(FontWeight.Bold).width('100%').backgroundColor("#044444").height(40)
Row(){
//显示对话框,用于添加任务
Text("待办事项:").margin({left:20}).fontColor("#ffffff").fontSize(18)
//知识点--基础组件TextInput
TextInput({placeholder:"请输入待办事项名称"}).type(InputType.Normal).onChange((value)=>{
//获取数据更改变量值。
this.addTask=value;
}).layoutWeight(1).margin({right:20}).backgroundColor("#eee")
}
Button("添加").width('80%').margin({bottom:20}).onClick(()=>{
//向数组中添加数据,total+1
//知识点--类的定义和使用
this.taskTest.push(new TaskTest(this.addTask,false));
this.onTaskChange();
this.flag=false;
this.addTask="";
})
}.width("90%")
.backgroundColor('#ff4e4c4c')
.shadow({ radius: 10, color: Color.Gray, offsetX: 20, offsetY: 20 })
}
}