鸿蒙 ArkTS 实战:搜索列表 城市实时过滤(示例 94)
引言搜索功能是现代应用中最高频的交互之一。从电商平台的商品搜索到通讯录的联系人查找从音乐 App 的歌曲检索到城市列表的地区筛选搜索框 列表的组合几乎出现在每一个需要数据检索的场景中。一个优秀的搜索体验需要满足三个核心要求实时响应用户输入即出结果、模糊匹配不需要精确输入也能找到、清晰反馈有结果时显示列表无结果时给出提示。示例 94 以「城市搜索」为业务场景实现了一个包含 20 个城市的可搜索列表。用户在搜索框中输入关键字列表实时过滤显示匹配的城市支持中文包含匹配如输入「北」可匹配「北京」搜索框旁边有「清空」按钮一键清除关键字列表上方显示匹配结果数量当没有匹配结果时显示「未找到匹配城市」的空状态提示。整个流程涉及TextInput的onChange实时监听、get计算属性的动态过滤、String.includes的包含匹配、String.toLowerCase的大小写不敏感处理、ListForEach的列表渲染、以及if/else条件渲染的空状态几乎涵盖了搜索列表功能的全部核心要点。这篇文章会严格按源码顺序先介绍应用的整体功能与布局结构再拆解实时搜索的核心逻辑接着逐段解读.ets源码中的数据定义、过滤算法、列表渲染、空状态处理然后分析get计算属性的特性、大小写不敏感匹配的技巧、条件渲染的策略最后给出运行操作指南、可扩展方向与常见问题调试技巧。读完后你不仅能看懂这一个城市搜索页面还能举一反三把它应用到联系人搜索、商品检索、文章查找等任何需要实时过滤的场景。1. 应用概述与功能「城市搜索」是一个面向数据检索场景的工具型页面交互路径清晰输入关键字 → 实时过滤列表 → 点击查看详情 → 可清空重置。页面自上而下分为五块区域顶部返回栏返回按钮 标题「城市搜索」搜索栏TextInput输入框 「清空」按钮结果统计文本「共 N 条结果」结果列表ListForEach渲染匹配的城市或空状态提示无匹配时显示底部提示文字「支持中文包含匹配如输入北可匹配北京」。1.1 核心功能清单实时搜索用户在搜索框中输入关键字时列表实时过滤无需点击搜索按钮。中文包含匹配支持中文关键字的部分匹配如输入「广」可匹配「广州」。大小写不敏感英文输入时自动转小写比较如输入「bei」可匹配「Beijing」如果数据中有英文。结果计数搜索框下方实时显示匹配结果数量。清空功能点击「清空」按钮一键清除搜索关键字恢复完整列表。空状态提示当没有匹配结果时显示「未找到匹配城市」的友好提示避免空白页面。点击查看点击列表项弹出 Toast 提示模拟查看详情的交互。1.2 技术要点一览整个示例用到的关键技术对「实时搜索列表」类页面很有代表性TextInput的onChange事件实时监听输入、get计算属性实现动态过滤每次访问时重新计算、String.trim()String.toLowerCase()的输入预处理、String.includes()的包含匹配、ListForEach的列表渲染、if/else条件分支处理有结果/无结果两种状态、以及Blank()弹性布局。把这些要点串起来就构成了一条完整的「输入监听 → 关键字预处理 → 数组过滤 → 条件渲染」的搜索数据流。2. 核心知识点在逐段读代码之前先把搜索列表页面承载的 ArkTS 核心知识讲清楚。2.1 get 计算属性与实时过滤ArkTS 中的get访问器可以定义计算属性——每次访问该属性时都会重新计算并返回结果getfilterList():string[]{constk:stringthis.keyword.trim().toLowerCase();if(k.length0){returnthis.cities;}constresult:string[][];for(leti0;ithis.cities.length;i){if(this.cities[i].toLowerCase().includes(k)){result.push(this.cities[i]);}}returnresult;}filterList是一个get计算属性它不存储数据而是在每次被访问时根据当前this.keyword的值动态计算过滤结果。当keyword为空时返回完整列表不为空时遍历所有城市将包含关键字的城市加入结果数组。计算属性的优势在于声明式——你只需要定义「结果是什么」不需要关心「何时重新计算」。ArkUI 的响应式系统会在keyword变化时自动重新调用filterList的 getter获取新的过滤结果并更新 UI。2.2 String.includes 包含匹配String.prototype.includes方法判断一个字符串是否包含另一个字符串this.cities[i].toLowerCase().includes(k)includes返回布尔值——true表示包含false表示不包含。与indexOf不同includes直接返回布尔值语义更清晰。例如北京.includes(北)→true上海.includes(北)→false广州.includes(广)→true2.3 trim toLowerCase 输入预处理搜索关键字在匹配前经过两步预处理constk:stringthis.keyword.trim().toLowerCase();trim()去掉关键字首尾的空白字符。避免用户不小心输入了空格导致匹配失败。toLowerCase()将关键字转为小写。同时城市名称也调用toLowerCase()实现大小写不敏感匹配。这种预处理确保了搜索的容错性——用户输入「 北京 」带空格或「BEIJING」大写都能正确匹配到「北京」。2.4 TextInput 的 onChange 实时监听TextInput组件的onChange事件在用户每次输入或删除字符时触发TextInput({placeholder:输入城市名称搜索,text:this.keyword}).onChange((v:string){this.keywordv;})onChange回调的参数v是输入框的当前完整文本。每次触发时将v赋值给this.keyword触发State变量变化进而触发filterList计算属性重新计算最终更新列表 UI。这就是「实时搜索」的实现原理——输入即触发无需额外的搜索按钮。text: this.keyword实现了双向绑定——当keyword被清空时如点击「清空」按钮输入框的文字也会同步清空。2.5 if/else 条件渲染搜索结果区域使用if/else条件渲染处理有结果和无结果两种状态if(this.filterList.length0){List(){ForEach(this.filterList,(city:string,idx:number){ListItem(){...}},...)}}else{Column(){Text(未找到匹配城市)...Text(请尝试输入其他关键字)...}}当filterList.length 0时渲染城市列表否则渲染空状态提示。if/else条件渲染是 ArkUI 中处理多状态 UI 的标准方式——只有条件为true的分支会被渲染到界面上另一个分支的组件不会创建。3. 源码逐段解析现在开始按源码顺序逐段解读index94.ets从导入声明到build方法完整展示搜索列表的实现细节。3.1 导入声明与组件声明import{router}fromkit.ArkUI;import{promptAction}fromkit.ArkUI;EntryComponentstruct Index94{导入router和promptAction声明页面入口组件Index94。3.2 城市数据与搜索状态Statecities:string[][北京,上海,广州,深圳,杭州,南京,成都,重庆,武汉,西安,天津,苏州,青岛,大连,厦门,长沙,郑州,昆明,沈阳,哈尔滨];Statekeyword:string;cities是包含 20 个城市的字符串数组覆盖了国内主要的一二线城市。keyword是搜索关键字初始为空串显示完整列表。注意cities使用了State修饰虽然在本示例中城市列表不会被修改但使用State确保了在需要动态更新列表时如从网络加载UI 能正确刷新。3.3 filterList 计算属性getfilterList():string[]{constk:stringthis.keyword.trim().toLowerCase();if(k.length0){returnthis.cities;}constresult:string[][];for(leti0;ithis.cities.length;i){if(this.cities[i].toLowerCase().includes(k)){result.push(this.cities[i]);}}returnresult;}filterList是整个搜索功能的核心。它的执行逻辑预处理关键字this.keyword.trim().toLowerCase()去除首尾空格并转小写。空关键字短路如果处理后的关键字长度为 0即输入框为空或只有空格直接返回完整城市列表无需遍历。遍历过滤遍历所有城市将每个城市名转小写后调用includes(k)检查是否包含关键字。如果包含加入结果数组。返回结果返回过滤后的城市数组。注意这里同时处理了城市名和关键字的大小写转换——this.cities[i].toLowerCase()和k都是小写确保匹配不受大小写影响。对于中文toLowerCase()不会有任何影响中文没有大小写之分所以这个处理对中文搜索完全透明。3.4 clearSearch 清空方法privateclearSearch():void{this.keyword;}clearSearch方法非常简洁——只需将keyword设为空串。由于keyword是State变量赋空串后会触发输入框文字清空text: this.keyword双向绑定、filterList重新计算返回完整列表、结果计数更新为 20、列表恢复完整显示。3.5 build 方法整体结构build(){Column(){// 顶部返回栏Row(){...}// 搜索栏Row(){...}// 结果统计Text(共 this.filterList.length 条结果)...// 结果列表或空状态if(this.filterList.length0){List(){...}}else{Column(){...}}// 底部提示Text(支持中文包含匹配...)...}.width(100%).height(100%).backgroundColor(#f2f3f5)}3.6 搜索栏Row(){TextInput({placeholder:输入城市名称搜索,text:this.keyword}).layoutWeight(1).height(42).backgroundColor(#ffffff).borderRadius(8).onChange((v:string){this.keywordv;})Button(清空).height(42).backgroundColor(#1a6cff).fontColor(Color.White).margin({left:10}).onClick((){this.clearSearch();})}.width(100%).padding({left:12,right:12})搜索栏使用Row布局左侧是TextInput输入框右侧是「清空」按钮TextInput使用layoutWeight(1)占据剩余空间高度 42vp白色背景圆角 8。text: this.keyword实现双向绑定。Button固定宽度蓝色背景左侧外边距 10vp 与输入框保持间距。layoutWeight(1)在这里很关键——它让TextInput自适应宽度无论屏幕多宽都能填满「清空」按钮之外的所有空间。3.7 结果统计Text(共 this.filterList.length 条结果).fontSize(13).fontColor(#888888).width(100%).padding({left:16,right:16,top:10,bottom:6})结果统计文本显示当前过滤后的城市数量。由于filterList是计算属性每次keyword变化时都会重新计算统计文本也会同步更新。例如输入「北」时显示「共 1 条结果」输入「州」时显示「共 3 条结果」广州、苏州、郑州。3.8 结果列表if(this.filterList.length0){List(){ForEach(this.filterList,(city:string,idx:number){ListItem(){Row(){Text(city).fontSize(16).fontColor(#333333)Blank()Text(点击查看).fontSize(12).fontColor(#aaaaaa)}.width(100%).height(48).padding({left:16,right:16}).onClick((){promptAction.showToast({message:点击了city});})}},(city:string,idx:number)cityidx.toString())}.width(100%).layoutWeight(1)}结果列表使用ListForEach渲染过滤后的城市。每个ListItem内部是一个Row左侧显示城市名16号深灰色字。右侧显示「点击查看」提示文字12号浅灰色字。Blank()弹性分隔将两个文本推到两端。点击列表项弹出 Toast 提示。ForEach的键值生成器使用city idx.toString()确保每个城市项有唯一标识避免 ArkUI diff 算法出现重用错误。List设置layoutWeight(1)占据剩余空间当城市较多时可以滚动浏览。3.9 空状态提示else{Column(){Text(未找到匹配城市).fontSize(15).fontColor(#aaaaaa)Text(请尝试输入其他关键字).fontSize(12).fontColor(#cccccc).margin({top:8})}.width(100%).layoutWeight(1).justifyContent(FlexAlign.Center)}当filterList.length 0时即搜索关键字没有匹配到任何城市渲染空状态提示。Column居中显示两行文字「未找到匹配城市」——15号浅灰色字主提示。「请尝试输入其他关键字」——12号更浅灰色字辅助提示上方间距 8vp。justifyContent(FlexAlign.Center)让内容垂直居中避免提示文字出现在页面顶部或底部影响视觉平衡。3.10 底部提示Text(支持中文包含匹配如输入北可匹配北京).fontSize(12).fontColor(#aaaaaa).margin({top:10,bottom:14})底部提示文字向用户说明搜索的匹配规则降低使用门槛。4. 交互流程详解4.1 实时搜索流程用户在搜索框中输入「广」TextInput的onChange回调触发参数v为广。回调执行this.keyword 广State变量更新。ArkUI 响应式系统检测到keyword变化标记依赖keyword的 UI 需要重新渲染。filterList计算属性被重新调用广.trim().toLowerCase()广遍历城市列表广州.includes(广)true广元.includes(广)true如果有的话返回[广州]。结果统计文本更新为「共 1 条结果」。List中的ForEach重新渲染只显示「广州」一项。4.2 清空搜索流程用户点击「清空」按钮clearSearch()方法执行this.keyword 。TextInput的text绑定更新输入框文字清空。filterList重新计算.trim().length 0返回完整城市列表。结果统计更新为「共 20 条结果」。列表恢复显示所有 20 个城市。4.3 空结果流程用户输入「xyz」不匹配任何城市this.keyword xyz。filterList计算所有城市的toLowerCase().includes(xyz)都为false返回空数组[]。结果统计更新为「共 0 条结果」。if (this.filterList.length 0)条件为false渲染else分支的空状态提示。列表区域显示「未找到匹配城市」居中提示。4.4 点击列表项流程用户点击某个城市ListItem的onClick回调触发。promptAction.showToast({ message: 点击了 city })弹出 Toast。Toast 持续约 2 秒后自动消失。5. UI 样式设计思路5.1 搜索栏布局搜索栏采用TextInputButton的水平排列layoutWeight(1)让输入框自适应宽度。这种布局在各种应用的搜索页面中非常常见用户可以快速输入关键字并一键清空。5.2 列表项布局每个列表项使用Row布局城市名在左、「点击查看」在右通过Blank()弹性分隔。这种两端对齐的布局让列表项看起来整洁有序同时右侧的提示文字引导用户进行下一步操作。5.3 空状态设计空状态提示使用居中布局两行文字大小不同15号和12号颜色深浅不同形成主次关系。这种设计比简单的「无结果」文字更友好给出了具体的操作建议「请尝试输入其他关键字」帮助用户修正搜索。5.4 结果计数结果计数位于搜索栏和列表之间用浅灰色小字显示。这个细节看似不起眼但极大提升了搜索体验——用户可以立即知道搜索返回了多少条结果决定是否需要修改关键字。6. 运行与测试6.1 运行步骤使用 DevEco Studio 打开项目。运行项目到模拟器或真机。在首页找到「城市搜索」示例入口点击进入。在搜索框中输入不同关键字观察列表实时过滤。点击「清空」按钮确认列表恢复完整。输入不匹配的关键字观察空状态提示。6.2 测试场景测试场景预期结果输入「北」显示「北京」共 1 条结果输入「州」显示「广州」「苏州」「郑州」共 3 条结果输入「海」显示「上海」共 1 条结果输入「xyz」显示「未找到匹配城市」空状态点击「清空」按钮输入框清空列表恢复 20 个城市输入空格「 」trim 后为空显示完整列表20 条点击列表项「北京」弹出 Toast「点击了北京」7. 可扩展方向7.1 拼音搜索当前只支持中文字符匹配。可以集成拼音转换库实现拼音搜索——输入「beijing」或「bj」也能匹配到「北京」。需要将城市名转为拼音后建立索引。7.2 搜索高亮在搜索结果中高亮显示匹配的关键字。例如输入「广」列表中「广州」的「广」字用红色或加粗显示。需要在Text组件中使用RichText或分段Text实现部分文字高亮。7.3 搜索历史记录用户最近搜索的关键字在搜索框为空时显示历史搜索列表。点击历史项可以直接搜索。需要使用Preferences持久化存储搜索历史。7.4 分组显示将搜索结果按首字母分组显示如 A 组安庆、B 组北京、包头等。需要在数据中添加拼音首字母字段并使用ForEach嵌套渲染分组标题和组内城市。7.5 网络搜索将城市数据改为从网络接口获取支持更大数据量的搜索。需要添加网络请求逻辑和加载状态提示。8. 常见问题与调试8.1 搜索不实时问题输入文字后列表没有立即更新需要点击其他地方才更新。排查确认TextInput的onChange回调中正确更新了this.keyword。检查filterList是否使用了get计算属性而非普通方法。如果用普通方法需要手动触发刷新。8.2 大小写敏感问题输入大写字母无法匹配小写数据。排查确认filterList中对关键字和数据都调用了toLowerCase()。检查是否有其他地方覆盖了keyword的值导致预处理失效。8.3 空格导致无结果问题输入「 北京」带空格没有匹配结果。排查确认filterList中对关键字调用了trim()去除首尾空格。如果需要在数据中间匹配空格不要对数据调用trim()。8.4 列表不滚动问题搜索结果较多时列表无法滚动。排查确认List设置了layoutWeight(1)让它占据剩余空间。如果List没有固定高度或layoutWeight内容超出时不会滚动。检查父容器Column的高度是否为100%。8.5 ForEach key 冲突问题列表渲染出现重复项或项错乱。排查检查ForEach的键值生成器。示例使用city idx.toString()如果城市名重复且索引相同key 会冲突。如果数据中有重复项使用更复杂的 key 策略如city _ idx.toString()。9. 技术总结示例 94 的搜索列表展示了实时搜索功能的完整实现。通过这个示例我们可以总结出搜索列表的几个关键范式get 计算属性用get定义过滤逻辑声明式地表达「结果是什么」ArkUI 自动处理何时重新计算。trim toLowerCase对关键字和数据都做预处理确保搜索的容错性和大小写不敏感。includes 包含匹配String.includes是最简洁的包含判断方法直接返回布尔值。if/else 条件渲染用条件分支处理有结果和无结果两种 UI 状态避免空白页面。layoutWeight 自适应TextInput使用layoutWeight(1)自适应宽度List使用layoutWeight(1)占满剩余空间实现滚动。掌握了这些范式后就可以轻松地将实时搜索功能应用到联系人、商品、文章等各种数据检索场景中。搜索作为应用中最核心的交互之一是每个鸿蒙开发者必须熟练掌握的功能模式。