当前位置: 首页 > news >正文

HarmonyOS 实战:用 List 与 AlphabetIndexer 打造高效城市选择功能

HarmonyOS 实战:用 List 与 AlphabetIndexer 打造高效城市选择功能

在移动应用开发中,城市选择功能是很多 App 的必备模块。想象一下,当用户需要从数百个城市中找到自己所在的城市时,如果没有高效的导航方式,体验会有多糟糕。今天我将带大家实现一个 HarmonyOS 平台上的城市选择器,通过 List 与 AlphabetIndexer 的联动,让用户轻松找到目标城市。

在这里插入图片描述

功能需求分析

我们需要实现的城市选择功能应该包含这些核心特性:

  • 展示历史选择城市,方便用户快速访问

  • 提供热门城市快捷入口

  • 按字母顺序分组展示所有城市

  • 右侧字母索引条,支持点击快速跳转

  • 列表滚动时自动同步索引位置

这种交互模式在通讯录、词典等应用中也非常常见,掌握了这个技巧可以举一反三。

核心组件介绍

实现这个功能我们主要依赖 HarmonyOS 的两个核心组件:

List 组件:作为容器展示大量数据,支持分组展示和滚动控制,通过 Scroller 可以精确控制滚动位置。

AlphabetIndexer 组件:字母索引条,支持自定义字母数组和选中样式,通过 onSelect 事件可以监听用户选择的索引位置。

这两个组件的联动是实现功能的关键,也是最容易出现问题的地方。

实现步骤详解

1. 数据结构定义

首先我们需要定义城市数据的结构,以及存储各类城市数据:

// 定义城市分组数据结构interface BKCityContent {  initial: string        // 字母首字母  cityNameList: string\[] // 该字母下的城市列表}// 组件内部数据定义@State hotCitys: string\[] = \['北京', '上海', '广州', '深圳', '天津', ...]@State historyCitys: string\[] = \['北京', '上海', '广州', ...]@State cityContentList: BKCityContent\[] = \[  { initial: 'A', cityNameList: \['阿拉善', '鞍山', '安庆', ...] },  { initial: 'B', cityNameList: \['北京', '保定', '包头', ...] },  // 其他字母分组...]

2. 索引数组构建

索引数组需要包含特殊分组(历史、热门)和所有城市首字母,我们在页面加载时动态生成:

