在移动端表单中当选项数量众多且具有层级关系时单选按钮和复选框都不再适用。省市区地址选择、商品规格选择、身高体重选择——这些场景需要一种能容纳大量选项、支持层级联动、操作直观的组件。这就是 TextPicker——滚轮式文本选择器。HarmonyOS NEXT ArkUI 提供了 TextPicker 组件——一个可滚动的文本选择器用户通过在滚轮上滑动来选择选项。本文将通过构建一个省市区三级地址选择器深入讲解 TextPicker 的 API、多列联动机制和级联数据管理。关键词HarmonyOS、ArkUI、TextPicker、滚轮选择器、地址选择、三级联动、级联数据一、TextPicker 组件 API1.1 基本用法TextPicker({range:[选项A,选项B,选项C],selected:0}).onChange((value:string|string[],index:number|number[]){// value: 当前选中的文本// index: 当前选中的索引console.log(选中:,(valueasstring),索引:,(indexasnumber));})核心参数与属性参数/属性类型说明rangestring[]|Resource数据源数组TextPicker 展示的选项列表selectednumber默认选中的索引0-basedvaluestring默认选中的值与selected二选一.onChange()callback选中的值变化时的回调1.2 onChange 回调的类型TextPicker 的回调签名与其他组件不同——它的onChange参数类型是(value: string | string[], index: number | number[]) void。这是因为 TextPicker 支持多列模式通过CascadePicker或MultiPicker此时value和index都是数组。但在单列模式下如 Demo 中的三个独立 TextPickervalue始终是stringindex始终是number。因此在回调中使用类型断言.onChange((value:string|string[],index:number|number[]){this.onProvinceChange(valueasstring,indexasnumber);})这是一个容易忽视的 API 细节——如果你给回调参数标注(value: string, index: number)编译器会报错因为形参类型必须兼容string | string[]。1.3 动态 range 更新TextPicker 的range是一个构造参数而非属性方法这意味着你无法直接通过链式调用更新数据源。但当你用State变量驱动range通过方法返回值时每当State变化range会自动重新计算。Demo 中三个 TextPicker 的range都通过方法动态生成// 省份 range 固定——直接返回全部省份名TextPicker({range:this.getProvinceNames(),selected:this.provinceIndex})// 城市 range 动态——根据当前选中的省份返回该省的城市TextPicker({range:this.getCityNames(),selected:this.cityIndex})// 区县 range 动态——根据当前选中的城市返回该市的区县TextPicker({range:this.getDistrictNames(),selected:this.districtIndex})当provinceIndex变化时getCityNames()返回新省份的城市列表城市 TextPicker 自动更新。同理当cityIndex变化时getDistrictNames()返回新城市的区县列表。1.4 索引越界保护由于selected参数始终使用当前的cityIndex和districtIndex当上级选项变化时如切换省份下级的selected可能指向一个不再存在的索引。例如原省份有 3 个城市cityIndex 2新省份只有 2 个城市cityIndex 2就越界了。解决方案是在上级onChange回调中重置下级索引onProvinceChange(value:string,index:number):void{this.provinceIndexindex;this.cityIndex0;// 重置城市索引this.districtIndex0;// 重置区县索引}onCityChange(value:string,index:number):void{this.cityIndexindex;this.districtIndex0;// 重置区县索引}每次切换省份城市和区县都回到第一个选项每次切换城市区县回到第一个选项。这种级联重置确保了索引用不越界。二、三级地址选择器的整体设计2.1 页面架构AddressPickerPage ├── 标题栏 — 地址选择 历史记录数 ├── 当前选择预览卡片 │ ├── 完整地址省 市 区 │ └── 已保存状态标签 ├── 三级滚轮选择区三个 TextPicker 并排 │ ├── 省份列 │ ├── 城市列联动更新 │ └── 区县列联动更新 ├── 确认选择按钮 └── 历史记录列表含删除按钮2.2 数据类设计Demo 中定义了两个类classAddressNode{name:string;children:AddressNode[];constructor(name:string,children:AddressNode[]){this.namename;this.childrenchildren;}}classSelectedAddress{province:string;city:string;district:string;constructor(province:string,city:string,district:string){this.provinceprovince;this.citycity;this.districtdistrict;}getFullAddress():string{returnthis.province.concat( ).concat(this.city).concat( ).concat(this.district);}equals(other:SelectedAddress):boolean{returnthis.provinceother.provincethis.cityother.citythis.districtother.district;}}AddressNode是树形数据节点。每个节点有一个name名称和一个children数组子节点。省份是根节点城市是子节点区县是孙节点。区县的children为空数组[]。SelectedAddress表示用户确认后的完整地址。getFullAddress()返回空格分隔的完整地址字符串。equals()用于去重——当用户反复确认同一地址时只保留一条记录。2.3 地址数据Demo 包含 6 个省/直辖市共计 16 个城市、40 个区县覆盖了中国主要的区域省份城市区县数北京市北京市4朝阳/海淀/西城/东城上海市上海市4浦东/徐汇/静安/黄浦广东省广州/深圳/东莞333浙江省杭州/宁波/温州332江苏省南京/苏州/无锡332四川省成都/绵阳42直辖市北京、上海的数据结构比较特殊——省份和城市同名“北京市下的北京市”。这种设计真实反映了行政区划的实际情况直辖市下不设地级市直接辖区县。2.4 名称提取方法三个方法负责从AddressNode树中提取当前层级的名称数组getProvinceNames()— 总是返回所有省份名getProvinceNames():string[]{letnames:string[][];letdatathis.getAddressData();for(leti0;idata.length;i){namesnames.slice().concat(data[i].name);}returnnames;}getCityNames()— 返回当前选中省份的城市名getCityNames():string[]{letnames:string[][];letdatathis.getAddressData();letprovincedata[this.provinceIndex];if(province){for(leti0;iprovince.children.length;i){namesnames.slice().concat(province.children[i].name);}}returnnames;}getDistrictNames()— 返回当前选中城市的区县名getDistrictNames():string[]{letnames:string[][];letdatathis.getAddressData();letprovincedata[this.provinceIndex];if(province){letcityprovince.children[this.cityIndex];if(city){for(leti0;icity.children.length;i){namesnames.slice().concat(city.children[i].name);}}}returnnames;}注意所有方法都使用names.slice().concat(...)而非names.push(...)——这是 ArkTS 的不可变数据惯例。虽然names是局部变量非State但保持一致性有助于避免误用。三、三级联动机制3.1 联动逻辑三级联动是地址选择器的核心。Demo 通过State变量链实现联动provinceIndex (省份索引) │ ▼ onChange → 更新 cityIndex0, districtIndex0 cityIndex (城市索引) │ ▼ onChange → 更新 districtIndex0 districtIndex (区县索引)这三个State变量形成了一个简短的依赖链。当上游索引变化时下游索引自动重置为 0确保数据一致性。关键代码onProvinceChange(value:string,index:number):void{this.provinceIndexindex;this.cityIndex0;this.districtIndex0;this.savedfalse;}onCityChange(value:string,index:number):void{this.cityIndexindex;this.districtIndex0;this.savedfalse;}onDistrictChange(value:string,index:number):void{this.districtIndexindex;this.savedfalse;}saved false的作用用户切换选项后之前可能的已保存状态失效预览区回到请选择省/市/区的提示状态。3.2 三列布局三个 TextPicker 在Row中水平并排每个占等宽Row(){Column(){Text(省份).fontSize(11).fontColor(#BBBBCC).margin({bottom:6})TextPicker({range:this.getProvinceNames(),selected:this.provinceIndex}).onChange(...)}.layoutWeight(1).alignItems(HorizontalAlign.Center)Column(){Text(城市).fontSize(11).fontColor(#BBBBCC).margin({bottom:6})TextPicker({range:this.getCityNames(),selected:this.cityIndex}).onChange(...)}.layoutWeight(1).alignItems(HorizontalAlign.Center)Column(){Text(区县).fontSize(11).fontColor(#BBBBCC).margin({bottom:6})TextPicker({range:this.getDistrictNames(),selected:this.districtIndex}).onChange(...)}.layoutWeight(1).alignItems(HorizontalAlign.Center)}每列包含一个标签11sp 灰色文字和一个 TextPicker。.layoutWeight(1)三等分父容器宽度。每列居中对齐。在典型手机屏幕360dp上每列约 120dp——对 TextPicker 的滚轮来说足够显示中文选项。四、地址确认与历史记录4.1 确认选择用户在三列滚轮中选好省市区后点击确认选择按钮confirmAddress():void{letaddrthis.getCurrentAddress();// 去重检查letexistsfalse;for(leti0;ithis.history.length;i){if(this.history[i].equals(addr)){existstrue;break;}}if(exists){promptAction.showToast({message:该地址已保存,duration:1500});return;}this.historythis.history.slice().concat(addr);this.savedtrue;promptAction.showToast({message:地址已保存,duration:1500});}流程分四步调用getCurrentAddress()获取当前三省市区名称组装为SelectedAddress遍历history检查是否重复equals()比较省市区三个字段如果重复Toast 提示该地址已保存不重复添加如果不重复通过slice().concat(addr)创建新数组更新State history4.2 预览区状态切换预览区根据saved状态展示不同内容Column(){Text(当前选择).fontSize(11).fontColor(#BBBBCC).margin({bottom:6})Text(this.saved?this.getCurrentAddress().getFullAddress():请选择省/市/区).fontSize(20).fontColor(this.saved?#1a1a2e:#BBBBCC).fontWeight(this.saved?FontWeight.Bold:FontWeight.Normal)if(this.saved){Text(已保存到历史记录).fontSize(11).fontColor(#52C41A).margin({top:6})}}未确认时saved false显示灰色请选择省/市/区字号 20sp常规字重已确认后saved true显示深色完整地址如广东省 深圳市 南山区加粗下方显示绿色已保存到历史记录标签注意用户切换任何 TextPicker 时saved会被重置为false预览区回到提示状态。这确保已保存标签只在当前地址确实已保存后才显示。4.3 历史记录列表if(this.history.length0){Column(){Row(){Text(历史记录).fontSize(14).fontColor(#1a1a2e).fontWeight(FontWeight.Medium)Blank()Text(左滑删除).fontSize(11).fontColor(#CCCCDD)}.width(100%).margin({bottom:10})ForEach(this.history,(addr:SelectedAddress,idx:number){Row(){Column(){Text(addr.getFullAddress()).fontSize(14).fontColor(#1a1a2e).fontWeight(FontWeight.Medium)Text(已保存).fontSize(11).fontColor(#CCCCDD).margin({top:2})}.alignItems(HorizontalAlign.Start).layoutWeight(1)Text(删除).fontSize(12).fontColor(#FF4D4F).padding({top:5,bottom:5,left:10,right:10}).borderRadius(12).backgroundColor(#FFF1F0).onClick((){this.deleteHistory(idx);})}.width(100%).padding({top:12,bottom:12})if(idxthis.history.length-1){Divider().height(1).color(#F8F9FA)}},(addr:SelectedAddress,idx:number)idx.toString())}.width(100%).padding(Spacing.LG).backgroundColor(#FFFFFF).borderRadius(BorderRadius.LG).margin({...})}每条历史记录显示完整地址 已保存副标签 红色删除按钮。记录间有浅灰分割线。当history.length 0时整个历史记录区域不渲染。4.4 删除历史deleteHistory(index:number):void{letnewHistory:SelectedAddress[][];for(leti0;ithis.history.length;i){if(i!index){newHistorynewHistory.slice().concat(this.history[i]);}}this.historynewHistory;}通过遍历跳过指定索引来创建新数组。头部的历史记录计数“N 条记录”自动更新。五、预览区与滚轮区的视觉设计5.1 预览卡片预览区使用白色圆角卡片内边距 20dp。地址文字使用 20sp 字号——比一般正文大不少让用户清楚地看到当前选择结果。选择前后颜色和字重的对比强化了已确认vs未确认的状态差异。5.2 滚轮区域三个 TextPicker 并排在一个白色圆角卡片中。每列上方有灰色标签标明省份“城市”“区县”。TextPicker 本身的滚动交互由系统处理——用户上下滑动滚轮选择选项选中项居中高亮显示。三列并排的设计参考了 iOS 原生地址选择器的经典布局——一眼就能看到省市区三个层级无需额外的步骤切换。六、交互流程演示6.1 默认状态进入页面三个 TextPicker 分别显示第一个省份北京市、第一个城市、第一个区县。预览区显示请选择省/市/区。标题栏显示0 条记录。6.2 选择地址在省份 TextPicker 上向下滑动选择广东省。城市 TextPicker 自动更新为广东省的城市列表广州/深圳/东莞区县 TextPicker 自动更新为第一个城市的区县。在城市 TextPicker 上滑动选择深圳市。区县 TextPicker 自动更新为深圳市的区县列表南山/福田/罗湖。在区县 TextPicker 上滑动选择南山区。预览区仍然显示请选择省/市/区因为尚未确认。6.3 确认保存点击确认选择按钮。Toast 提示地址已保存。预览区变为广东省 深圳市 南山区深色加粗下方显示绿色已保存到历史记录。标题栏变为1 条记录。历史记录区域出现一条记录。6.4 再次选择在省份 TextPicker 切换到浙江省。城市和区县自动重置为第一个。选中杭州市→西湖区再次点击确认选择。标题栏变为2 条记录。历史记录区域出现两条记录。如果再次确认广东省 深圳市 南山区Toast 提示该地址已保存不会重复添加。6.5 删除记录点击第一条历史记录右侧的红色删除按钮。该记录消失。标题栏更新为1 条记录。七、级联选择器的扩展思路本文构建的三级地址选择器是一个标准模板可以轻松扩展到其他级联场景商品规格选择品类 → 品牌 → 型号。例如手机 → Apple → iPhone 16 Pro。组织架构选择公司 → 部门 → 小组。例如某某集团 → 技术部 → 前端组。课程目录选择学科 → 章节 → 知识点。例如数学 → 高等数学 → 微积分。只需替换AddressNode树的数据内容其余联动逻辑完全不变。八、总结本文通过三级地址选择器这个实战案例全面讲解了 ArkUI TextPicker 滚轮选择器的使用方法。核心知识点包括TextPicker 基础 APIrange数据源 selected默认索引 onChange回调注意参数类型string | string[]树形数据结构AddressNode递归定义省份→城市→区县的三级关系级联联动机制上游索引变化 → 重置下游索引 →range动态重新计算三列并排布局RowlayoutWeight(1)三等分 TextPicker每列独立索引越界保护onChange回调中重置下级索引为 0地址确认与去重SelectedAddress.equals()防止重复保存历史记录管理slice().concat()不可变添加 过滤删除TypeScript 类型细节onChange回调参数必须兼容string | string[]和number | number[]滚轮选择器是移动端处理大量选项的最佳方案。它比下拉菜单承载更多选项滚轮可无限滚动比单选按钮更节省空间。TextPicker 的级联模式让开发者可以构建多层级的选项关系让用户在几十上百个选项中快速定位目标——这是地址选择、商品筛选、组织导航等场景中不可或缺的交互模式。