背景:
公司现在要开发纯血鸿蒙版本APP,我被抽调过来做点功能。现在要做一个省市县级联选择框,并且要封装为组件,供其他页面模块使用。
效果图:
难点:
1. 现在官方文档上只是查到了TextPicker组件是可以做这样级联选择的效果
2. 这种弹窗,官方文档上是有半模态转场,只是这个半模态是一个严格和宿主节点绑定在一起的弹窗,需要绑定在触发的地方,才可以触发弹出,而且半模态转场中的bindSheet,需要初始化Builder类型装饰器(即模态框中要展示的内容),只是Builder装饰器不允许在组件外调用。
实现:
1. 创建一个组件,组件中内容包括选择器所需要的数据数组、选择器TextPicker所在的Builder装饰器,在TextPicker中去实现级联效果、组件build主体;通过状态(Link、State)来实现在目标页面调用选择器组件
2. 数据数组包括省市县的数据,可以静态写,也可以读取数据文件,我这是读的他们提供的地区xml文件
3. Builder装饰器是半模态专场初始化时的模态框中的内容,其中包括弹框顶部 “取消”、弹窗标题、“确定”布局和事件,下边的省市县三级选择器
@BuilderselectRegion() {Column() {Flex({justifyContent: FlexAlign.SpaceBetween,alignItems: ItemAlign.Center}) {Text($r('app.string.cancel_txt')).width('36lpx').fontColor('#4976EE').onClick(() => {this.showSheet = false;})Text(this.title).fontColor('#000').fontSize(18).fontWeight(600)Text($r('app.string.ok_txt')).width('36lpx').fontColor('#4976EE').onClick(() => {this.showSheet = false;if (this.okCallback) {this.okCallback(this.values);}})}.width('92%').height('36lpx').margin({ left: '4%', right: '4%', top: '6lpx' })TextPicker({range: this.range, //['北京', '北京市', '东城区']selected: $$this.selected, //[0, 0, 0]}).canLoop(false)// 不要循环.onChange((values) => {//进行防抖处理,防止滚动期间多次发送请求clearTimeout(this.timeId)this.timeId = setTimeout(async () => {let p = this.getProvince(values[0]);//当省份被改变if (this.values[0] !== values[0]) {this.range[1] = this.getCityArray(p.city) //市this.range[2] = this.getDistrictArray(p.city) //区//当省份发生变化需要把城市置零,当城市发生变化需要把地区置零this.selected[1] = 0this.selected[2] = 0//内容页面展示需要同步this.values[0] = values[0]this.values[1] = this.range[1][0]this.values[2] = this.range[2][0]//当城市被改变} else if (this.values[1] !== values[1]) {this.range[2] = this.getDistrictArrayByName(values[1], p.city) //区//当城市发生变化需要把地区置零this.selected[2] = 0//内容页面展示需要同步this.values[1] = values[1]this.values[2] = this.range[2][0]//当地区被改变} else if (this.values[2] !== values[2]) {//内容页面展示需要同步this.values[2] = values[2]}}, 100)})}}
4. 组件build主体,是封装的组件主体,是将来要放到要使用此组件的页面中的。在此主体中,绑定初始了半模态框,并定义了半模态框的一些样式
build() {Column().bindSheet($$this.showSheet, this.selectRegion(), {height: 260,showClose: false,shouldDismiss: ((sheetDismiss: SheetDismiss) => {console.log("bind sheet shouldDismiss")//sheetDismiss.dismiss()}),onWillDismiss: ((DismissSheetAction: DismissSheetAction) => {})})}
5.组件中状态和回调函数
@Link range: string[][] //TextPicker的values数组 [[], [], []]@Link selected: number[] //TextPicker的index数组@Link values: string[] //选中后用于展示的数组 ['北京', '北京市', '东城区']@Link showSheet: boolean; //半模态框@Link title: string;timeId: number = -1 //防抖处理的延时器Idprivate okCallback?: (values: string[]) => void
6.在页面中引入并触发显示弹框组件
import { AreaRegion } from '../../model/AreaRegion'......@State isShowSelectRegion: boolean = false;showAreagion() {this.isShowSelectRegion = true;}build() {Column() {......AreaRegion({showSheet: $isShowSelectRegion,title: $regionSelectTitle,values: $regionData,selected: $regionSelected,range: $regionRange,okCallback: (values) => {/*更新账号所属地区*/let region = values[0] + values[1] + values[2]console.info(region)}).catch((msg: string) => {})}});Button('选择省市区').width('60%').margin({ left: '20%' }).fontColor('#fff').fontSize('22fp').backgroundColor('#2E74FE').onClick(() => {this.showAreagion();})}
}
7。这种做法感觉不是那么标准,因为是要在所使用的页面UI中先加载AreaRegion组件,然后再通过改变@State变量,传递到组件中,实现组件的展示与否。不过现在暂时没有想到其他方案,如果好办法,欢迎交流。
8.注意文中所贴代码不是全部的代码,可以参考这种方式以及重要实现步骤。