简介Android城市选择器是一套面向Android开发者的可复用城市选择组件模仿美团城市选择界面适合电商、旅游、社交和生活服务类应用快速加入城市切换功能。压缩包共285个文件、约908KB包含164个class、31个java源码、40个xml布局与资源、22个png图片以及json配置、jar依赖和db数据等覆盖界面资源、定位逻辑与模块调用所需文件目录结构便于按功能查找。借助高德地图SDK完成地理位置定位组件以独立模块和简洁API提供选择回调开发者可参考源码调整城市数据、UI样式和交互细节。已有931人学习下载适合需要快速集成城市选择能力或研究仿美团界面实现的初中级Android开发者参考。1. 城市选择器不是“选个城市”一个被低估的 Android 交互组件做 App 的都遇到过这种需求用户注册要选城市、电商下单要填收货地、租房按城市筛房源。看起来只是点几下屏幕的事真动手写才发现一堆麻烦——城市数据从哪儿来拼音索引和首字母悬浮怎么对齐定位到的城市和用户手选的冲突怎么办我见过不少项目直接在业务代码里塞一个AlertDialog加几个字符串第二年新来的人维护时差点把键盘摔了。Android 城市选择器就是在这种背景下被反复造轮子的组件。这份资源打包了一套可直接集成到 Android 工程里的城市选择器核心能力包括城市列表、热门城市、字母索引侧栏、定位城市展示和关键词过滤。它的价值不在于算法多复杂而在于把交互细节和数据组织方式整理成一套结构让业务层只关心“拿到 cityId 和 cityName”不用去管 UI 状态怎么流转。适合谁正在做电商、出行、本地生活类模块的 Android 开发者尤其是要在短期内同时处理定位选择、手动搜索和首字母快速定位三种入口的同学。2. 界面结构与索引机制先看懂城市列表是怎么拼出来的2.1 列表数据的三种视图形态城市选择器的界面在用户眼里是一根列表在代码里其实是三种视图形态复用同一份数据源首字母索引视图A-Z 排序带吸附头、热门城市网格固定九宫格或两行网格、搜索结果视图关键词匹配后的扁平列表。这三者不是三套数据而是同一份ListCity在不同状态下的投影。常见做法是建一个CityGroup模型每个分组持有首字母和该字母下的城市集合public class CityGroup { public String letter; // 首字母如 B public ListCity cities; // 该字母下的城市 public boolean isHot; // 是否为热门城市分组 public CityGroup(String letter, ListCity cities) { this.letter letter; this.cities cities; } }letter不参与业务逻辑只是渲染节头部的标识cities是实际可点击项。热门分组通过isHot标记区分渲染时换成网格布局。用分组结构的好处是侧栏索引的实现变得很直接——侧栏字母列表正是从letter字段派生的两者天然同步不会出现侧栏有 Z 而列表里没有 Z 开头的城市这种“翻车”情况。2.2 拼音首字母的生成与降级策略城市显示名多半是“杭州”“乌鲁木齐”这类中文界面要按 A-Z 排序就得拿到拼音首字母。这里有个玄学问题Android 自带的Collator只负责中文排序按拼音或笔画取决于 Locale不直接给首字母。所以实际工程里通常两条路第一条是数据预处理把每个城市的首字母写死在 JSON 或数组中{name:杭州,pinyin:hangzhou,letter:H}。第二条是运行时用第三方拼音库转换。我一般倾向于第一条原因是体积换确定性城市总量撑死几百条硬编码或预装进 assets 也就几十 KB而拼音库要处理多音字比如“长安”的 cháng 和 zhǎng、“厦门”的 xià 和 shà运行时转换容易翻车。如果确实要用运行时转换必须写降级逻辑——遇到转不出首字母的极端情况把城市归到#分组而不是抛异常或落到空分组里private String resolveLetter(City city) { String py city.getPinyin(); if (py null || py.trim().isEmpty()) { return #; // 无法识别首字母时统一归档保证列表完整 } char c py.charAt(0); if (c a c z) { return String.valueOf(c).toUpperCase(Locale.ROOT); } return #; }这里Locale.ROOT别改成Locale.CHINA否则在某些机型上可能产生带区域变体的字符后续做equals比较时对不上。这是血泪经验换来的一行固定写法。2.3 侧栏索引与列表的联动侧栏字母悬浮在右侧用户上滑时跟着走点击字母时列表跳到对应分组。实现有两种重写RecyclerView的addItemDecoration画悬浮头或者用LinearLayoutManager.scrollToPositionWithOffset精确滚动。前者负责视觉后者负责定位两个都要。点击侧栏字母后先找到该字母在分组列表里的索引然后算目标位置。如果你用的是展开的扁平列表索引就是可见位置如果用的是分组结构需要再加上头部占用的偏移private void scrollToLetter(String letter) { int groupIndex -1; for (int i 0; i groups.size(); i) { if (letter.equals(groups.get(i).letter)) { groupIndex i; break; } } if (groupIndex 0) return; // 计算该分组在扁平列表中的起始位置前序所有分组条目数 分组头 int position 0; for (int i 0; i groupIndex; i) { position groups.get(i).cities.size() 1; // 1 为分组头 } layoutManager.scrollToPositionWithOffset(position, 0); }注意1这个偏移量它对应分组头。少加就会跳错一行用户点击“H”却停在“G”的最后一个城市上这种错位在小屏设备上非常明显。另外scrollToPositionWithOffset第二个参数是像素级的顶部偏移如果界面里列表上方还有搜索框要把搜索框高度减进去否则目标分组会藏在搜索栏下面。2.4 布局文件与关键视图界面的骨架不复杂一个垂直容器放搜索框、当前城市、热门城市、带索引的列表再加一个右侧字母栏LinearLayout android:layout_widthmatch_parent android:layout_heightmatch_parent android:orientationvertical EditText android:idid/et_search android:layout_widthmatch_parent android:layout_heightwrap_content android:hint输入城市名或拼音 / androidx.recyclerview.widget.RecyclerView android:idid/rv_city android:layout_widthmatch_parent android:layout_height0dp android:layout_weight1 / com.example.CityIndexBar android:idid/index_bar android:layout_width24dp android:layout_heightmatch_parent android:layout_gravityend / /LinearLayoutRecyclerView用0dp weight1占满剩余空间是最稳的别给固定高度。索引栏宽度给 24dp 到 32dp 之间太窄手指点不准太宽遮挡列表内容。CityIndexBar是自定义 View负责绘制字母和在onTouchEvent里把触点换算成字母索引。搜索框的inputType记得设成text不要开textUri否则部分输入法会把空格吃掉导致“呼和 浩特”这类带空格的城市名搜不到。3. 定位与搜索交互入口的冲突如何处理3.1 定位城市的展示与降级城市选择器里通常有个“当前定位”区域显示定位到的城市名旁边一个“重新定位”按钮。这里的关键不是定位本身那是地图 SDK 的事而是定位结果如何与用户选择合并。常见做法是城市选择器对外只暴露一个OnCitySelectedListener定位成功时也不直接回调业务方而是把定位城市作为“当前城市”显示在顶部用户点了才算选中。原因是定位有失败率直接回调会让业务层收到一个没经过用户确认的城市后面下单地址就错乱了。public interface OnCitySelectedListener { void onCitySelected(City city, boolean fromLocate); } // 定位成功后仅更新顶部展示不触发业务回调 public void onLocateResult(String cityName) { currentCity.setText(cityName); currentCity.setTag(cityName); }fromLocate这个布尔值很有用业务方可以用它区分“用户主动翻列表选的”和“点了定位城市条目选的”比如前者可以静默保存后者可能需要二次确认。定位失败时顶部显示“定位失败点击重试”不要显示空白空白会让用户以为界面卡死了。3.2 搜索过滤的匹配策略搜索框的过滤逻辑要同时支持中文名和拼音。用户输入“hz”也能搜到“杭州”输入“zhou”也能搜到这靠的是每个城市对象上挂的拼音字段。public ListCity filter(String keyword) { if (keyword null || keyword.trim().isEmpty()) { return new ArrayList(); } String kw keyword.trim().toLowerCase(Locale.ROOT); ListCity result new ArrayList(); for (City city : allCities) { boolean nameHit city.getName().contains(keyword.trim()); boolean pinyinHit city.getPinyin() ! null city.getPinyin().toLowerCase(Locale.ROOT).contains(kw); // 同时匹配全拼和首字母缩写覆盖 hangzhou 和 hz 两种输入 boolean abbrHit city.getPinyinAbbr() ! null city.getPinyinAbbr().toLowerCase(Locale.ROOT).startsWith(kw); if (nameHit || pinyinHit || abbrHit) { result.add(city); } } return result; }nameHit用原始大小写的中文pinyinHit统一小写后比较避免大小写敏感导致“Hangzhou”搜不到。abbrHit用startsWith而不是contains因为缩写匹配是前缀式的——输入“hz”应命中“杭州”但输入“z”不应该把“杭州”也带出来否则结果太泛会让用户困惑。搜索无结果时给个明确的空状态提示不要只显示一片白。3.3 数据持久化与最近访问城市用户选过的城市要不要记住如果产品有“最近访问”模块就需要持久化。简单的方案是SharedPreferences存一个城市 ID 列表限制长度 5 到 8 条超出就淘汰最旧的。注意城市名称是会变的行政区划调整所以存 ID 为主、名称为辅读取时用 ID 去数据源里反查当前名称查不到就丢弃这条记录。如果本地数据源里没有 ID 字段那就用城市名做键但要接受“改名后记录失效”这个代价别为了兼容去写复杂的模糊匹配收益不成正比。3.4 与业务层的解耦好的城市选择器应该能独立成一个 module 或一个Activity通过Intent传参、通过onActivityResult回传。传参时只需要一个“是否显示定位”的开关和一个“当前选中城市”的初始值回传时给cityId、cityName、provinceName三个字段就够了。业务层不需要知道选择器内部用的是 RecyclerView 还是 ListView也不需要知道侧栏怎么画。接口越窄被复用到别的项目里就越省事这一点在跨项目搬代码时体会最深。4. 避坑排查五个真实翻车场景4.1 侧栏字母与列表分组对不上现象侧栏显示 26 个字母列表里只有 23 个分组点击某些字母没反应。原因侧栏字母是从静态字符串 “ABCDEFGHIJKLMNOPQRSTUVWXYZ” 硬编码生成的而列表分组是从数据里实际统计出来的两者数据源不一致。某些字母下恰好没有城市就出现“有索引无内容”。解决侧栏字母必须由列表分组数据动态生成遍历groups取letter去重排序。宁可侧栏少几个字母也不要出现点击落空。4.2 首字母悬浮头跳动或错位现象滑动列表时顶部悬浮的字母头一闪一闪或者停留在错误的位置。原因ItemDecoration里计算悬浮头位置时没有考虑下一个分组头即将顶上来时的临界判断导致悬浮头在两组之间的切换时机算错。解决在onDrawOver里先判断下一个分组头的顶部是否已经到达悬浮头位置到了就让悬浮头跟着上移用if (nextTop headerHeight)做上推逻辑。这类错位通常是 1 到 2 像素的误差被放大了逐帧打印坐标能很快定位。4.3 定位权限缺失导致界面卡住现象进入页面后顶部一直显示“定位中”等半天没有结果。原因这里的问题往往不在权限申请代码本身而是定位 SDK 的回调在权限被拒后没有返回失败状态界面就一直停在 loading。解决在调用定位前先检查权限是否授予拒绝时立即把顶部状态置为“定位失败点击重试”并给重试按钮重新触发权限申请。不要依赖 SDK 的失败回调兜底那个回调不一定按预期触发。4.4 搜索输入法弹出后列表被遮挡现象点搜索框弹出键盘下面的城市列表只剩一条缝用户看不清结果。原因Activity 的windowSoftInputMode默认是adjustPan整个窗口被推上去列表可视区域被压缩。解决在AndroidManifest.xml里给城市选择页设置android:windowSoftInputModeadjustResize让列表高度自适应剩余空间同时把搜索框固定在列表上方搜索状态下把侧栏隐藏掉减少视觉干扰。4.5 快速滑动时索引栏触点漂移现象手指在侧栏快速上下滑动选中的字母和手指实际位置差了一两格。原因onTouchEvent里用event.getY()直接除以字母高度但当栏内加了 padding 或顶部偏移时没有扣除导致换算比例偏移。解决先减去索引栏的顶部 padding再除以单个字母的绘制高度最后做Math.max(0, Math.min(index, letters.size() - 1))的越界保护。越界保护很关键手指滑出控件边界时未经保护的索引会越界抛异常。5. 把城市选择器接进真实工程几个值得养成的习惯集成阶段最容易忽略的是数据源的组织方式。我一般会把城市数据放在assets/cities.json应用启动时异步读取一次解析成ListCity后缓存到内存。别每次打开选择器都重新读文件几百条 JSON 在小内存机型上也能感觉到卡顿。[ {id: 330100, name: 杭州, pinyin: hangzhou, abbr: hz, letter: H, hot: true}, {id: 110100, name: 北京, pinyin: beijing, abbr: bj, letter: B, hot: true} ]字段上id用于业务关联pinyin用于搜索abbr用于首字母缩写匹配letter用于分组hot标记热门。把abbr单独存一列而不是运行时截取是为了处理“重庆”这种 chóngqìng 但缩写习惯上更常写作 cq 的情况——直接截首字取 c、第二个字取 q 也能得到 cq但遇到“长沙”changsha缩写 cs和“长春”changchun也常写 cc时规则化截取不一定符合各地习惯预存一列能省掉后续函数维护。验证集成是否到位我通常做三步一是断网打开选择器确认列表和搜索可用定位区域显示失败态二是用两台不同分辨率的设备滑动侧栏确认点击落点一致三是把系统语言切成英文确认拼音搜索仍能返回结果中文城市名不出现乱码。第三步很多人会漏toLowerCase时没带Locale.ROOT的话土耳其语环境下 “I” 转小写会变成 “ı”虽然是极端情况但测试成本很低。如果项目里已经有全局的城市数据服务城市选择器应该只负责 UI 和交互数据通过构造参数或接口注入。硬编码数据源看似省事等到城市列表要更新时才发现得改代码重新发版这个坑很常见。组件边界画清楚后面换数据源、换定位实现时改动都只落在一个地方。从那以后我每次接这类选择器组件都会先跑一遍断网、多分辨率、切语言这三项哪怕业务方催得再紧也不跳过——城市选错一个后面的地址、运费、配送范围全跟着错这种错误上线后修复的成本远高于测试十分钟。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
UNet图像分割实战:PyTorch从掩膜生成到滑窗推理与ONNX校验 简介:面向Python图像处理与深度学习实践者的U-Net图像分割资料包,适合希望从数据准备、模型构建、损失函数选择到训练预测完整走通分割流程的初中级开发者,也可用于医学影像、遥感等场景的入门复现。压缩包共21个文件、约5.6MB,以… · 2026/11/10 23:09:01
JSP相册管理系统实战:图片上传、缩略图与分页鉴权 简介:这是一套面向Java Web入门者与课程设计学习者的JSP相册管理系统源码,围绕照片上传、分类管理、评论与后台维护等常见需求展开,适合用来理解JSP页面、Servlet请求处理、JavaBean业务封装与MySQL数据访问之间的协作方式。压缩包共115个文件… · 2026/11/10 23:09:02
Samba 4 域控运维脚本包:备份、sysvol 与巡检实战 简介:这份资源面向在 Debian Jessie 与 Stretch 上运维 Samba 4(AD-DC)的 Linux 系统管理员和运维工程师,汇集了作者日常使用的实用脚本,用于解决域控备份、SePrivileges 权限核查、sysvol ACL 检查与设置、域信息查看… · 2026/11/5 1:38:09
香河哪里有考电工证怎么找靠谱渠道 电工证含金量高吗 香河哪里有考电工证怎么找靠谱渠道 电工证含金量高吗 不知道去哪报名怕被中介坑,香河地区考电工证的小伙伴,很多人一上来就问“香河哪里有考电工证”。但真正了解流程的人知道,找对地方只是第一步,关键还在于证书的含金量和报考流程是否规范。… · 2026/11/10 23:09:02
山东省电工证法证机关通过率揭秘:别再被中介坑了,自己也能搞定 山东省电工证法证机关通过率揭秘:别再被中介坑了,自己也能搞定 你是不是也这样,拿着手机搜“山东省电工证法证机关”,翻了十几页结果,全是各种培训机构的广告,连个靠谱的官方信息都找不到?别急,这篇内容就为你 揭秘通过率… · 2026/11/10 23:09:02
特种工考场考试视频内部消息:急需特种作业证才能上岗找工作怎么办? 特种工考场考试视频内部消息:急需特种作业证才能上岗找工作怎么办? 你现在就缺一个特种作业证,但不知道从哪儿下手?别急,这篇文章专门讲【特种工考场考试视频】的内部消息,带你一步步搞定考试,稳稳拿到证,顺利上岗!… · 2026/11/10 23:09:02
湖北电工证官网报名怎么报名?证书过期了不知道怎么复审延期怎么办? 湖北电工证官网报名怎么报名?证书过期了不知道怎么复审延期怎么办? 证书过期了不知道怎么复审延期,是不是你最近的烦恼?别急,今天就给你讲清楚【湖北电工证官网报名】怎么报名,也教你如何处理证书过期的问题,别再踩坑了!… · 2026/11/10 23:09:02
上海外地电工证怎么考全流程办理指南:郑州报考避坑指南全解析 上海外地电工证怎么考全流程办理指南:郑州报考避坑指南全解析 之前考的证在外省能不能转过来?这几乎是所有跨省打工人的第一反应。尤其是像郑州、洛阳等地的电工朋友,不少人都经历过在外省考了证,回老家后却不知道怎么转,或者转不了的尴尬情况。今天我们… · 2026/11/10 23:09:02
电工证丢失还能补吗?千万别踩坑,丢了证等于失业 电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。… · 2026/11/10 2:16:10
电力电缆作业证直播报名入口与办理流程良心建议 电力电缆作业证直播报名入口与办理流程良心建议 你是不是也担心学历不高报不了名?别急,今天就给你讲讲 电力电缆作业证直播 的报名流程和那些你必须知道的干货, 良心建议 在这里,别错过。 电力电缆作业证直播报名需要什么条件?… · 2026/11/10 17:46:39
英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 怕考不过白交培训费?英德电工证报考费多少钱,你真的了解吗?现在市面上培训机构五花八门,价格参差不齐,很多人一不小心就花了冤枉钱,结果还考不过。别急,今天就来给你说清楚英德电工证的… · 2026/11/10 17:11:43
丹寨电工证怎么考?内部消息透露报考条件和流程 丹寨电工证怎么考?内部消息透露报考条件和流程 急需特种作业证才能上岗找工作,这是很多电工朋友最真实的焦虑。尤其是像丹寨电工证这样的证书,直接影响你的职业发展和薪资待遇。但你知道吗?很多人都在报考过程中踩坑,有的甚至花了几千块最后白忙一场。今… · 2026/11/10 0:00:13
成都建筑电工证办理郑州报考避坑指南 成都建筑电工证办理郑州报考避坑指南 证书过期了不知道怎么复审延期?别慌!这篇文章手把手带你解决成都建筑电工证办理的难题,顺便避掉郑州报考的那些坑。不管你是刚拿到证的新手,还是准备复审的老电工,这波干货都别错过。… · 2026/11/10 0:00:13
梅州低压电工证怎么考?怕被中介坑?手把手教你全国通用流程 梅州低压电工证怎么考?怕被中介坑?手把手教你全国通用流程 你是不是正为梅州低压电工证发愁?不知道去哪报名,怕被中介骗钱,又担心证书是不是全国通用?其实,这些顾虑都是正常人会有的。今天我就从一个在开封房建工程一线干了几年的电工角度,手把手教你… · 2026/11/10 0:00:13
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/10 20:19:29
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/10 19:22:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/10 17:00:47