@State arr: string\[] = \[]scroller: Scroller = new Scroller()aboutToAppear() {&#x20; // 先添加特殊分组标识&#x20; this.arr.push("#", "🔥")  // #代表历史,🔥代表热门&#x20; // 再添加所有城市首字母&#x20; for (let index = 0; index < this.cityContentList.length; index++) {&#x20;   const element = this.cityContentList\[index];&#x20;   this.arr.push(element.initial)&#x20; }}

3. 列表 UI 构建

使用 List 组件构建主内容区,包含历史城市、热门城市和按字母分组的城市列表:

List({ scroller: this.scroller }) {&#x20; // 历史城市分组&#x20; ListItemGroup({ header: this.header("历史") }) {&#x20;   ListItem() {&#x20;     Flex({ wrap: FlexWrap.Wrap }) {&#x20;       ForEach(this.historyCitys, (item: string) => {&#x20;         Text(item)&#x20;           .width("33.33%")&#x20;           .margin({ top: 20, bottom: 20 })&#x20;           .textAlign(TextAlign.Center)&#x20;       })&#x20;     }&#x20;   }&#x20; }&#x20; // 热门城市分组&#x20; ListItemGroup({ header: this.header("热门") }) {&#x20;   // 结构类似历史城市...&#x20; }&#x20; // 字母分组城市&#x20; ForEach(this.cityContentList, (item: BKCityContent) => {&#x20;   ListItemGroup({ header: this.header(item.initial) }) {&#x20;     ForEach(item.cityNameList, (item2: string) => {&#x20;       ListItem() {&#x20;         Text(item2)&#x20;           .fontSize(20)&#x20;       }&#x20;     })&#x20;   }&#x20; })}.width("100%").backgroundColor(Color.White)

4. 索引器 UI 构建

在列表右侧添加 AlphabetIndexer 组件作为索引条:

AlphabetIndexer({ arrayValue: this.arr, selected: this.current })&#x20; .itemSize(20)&#x20; .font({ size: "20vp" })&#x20; .selectedFont({ size: "20vp" })&#x20; .height("100%")&#x20; .autoCollapse(false)&#x20; .onSelect(index => {&#x20;   this.current = index&#x20;   // 点击索引时滚动列表&#x20;   this.scroller.scrollToIndex(index)&#x20; })

解决联动核心问题

很多开发者在实现时会遇到一个问题:索引器与列表位置不匹配。这是因为 List 的分组索引和 AlphabetIndexer 的索引需要正确映射。

原代码中的问题在于滚动回调处理不正确,我们需要修正这个逻辑:

// 正确的列表滚动回调处理.onScrollIndex((start) => {&#x20; // 计算当前滚动到的分组对应的索引器位置&#x20; if (start === 0) {&#x20;   this.current = 0; // 历史分组对应索引0&#x20; } else if (start === 1) {&#x20;   this.current = 1; // 热门分组对应索引1&#x20; } else {&#x20;   // 字母分组从索引2开始&#x20;   this.current = start - 2 + 2;&#x20;&#x20; }})

更好的解决方案是创建一个映射数组,明确记录每个索引器项对应的列表分组索引:

// 定义映射关系数组private listIndexMap: number\[] = \[]aboutToAppear() {&#x20; // 构建索引映射:索引器索引 -> 列表分组索引&#x20; this.listIndexMap = \[0, 1]  // 历史在列表第0组,热门在列表第1组&#x20; this.cityContentList.forEach((item, index) => {&#x20;   this.listIndexMap.push(index + 2)  // 字母分组从列表第2组开始&#x20; })}// 使用映射数组处理滚动.onScrollIndex((start) => {&#x20; const index = this.listIndexMap.indexOf(start)&#x20; if (index !== -1) {&#x20;   this.current = index&#x20; }})// 索引器选择时也使用映射数组.onSelect((index: number) => {&#x20; this.current = index&#x20; const listIndex = this.listIndexMap\[index]&#x20; if (listIndex !== undefined) {&#x20;   this.scroller.scrollToIndex(listIndex, true)&#x20; }})

这种映射方式更灵活,即使后续添加新的分组类型也不容易出错。

完整优化代码

结合以上优化点,我们的完整代码应该是这样的(包含 UI 美化和交互优化):

interface BKCityContent {&#x20; initial: string&#x20; cityNameList: string\[]}@Component@Entrystruct CitySelector {&#x20; @State isShow: boolean = true&#x20; @State selectedCity: string = ""&#x20; @State currentIndex: number = 0&#x20;&#x20;&#x20; // 城市数据定义...&#x20; hotCitys: string\[] = \['北京', '上海', '广州', ...]&#x20; historyCitys: string\[] = \['北京', '上海', ...]&#x20; cityContentList: BKCityContent\[] = \[/\* 城市数据 \*/]&#x20;&#x20;&#x20; @State indexArray: string\[] = \[]&#x20; scroller: Scroller = new Scroller()&#x20; private listIndexMap: number\[] = \[]&#x20; aboutToAppear() {&#x20;   // 构建索引数组和映射关系&#x20;   this.indexArray = \["#", "🔥"]&#x20;   this.listIndexMap = \[0, 1]&#x20;  &#x20;&#x20;   this.cityContentList.forEach((item, index) => {&#x20;     this.indexArray.push(item.initial)&#x20;     this.listIndexMap.push(index + 2)&#x20;   })&#x20; }&#x20; // 标题构建器&#x20; @Builder header(title: string) {&#x20;   Text(title)&#x20;     .fontWeight(FontWeight.Bold)&#x20;     .fontColor("#666666")&#x20;     .fontSize(16)&#x20;     .padding({ left: 16, top: 12, bottom: 8 })&#x20; }&#x20; // 城市网格构建器(用于历史和热门城市)&#x20; @Builder cityGrid(cities: string\[]) {&#x20;   Flex({ wrap: FlexWrap.Wrap }) {&#x20;     ForEach(cities, (item: string) => {&#x20;       Text(item)&#x20;         .padding(12)&#x20;         .margin(6)&#x20;         .backgroundColor("#F5F5F5")&#x20;         .borderRadius(6)&#x20;         .onClick(() => {&#x20;           this.selectedCity = item&#x20;         })&#x20;     })&#x20;   }&#x20;   .padding(10)&#x20; }&#x20; build() {&#x20;   Column() {&#x20;     // 顶部标题栏&#x20;     Row() {&#x20;       Text(this.selectedCity ? \`已选: \${this.selectedCity}\` : "选择城市")&#x20;         .fontSize(18)&#x20;         .fontWeight(FontWeight.Bold)&#x20;     }&#x20;     .padding(16)&#x20;     .width("100%")&#x20;    &#x20;&#x20;     // 主内容区&#x20;     Stack({ alignContent: Alignment.End }) {&#x20;       // 城市列表&#x20;       List({ scroller: this.scroller }) {&#x20;         // 历史城市分组&#x20;         ListItemGroup({ header: this.header("历史") }) {&#x20;           ListItem() { this.cityGrid(this.historyCitys) }&#x20;         }&#x20;        &#x20;&#x20;         // 热门城市分组&#x20;         ListItemGroup({ header: this.header("热门") }) {&#x20;           ListItem() { this.cityGrid(this.hotCitys) }&#x20;         }&#x20;        &#x20;&#x20;         // 字母分组城市&#x20;         ForEach(this.cityContentList, (item: BKCityContent) => {&#x20;           ListItemGroup({ header: this.header(item.initial) }) {&#x20;             ForEach(item.cityNameList, (city: string) => {&#x20;               ListItem() {&#x20;                 Text(city)&#x20;                   .padding(16)&#x20;                   .width("100%")&#x20;                   .onClick(() => { this.selectedCity = city })&#x20;               }&#x20;             })&#x20;           }&#x20;         })&#x20;       }&#x20;       .onScrollIndex((start) => {&#x20;         const index = this.listIndexMap.indexOf(start)&#x20;         if (index !== -1) {&#x20;           this.currentIndex = index&#x20;         }&#x20;       })&#x20;      &#x20;&#x20;       // 字母索引器&#x20;       AlphabetIndexer({&#x20;&#x20;         arrayValue: this.indexArray,&#x20;&#x20;         selected: this.currentIndex&#x20;&#x20;       })&#x20;       .itemSize(24)&#x20;       .selectedFont({ color: "#007AFF" })&#x20;       .height("90%")&#x20;       .autoCollapse(false)&#x20;       .onSelect((index: number) => {&#x20;         this.currentIndex = index&#x20;         const listIndex = this.listIndexMap\[index]&#x20;         if (listIndex !== undefined) {&#x20;           this.scroller.scrollToIndex(listIndex, true)&#x20;         }&#x20;       })&#x20;       .padding({ right: 8 })&#x20;     }&#x20;     .flexGrow(1)&#x20;   }&#x20;   .width("100%")&#x20;   .height("100%")&#x20;   .backgroundColor("#F9F9F9")&#x20; }}

功能扩展建议

基于这个基础功能,你还可以扩展更多实用特性:

  1. 城市搜索功能:添加搜索框,实时过滤城市列表

  2. 选择动画:为城市选择添加过渡动画,提升体验

  3. 定位功能:调用定位 API,自动推荐当前城市

  4. 数据持久化:保存用户的历史选择,下次打开时恢复

  5. 样式主题:支持深色 / 浅色模式切换

总结

通过本文的讲解,我们学习了如何使用 HarmonyOS 的 List 和 AlphabetIndexer 组件实现高效的城市选择功能。核心要点是理解两个组件的工作原理,建立正确的索引映射关系,实现双向联动。

这种列表加索引的模式在很多场景都能应用,掌握后可以显著提升应用中大数据列表的用户体验。希望本文对你有所帮助,如果你有更好的实现方式,欢迎在评论区交流讨论!

http://www.xdnf.cn/news/1302265.html

相关文章:

  • 【Java】HashMap的详细介绍
  • PCA降维全解析:从原理到实战
  • JAVA文件管理系统:如何玩转文件操作
  • CUDA中的基本概念
  • Scikit-learn (sklearn) 库详细介绍
  • 869. 重新排序得到 2 的幂
  • iSCSI 服务详解:配置与远程存储
  • 「iOS」————UITableView性能优化
  • PaddleOCR从小红书视频中提取字幕并生成思维导图
  • VUE+SPRINGBOOT从0-1打造前后端-前后台系统-一分钟音频转文字
  • Spring WebFlux 性能优化实践指南
  • 金融项目高可用分布式TCC-Transaction(开源框架)
  • 基于RobustVideoMatting(RVM)进行视频人像分割(torch、onnx版本)
  • 力扣 —— 二分查找
  • [优选算法专题二滑动窗口——无重复字符的最长子串]
  • docker 安装 使用
  • QT在Widget类下的四种QPushbutton的信号与槽的连接方式
  • Python中推导式和表达式
  • QT(事件)
  • 【机器学习深度学习】客观评估训练程度
  • AIoT浪潮之巅:AI如何赋能边缘物联网,解锁三大核心潜能
  • Spring中存在两个相同的Bean是否会报错?
  • Java 大视界 -- Java 大数据在智能教育虚拟学习环境构建与学习体验增强中的应用(399)
  • STM32F103C8T6学习——直接存储器访问(DMA)标准库实战3(ADC数据采集+DMA回传)
  • 开始回溯的学习
  • I/O多路复用特性与实现
  • 【学习嵌入式day-25-线程】
  • 扣子(Coze),开源了!Dify 天塌了
  • 无人机智能跟踪模块设计与运行分析
  • Mac Mysql 卸载