全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2024-08-10_鸿蒙Next - 手把手教你实现省市区镇-四级地址选择弹窗组件

您的位置:首页 >> 新闻 >> 行业资讯

鸿蒙Next - 手把手教你实现省市区镇-四级地址选择弹窗组件 点击关注公众号,“技术干货”及时达!前言hello 大家好,我是无言,因为地址级联选择功能其实还是非常常见的,而且官方有TextPicker文本选择组件也可以实现地址级联选择,但是我发现超过3级之后,文字就太多了,会很难看,不好操作等相关问题。所以有必要自己来实现一个好看的省市区镇-四级地址级联选择组件。 目的通过本篇文章小伙伴们能学到什么?我简单的总结了一下大概有以下几点。 了解到鸿蒙Next 自定义弹窗 的核心用法。了解到 实现级联选择的实现思路和过程,不仅限于鸿蒙,也适用于其他框架和场景。了解到鸿蒙Next中如何封装自己的自定义组件。了解到鸿蒙Next中组件之间是如何通信的,以及如何实现自己想要的功能。效果提前看一看 实现过程一、准备工作安装好最新DevEco Studio[1]开发工具,创建一个新的空项目。新增目录结构 ets/components/cascade/ ,在下面添加文件 addressObj.ts 用于存放地址Obj对象,index.ets 用来初始化弹窗容器,CustomAddress.ets用来存放具体的级联选择业务代码,Cascade.d.ts TS 声明文件。 二、实现自定义弹窗将官网自定义弹窗的示例3复制过来,放入到ets/components/cascade/index.ets中后稍微修改一下,修改的地方我都添加注释了。主要是 去掉 @Entry 页面的入口组件装饰,修改组件命名并用 export暴露组件供外部使用。// xxx.ets @CustomDialog struct CustomDialogExample { controller?: CustomDialogController cancel: () = void = () = { } confirm: () = void = () = { } build() { Column() { Text('这是自定义弹窗') .fontSize(30) .height(100) Button('点我关闭弹窗') .onClick(() = { if (this.controller != undefined) { this.controller.close() } }) .margin(20) } } } // @Entry 去掉入口页面标志 @Component export struct CustomDialogCascade { //修改命名 注意前面加了 export 需要暴露组件 dialogController: CustomDialogController | null = new CustomDialogController({ builder: CustomDialogExample({ cancel: ()= { this.onCancel() }, confirm: ()= { this.onAccept() } }), cancel: this.existApp, autoCancel: true, onWillDismiss:(dismissDialogAction: DismissDialogAction)= { console.info("reason=" + JSON.stringify(dismissDialogAction.reason)) console.log("dialog onWillDismiss") if (dismissDialogAction.reason == DismissReason.PRESS_BACK) { dismissDialogAction.dismiss() } if (dismissDialogAction.reason == DismissReason.TOUCH_OUTSIDE) { dismissDialogAction.dismiss() } }, alignment: DialogAlignment.Center, offset: { dx: 0, dy: -20 }, customStyle: false, cornerRadius: 20, width: 300, height: 200, borderWidth: 1, borderStyle: BorderStyle.Dashed,//使用borderStyle属性,需要和borderWidth属性一起使用 borderColor: Color.Blue,//使用borderColor属性,需要和borderWidth属性一起使用 backgroundColor: Color.White, shadow: ({ radius: 20, color: Color.Grey, offsetX: 50, offsetY: 0}), }) // 在自定义组件即将析构销毁时将dialogController置空 aboutToDisappear() { this.dialogController = null // 将dialogController置空 } onCancel() { console.info('Callback when the first button is clicked') } onAccept() { console.info('Callback when the second button is clicked') } existApp() { console.info('Click the callback in the blank area') } build() { Column() { Button('click me') .onClick(() = { if (this.dialogController != null) { this.dialogController.open() } }).backgroundColor(0x317aff) }.width('100%').margin({ top: 5 }) } } 修改ets/pages/index.ets 去掉无关的代码,在页面中引入我们的组件。 import {CustomDialogCascade} from "../components/cascade/index" @Entry @Component struct Index { build() { RelativeContainer() { CustomDialogCascade() } .height('100%') .width('100%') } } 预览一下看看效果。 三、实现父子组件的通信在讲后续功能前,这里有必要讲一下鸿蒙开发组件状态。 @State用于装饰当前组件的状态变量而且「必须初始化」,@State装饰的变量在发生变化时,会驱动当前组件的视图刷新。 @Prop用于装饰子组件的状态变量而且「不允许本地初始化」,@Prop装饰的变量会同步父组件的状态,但「只能单向同步」。 @Link用于装饰子组件的状态变量而且「不允许本地初始化」,@Prop变量同样会同步父组件状态,但是「能够双向同步」。 @Provide和@Consume用于跨层级传递状态信息,其中@Provide用于装饰祖先组件的状态变量,@Consume用于装饰后代组件的状态变量。「@Provide装饰变量必须本地初始化,而@Consume装饰的变量不允许本地初始化」, 而且他们「能够双向同步」。 @Props与@Link声明接收的属性,必须是@State的属性,而不能是@State属性对象中嵌套的属性解决办法将嵌套对象的类型用class定义, 并使用@Observed来装饰,子组件中定义的嵌套对象的属性, 使用@ObjectLink来装饰。 @Watch用来监视状态数据的变化,包括:@State、@Prop、@Link、@Provide、@Consume 「一旦状态数据变化,监视的回调就会调用」,这里我有必要加一个示例。 @State @Watch('onCountChange') count: number = 0 /** * 一旦count变化,此回调函数就会自动调用 * @param count 被监视的状态属性名 */ onCountChange (count) { // 可以在此做特定处理 } 四、完善逻辑好了回到我们的主题,前面我们的示例中,只是子组件自己调用弹窗了,我们要实现以下几个功能。 父组件调用子组件方法唤醒子组件弹窗。父组件传参控制选择地址的层级数量。子组件选好数据之后回调方法传给父组件。修改 /ets/components/cascade/index.ets,实现父组件传参给子组件,子组件回调方法传值给父组件。然后还修改了弹窗的样式以及位置,详细请看下面代码。 // xxx.ets @CustomDialog struct CustomDialogExample { controller?: CustomDialogController @Prop level: number; cancel: () = void = () = { } confirm: (data:string) = void = () = { } build() { Column() { Text('这是自定义弹窗') .fontSize(30) .height(100) Text('层级'+this.level) Button('点我关闭弹窗') .onClick(() = { if (this.controller != undefined) { this.controller.close() } this.confirm('aaa') //回传信息 }) .margin(20) } } } // @Entry 去掉入口页面标志 @Component export struct CustomDialogCascade { //修改命名 注意前面加了 export 需要暴露组件 @Link CustomDialogController: CustomDialogController | null ; @Prop level: number; cancel?: () = void confirm?: (data:string) = void = () = { } aboutToAppear(): void { this.CustomDialogController= new CustomDialogController({ builder: CustomDialogExample({ cancel: this.cancel, confirm: this.confirm, level:this.level, }), autoCancel: true, onWillDismiss:(dismissDialogAction: DismissDialogAction)= { if (dismissDialogAction.reason == DismissReason.PRESS_BACK) { dismissDialogAction.dismiss() } if (dismissDialogAction.reason == DismissReason.TOUCH_OUTSIDE) { dismissDialogAction.dismiss() } }, alignment: DialogAlignment.Bottom, offset: { dx: 0, dy: 0}, customStyle: false, cornerRadius: 0, width: '100%', backgroundColor: Color.White, }) } aboutToDisappear() { this.CustomDialogController = null // 将dialogController置空 } build() { //因为用的 自定义弹窗功能,所以这下面可以为空 } } 修改/ets/pages/index.ets 文件实现功能主要是父元素调用子组件方法,以及子组件的回调方法调用 import {CustomDialogCascade} from "../components/cascade/index" @Entry @Component struct Index { @State CustomDialogController :CustomDialogController|null = null; build() { Column() { Button('打开弹窗') .onClick(()={ this.CustomDialogController?.open() }) CustomDialogCascade( { level:3,//层级 最大4 最小1 CustomDialogController:this.CustomDialogController, //弹窗实体类 主要控制弹窗显示隐藏等 confirm:(data)={ console.log('data',(data)) } }, ) } .height('100%') .width('100%') } } 运行效果如下,点击 点我关闭弹窗 按钮可以发现 子组件的回调信息 aaa 在父组件中成功打印。 五、完善地址级联逻辑因为对象取值性能最好,速度也快,所以我这里采用的是对象信息结构,在 addressObj.ts 文件中存放我们的所有城市信息,因为内容过多,我就只举例展示部分,我将完整的城市信息,放在了本文末尾,有需要的小伙伴可以自己下载下来尝试一下。 export const regionDict = { "86": { "110000": "北京市", "120000": "天津市", "130000": "河北省", ... }, "110000": { "110100": "北京市" }, "110100": { "110101": "东城区", "110102": "西城区", "110105": "朝阳区", "110106": "丰台区", "110107": "石景山区", "110108": "海淀区", "110109": "门头沟区", "110111": "房山区", "110112": "通州区", "110113": "顺义区", "110114": "昌平区", "110115": "大兴区", "110116": "怀柔区", "110117": "平谷区", "110118": "密云区", "110119": "延庆区" }, "110101": { "110101001": "东华门街道", "110101002": "景山街道" }, ... }; 声明文件 Cascade.d.ts 添加类型 export interface RegionType{ code?:string; pcode?:string; name?:string } export type levelNumber = 1 | 2 | 3 | 4; 修改 CustomAddress.ets 完成我们主要的核心业务代码,内容比较多,该添加注释的地方我都添加了,具体功能代码请看下面内容。import { regionDict } from "./addressObj" import {RegionType,levelNumber} from './Cascade' @CustomDialog export struct CustomAddress { controller?: CustomDialogController @State region:RegionType[]=[]; //存放选中的结果 @State data: RegionType[] = [];// 提供选中的列表 @State step:number = 0;//存放步数 @State active:number = 0;//当前高亮步骤 level:levelNumber=4; //层级 默认 为 4级 可以选镇街道一级 cancel: () = void = () = { console.log('关闭') } confirm: (region:RegionType[]) = void = () = { } // 页面加载执行 aboutToAppear(): void { this.loadRegionData('86') } /** * 根据父元素code生成列表数据 * @param pcode */ loadRegionData(pcode = '86') { this.data.length=0 Object.keys(regionDict).forEach((code)={ if(code==pcode){ Object.keys(regionDict[code]).forEach((key)={ this.data.push({ name:regionDict[code][key], code:key, pcode:pcode }) }) } }) if(this.data.length == 0) { this.controller?.close() //关闭弹窗 } } // 上面步骤选中 onStepClickSelect(index:number,item:RegionType){ this.active=index; this.loadRegionData(item.pcode) } // 数据选中 onRowClickSelect(item:RegionType){ if(this.active==3 || this.active=(this.level-1)){ //如果是到了最后一步选择街道 则直接结束 this.region.push(item) this.confirm(this.region) this.controller?.close()//关闭弹窗 return } if(this.active==this.step){ this.step++; this.active++; }else{ this.region= this.region.slice(0,this.active) //数组截取 this.active++; //从选中的地方重新开始 this.step=this.active //步骤也是一样重新开始 } this.region.push(item) this.loadRegionData(item.code) } // 获取名称 getLableName(){ let name =`请选择` switch (this.step){ case 0: name=`请选择省份/地区` break; case 1: name=`请选择城市` break; case 2: name=`请选择区县` break; case 3: name=`请选择街道` break; } return name } build() { Column() { // 存储已选择信息 Column(){ ForEach(this.region, (item: RegionType,index:number) = { Flex({alignItems:ItemAlign.Center}){ Row(){ Text() .backgroundColor(this.active=index?'#396ec1':'#ff737070') .width(6) .height(6) .borderRadius(10) // 顶部线条 if (index0){ Text() .width(1) .height(13) .position({left:2,top:0}) .backgroundColor(this.activeindex?'#396ec1':'#ff737070') } // 底部线条 if(this.step=index){ Text() .width(1) .height(13) .position({left:2,top:'50%'}) .backgroundColor(this.activeindex?'#396ec1':'#ff737070') } }.height(25) .width(20) .align(Alignment.Center) Row(){ Text(item.name).fontSize(14).fontColor('#333333') } .flexGrow(1) .height(25) .border({ width: { bottom:1 }, color: 0xf5f5f5, style: { left:null, right: null, top: null, bottom: BorderStyle.Solid } }) .onClick(()={ this.onStepClickSelect(index,item) }) }.width('100%') .padding({left:10}) }) // 提示信息 Flex({alignItems:ItemAlign.Center}){ Row(){ Text() .backgroundColor(this.active==this.step?'#396ec1':'#ff737070') .width(6) .height(6) .borderRadius(10) // 顶部线条 if(this.step){ Text() .width(1) .height(13) .position({left:2,top:0}) .backgroundColor(this.active==this.step?'#396ec1':'#ff737070') } }.height(25) .width(20) .align(Alignment.Center) Row(){ Text(this.getLableName()).fontSize(14).fontColor(this.active==this.step?'#396ec1':'#333') } .flexGrow(1) .height(25) }.width('100%') .padding({left:10}) }.padding({top:10,bottom:10}) // 分割线 Column(){ }.height(10) .backgroundColor(0xf5f5f5) .width('100%') // 下方列表 Column(){ List({ space: 5, initialIndex: 0 }) { ForEach(this.data, (item: RegionType) = { ListItem() { Text('' + item.name) .width('100%') .fontSize(14) .fontColor('#333333') .textAlign(TextAlign.Start) } .padding({left:10}) .height(25) .onClick(()={ this.onRowClickSelect(item) }) }) } .listDirection(Axis.Vertical) // 排列方向 .scrollBar(BarState.Off) .friction(0.6) .contentStartOffset(10) //列表滚动到起始位置时,列表内容与列表显示区域边界保留指定距离 .contentEndOffset(10) //列表内容与列表显示区域边界保留指定距离 .divider({ strokeWidth:1, color: 0xf5f5f5, startMargin: 5, endMargin: 5 }) // 每行之间的分界线 .edgeEffect(EdgeEffect.Spring) // 边缘效果设置为Spring .width('100%') .height('100%') }.height(200) } } } 修改/ets/components/cascade/index.ets 文件import {CustomAddress }from "./CustomAddress" import {RegionType,levelNumber} from './Cascade' export {RegionType }from './Cascade' //重新暴露声明文件类型 // @Entry 去掉入口页面标志 @Component export struct CustomDialogCascade { //修改命名 注意前面加了 export 需要暴露组件 @Link CustomDialogController: CustomDialogController | null ; @Prop level: levelNumber; cancel?: () = void confirm?: (data:RegionType[]) = void = () = { } aboutToAppear(): void { this.CustomDialogController= new CustomDialogController({ builder: CustomAddress({ cancel: this.cancel, confirm: this.confirm, level:this.level, }), autoCancel: true, onWillDismiss:(dismissDialogAction: DismissDialogAction)= { if (dismissDialogAction.reason == DismissReason.PRESS_BACK) { dismissDialogAction.dismiss() } if (dismissDialogAction.reason == DismissReason.TOUCH_OUTSIDE) { dismissDialogAction.dismiss() } }, alignment: DialogAlignment.Bottom, offset: { dx: 0, dy: 0}, customStyle: false, cornerRadius: 0, width: '100%', backgroundColor: Color.White, }) } aboutToDisappear() { this.CustomDialogController = null // 将dialogController置空 } build() { } } 重新运行一下,当街道选好之后,即可发现弹窗自动关闭,而且选好地址信息成功拿到。 总结本文详细介绍了关于在华为鸿蒙系统 去实现一个自定义弹窗的详细教程,主要是提供一些思路,了解组件之间通信的技巧,以及如何实现一个地址级联选中的详细过程。 希望这篇文章能帮到你,最后我把完整代码放到了gitee(https://gitee.com/dssasdadssdfsdfs/region-cascade-new)上有需要的小伙伴可以自己拉下来去试一试。 Reference[1]https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.huawei.com%2Fconsumer%2Fcn%2Fdownload%2F: https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.huawei.com%2Fconsumer%2Fcn%2Fdownload%2F 点击关注公众号,“技术干货”及时达! 阅读原文

上一篇:2025-05-16_一键开关灯!谷歌用扩散模型,将电影级光影控制玩到极致 下一篇:2025-09-17_AAAI2026延期!!

TAG标签:

19
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价