在移动应用中,地址选择是一项高频且有一定复杂度的交互。从省到市再到区,三层级联关系需要选择器具备"上级联动下级"的能力。HarmonyOS NEXT ArkUI 的 TextPicker 组件就是为这类场景而生的——它不仅支持单列文本选择,更支持多列模式和级联数据,天然适配地址选择、分类筛选等层级化选择场景。
本文将从 TextPicker 的基础 API 出发,深入讲解级联模式的原理与实现,并构建一个完整的"地址选择器"——包含省市区的三层级联、快捷城市预设和层级切换功能。
关键词:HarmonyOS、ArkUI、TextPicker、级联选择、地址选择器、联动
一、TextPicker 组件概览
TextPicker 是 ArkUI 的通用文本选择器组件,采用滚轮交互模式,支持三种数据结构:
| 模式 | range 类型 | 列数 | 典型场景 |
|---|---|---|---|
| 单列 | string[] | 1 | 性别、血型、星座 |
| 多列 | string[][] | 2+ | 身高(米+厘米)、车牌(省+编号) |
| 级联 | TextCascadePickerRangeContent[] | 2+ | 地址、行业分类、组织架构 |
其中级联模式是 TextPicker 最强大的特性。它通过TextCascadePickerRangeContent定义层级化的树形数据,当用户在上一级做出选择后,下一级的选项会自动更新为对应子节点。这种联动行为由组件内部自动处理,开发者只需提供正确的数据结构。
二、核心 API
2.1 构造函数
TextPicker({range:string[]|string[][]|TextCascadePickerRangeContent[],selected:number|number[],value:string|string[]})参数说明:
range:数据源。可以是简单的字符串数组、二维数组或级联结构selected:初始选中项的索引数组,每个元素对应该列的选中位置value:初始选中项的文本数组,与selected对应
2.2 TextCascadePickerRangeContent 接口
这是级联模式的核心数据类型:
interfaceTextCascadePickerRangeContent{text:string|Resource;// 当前节点的显示文本children?:TextCascadePickerRangeContent[];// 子节点列表}这个接口非常简单——每个节点有text(显示文本)和可选的children(子节点)。顶层数组是第一级选项(如省份),每个选项的children是第二级选项(如城市),以此类推。
2.3 onChange 回调
当用户滑动滚轮改变选择时触发:
.onChange((value:string|string[],index:number|number[])=>{// value: 当前选中的文本值(级联模式下为 string[])// index: 当前选中的索引位置(级联模式下为 number[])})在级联模式下,value和index都是数组类型,长度等于当前列数。
2.4 关键属性
| 属性 | 类型 | 说明 |
|---|---|---|
.defaultPickerItemHeight | number | string | 每列选项的高度 |
.canLoop | boolean | 是否循环滚动,默认true |
.disappearTextStyle | PickerTextStyle | 非选中项的文字样式 |
.selectedTextStyle | PickerTextStyle | 选中项的文字样式 |
三、实战:地址选择器
我们来实现一个完整的地址选择器——支持省/市/区三级级联、6 个快捷城市预设和层级切换(在 2 级和 3 级之间切换)。
3.1 数据准备
首先定义级联数据结构。我们使用 5 个省级区域,每个省下有 3-4 个城市,每个城市下有 3-4 个区/街道:
privatefullData:RegionNode[]=[{text:'北京市',children:[{text:'东城区',children:[{text:'东华门街道'},{text:'景山街道'},{text:'安定门街道'}]},{text:'西城区',children:[{text:'西长安街街道'},{text:'金融街街道'},{text:'什刹海街道'}]},{text:'朝阳区',children:[{text:'望京街道'},{text:'三里屯街道'},{text:'国贸'}]},{text:'海淀区',children:[{text:'中关村街道'},{text:'五道口街道'},{text:'上地街道'}]}]},// ... 上海市、广东省、浙江省、四川省];这是标准的TextCascadePickerRangeContent[]格式——顶层是省份,children是城市,城市的children是区/街道。
3.2 动态构建 2 级数据
为了支持层级切换,我们在aboutToAppear中动态构建一份不含区/街道的 2 级数据:
aboutToAppear():void{this.twoLevelData=[];for(leti=0;i<this.fullData.length;i++){constprov=this.fullData[i];constcityList:RegionNode[]=[];if(prov.children){for(letj=0;j<prov.children.length;j++){cityList.push({text:prov.children[j].text});// 不包含 children}}this.twoLevelData.push({text:prov.text,children:cityList});}this.updateAddress();}通过遍历原始数据并剥离城市节点下的children,我们得到一份干净的 2 级级联结构。Toggle 开关切换时,直接切换range的数据源即可。
3.3 TextPicker 集成
将数据接入 TextPicker,根据showDistrict状态动态选择 2 级或 3 级数据:
TextPicker({range:this.showDistrict?this.fullData:this.twoLevelData,selected:this.showDistrict?this.selectedIdx:[this.selectedIdx[0],this.selectedIdx[1]],value:this.selectedVal}).onChange((value:string|string[],index:number|number[])=>{this.onPickerChange(value,index);})关键设计点:2 级模式的selected数组长度是 2(省、市),3 级模式长度是 3(省、市、区)。切换模式时需要同步调整selected和value的数组长度。
3.4 选择结果展示
地址选择的结果展示在一个信息卡片中——显示当前选中的完整地址和对应的索引路径:
Column(){Row(){Text('📍').fontSize(22).margin({right:10})Text(this.addressText).fontSize(18).fontColor('#1a1a2e').fontWeight(FontWeight.Bold)}Row(){Text('层级:').fontSize(11).fontColor('#9999AA')Text(this.selectedIdx.join(' → ')).fontSize(11).fontColor('#9999AA')}.margin({top:8})}.width('100%').padding({left:Spacing.LG,right:Spacing.LG,top:14,bottom:14}).backgroundColor('#F0F6FF')卡片使用浅蓝底色(#F0F6FF)与页面其他区域区分,地址文本使用大号加粗字体凸显选择结果,索引路径作为辅助信息以灰色小字展示。
3.5 快捷预设
提供 6 个常用城市/区组合的快捷入口,让用户一键跳转到常见地址:
Row(){this.PresetBtn('北京 · 朝阳',()=>{this.applyPreset(0,2,0);})this.PresetBtn('北京 · 海淀',()=>{this.applyPreset(0,3,0);})this.PresetBtn('上海 · 浦东',()=>{this.applyPreset(1,0,0);})}点击任意预设按钮,调用applyPreset方法同时更新selectedIdx和selectedVal:
applyPreset(pIdx:number,cIdx:number,dIdx:number):void{this.selectedIdx=[pIdx,cIdx,dIdx];constprov=this.fullData[pIdx].text;constcity=this.fullData[pIdx].children![cIdx].text;constdist=this.fullData[pIdx].children![cIdx].children![dIdx].text;this.selectedVal=[prov,city,dist];this.updateAddress();}注意预设按钮使用@Builder方法提取为可复用组件:
@BuilderPresetBtn(label:string,onClick:()=>void){Text(label).fontSize(12).fontColor('#1677FF').fontWeight(FontWeight.Medium).padding({top:8,bottom:8,left:12,right:12}).borderRadius(8).backgroundColor('#EEF3FF').margin({right:8}).onClick(onClick)}3.6 层级切换
使用 Toggle 开关在 2 级和 3 级之间动态切换:
Toggle({type:ToggleType.Switch,isOn:this.showDistrict}).onChange((on:boolean)=>{this.showDistrict=on;this.selectedIdx=[0,0,0];if(on){this.selectedVal=['北京市','东城区','东华门街道'];}else{this.selectedVal=['北京市','东城区'];}this.updateAddress();})切换时重置选中位置到第一项,并根据新模式设置对应长度的 value 数组。
四、级联原理与自定义联动
TextPicker 的级联行为是自动的——当用户在第一列选择了一个新省份,组件内部会查找该省份的children并更新第二列选项。但这仅限于组件内部。
如果需要额外的自定义联动逻辑(如在选择结果卡中实时更新、触发搜索等),你需要在onChange回调中处理:
onPickerChange(value:string|string[],index:number|number[]):void{if(Array.isArray(value)){this.selectedVal=valueasstring[];}if(Array.isArray(index)){this.selectedIdx=indexasnumber[];}this.updateAddress();// 自定义逻辑:更新结果展示}这里的类型判断Array.isArray(value)是因为onChange的参数类型是联合类型string | string[]。在级联模式下,参数始终是数组类型(即使只有 2 级时也是 2 元数组)。
五、完整交互流程
- 初始状态:显示 3 级级联选择器,默认选中"北京市 - 东城区 - 东华门街道",结果卡片展示完整地址
- 手动选择:滑动第一列切换省份(例如从"北京市"到"广东省"),第二列自动变为广东省下的城市列表(广州市、深圳市、东莞市、佛山市),第三列相应更新
- 切换层级:关闭"显示街道/镇"开关,选择器变为 2 级(省 - 市),第三列消失
- 快捷预设:点击"深圳 · 南山"按钮,选择器自动跳转到"广东省 - 深圳市 - 南山区"
- 打开层级:重新打开开关,回到 3 级模式,继续精确到街道/镇级别
六、注意事项
6.1 数据完整性
级联数据必须是完整的树形结构——每个节点的children要么全部存在,要么全部不存在。不完整的结构(如部分城市有区、部分没有)可能导致级联行为异常。
6.2 索引范围
在 2 级和 3 级之间切换时,selected索引数组的长度必须与级数匹配。3 级时需要 3 个索引,2 级时需要 2 个。长度不匹配会导致运行时错误。
6.3 数据规模
TextPicker 会在内存中持有完整的级联数据。对于地址选择这类数据量适中的场景(数百个省市、数千个区),性能没有问题。但如果数据量极大(如数十万条),建议考虑异步加载方案。
6.4 与 DatePicker 的区别
TextPicker 和 DatePicker 虽然外观相似(都是滚轮选择),但用途完全不同:
- DatePicker 专用于日期选择,列固定为年/月/日,数据由系统生成
- TextPicker 是通用选择器,列数和数据完全由开发者定义
七、总结
TextPicker 是 ArkUI 中最灵活的选择器组件。从简单的单列选择(性别、血型)到复杂的多级级联(地址、行业),它都能胜任。其核心优势在于:
- 三种模式覆盖所有场景:单列简单、多列独立、级联联动
- 级联行为自动处理:开发者只需提供树形数据,无需手动实现联动逻辑
- 灵活的显示控制:通过 CSS 属性控制样式,适应不同设计需求
本文通过"地址选择器"这个完整的实战案例,覆盖了 TextPicker 级联模式的核心用法——从数据构建到选择器集成、从结果展示到快捷预设、从层级切换到联动逻辑。掌握了这些技术,你就能在任何需要层级化选择的场景中游刃有余。