Android城市选择器开发实战:拼音索引、搜索与定位回填

简介:这是一款面向Android开发者的城市选择器组件,仿照美团城市选择界面设计,帮助开发者快速在电商、旅游、社交或生活服务类应用中集成城市选择功能,适合从初学者到资深工程师的各类开发者使用。资源包共285个文件,包含164个class、40个xml、31个java、22个png及6个json等,涵盖编译产物、布局资源、源码与图片素材,压缩包约908KB,结构完整便于直接引入项目。该组件基于高德地图引擎实现精准定位,采用模块化设计,提供简洁易用的API,开发者只需添加依赖、初始化并注册回调,即可弹出选择器并处理用户选择结果,同时支持自定义城市数据与UI样式。目前已有929人学习下载,借助其清晰的源码结构与示例,读者可快速掌握集成方法,并根据项目需求灵活调整与扩展,显著节省开发与测试时间。

1. 城市选择器:一个被低估的 Android 高频模块

做本地生活、外卖、租房、出行这类 App,绕不开一个交互:让用户选城市。看起来简单,真做起来全是细节——热门城市怎么排、定位城市怎么回填、拼音首字母索引怎么对齐、搜索框输入「bj」能不能命中「北京」。我见过不少项目直接拿个 ListView 硬怼,结果列表滑动卡顿、索引条点不准、搜索延迟半秒,用户骂骂咧咧地手动滑到底部找自己的城市。

这份 Android 城市选择器资源,就是冲着这些痛点来的。它把城市数据、拼音检索、字母索引、定位回填、热门城市这几块拆得比较清楚,适合正在做本地生活类 App、需要快速接入城市选择能力的开发者。不管你是刚接触 Android 的新手,还是想找一个能直接改的轮子,这套代码的模块划分和数据结构都值得拆一遍。下面我按「能干什么 → 怎么跑 → 怎么改」的顺序,把这份资源从头到尾捋清楚。

2. 项目结构与技术选型:城市数据怎么组织、界面怎么搭

2.1 整体目录结构

拿到源码先别急着跑,花五分钟看目录,能省掉后面半小时的瞎找。这个项目的结构是典型的 Android 单模块工程,核心代码集中在app/src/main/java下,按功能分包:

app/src/main/ ├── java/com/example/citypicker/ │ ├── CityPickerActivity.java // 城市选择主界面 │ ├── SearchActivity.java // 搜索页 │ ├── adapter/ │ │ ├── CityListAdapter.java // 城市列表适配器 │ │ └── SearchResultAdapter.java // 搜索结果适配器 │ ├── model/ │ │ ├── City.java // 城市实体 │ │ └── CityGroup.java // 按字母分组的数据结构 │ ├── util/ │ │ ├── PinyinUtil.java // 拼音转换与首字母提取 │ │ └── CityDataLoader.java // 城市数据加载 │ └── widget/ │ └── SideIndexBar.java // 右侧字母索引条 ├── assets/ │ └── cities.json // 城市数据源 └── res/ ├── layout/ │ ├── activity_city_picker.xml │ └── item_city.xml └── values/ └── colors.xml

这个分包的思路很直白:model管数据、adapter管渲染、util管工具、widget管自定义控件。新手照着这个结构找代码不会迷路,熟手也能一眼看出哪些层可以替换。常见做法是把城市数据放assets里用 JSON 存,而不是硬编码在 Java 里,这样换数据不用重新编译逻辑代码。

2.2 城市实体与分组数据结构

城市数据的组织方式决定了后面搜索和索引好不好做。先看实体类:

public class City { private String name; // 城市名,如"北京" private String pinyin; // 全拼,如"beijing" private String firstLetter; // 首字母大写,如"B" private boolean isHot; // 是否热门城市 private boolean isLocated; // 是否当前定位城市 // 构造、getter、setter 省略 }

pinyin和firstLetter这两个字段是关键。很多新手会想「我搜索的时候现算拼音不行吗」,行,但每次输入都遍历几千个城市现算,输入框会卡到你怀疑人生。正确做法是在数据加载阶段就把拼音和首字母算好缓存进对象,搜索时直接比对字段。isHot和isLocated用布尔标记,渲染时决定要不要插到列表顶部。

分组结构CityGroup则是给列表分区用的:

public class CityGroup { private String letter; // 分组字母,如"A" private List<City> cities; // 该字母下的城市列表 }

列表渲染时,外层按letter排序,内层按城市名排序,右侧索引条点击时直接跳到对应letter的 position。这个设计比把所有城市塞一个平铺列表要清晰得多,索引条和列表的联动也简单。

2.3 主界面布局与索引条

主界面布局activity_city_picker.xml的结构是「顶部搜索框 + 当前城市 + 热门城市 + 全部城市列表 + 右侧索引条」。核心是列表和索引条的叠放:

<FrameLayout ...> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_city_list" android:layout_width="match_parent" android:layout_height="match_parent" /> <com.example.citypicker.widget.SideIndexBar android:id="@+id/side_index_bar" android:layout_width="24dp" android:layout_height="match_parent" android:layout_gravity="right|center_vertical" /> </FrameLayout>

SideIndexBar是自定义 View,负责绘制 A-Z 加「#」的索引字母,并处理触摸事件。它和 RecyclerView 的联动逻辑是:手指在索引条上滑动时,计算当前触摸的字母,通过接口回调把字母传给 Activity,Activity 再查该字母对应的分组 position,调用rvCityList.scrollToPosition(pos)。这里有个细节,索引条的高度要按字母数量均分,触摸时用(int) (y / itemHeight)算索引,不能直接用y去比,否则点「C」会跳到「F」。

2.4 拼音工具与数据加载

PinyinUtil负责把中文城市名转成拼音和首字母。项目里用的是常见的拼音库方案,核心方法两个:

public class PinyinUtil { // 获取全拼,如"北京" -> "beijing" public static String getPinyin(String chinese) { // 调用拼音库逐字转换后拼接 } // 获取首字母大写,如"北京" -> "B" public static String getFirstLetter(String chinese) { String pinyin = getPinyin(chinese); return pinyin.substring(0, 1).toUpperCase(); } }

参数说明:chinese是城市中文名,返回的拼音全小写、无空格。注意多音字问题,比如「重庆」的「重」要读「chong」不是「zhong」,这类城市需要在数据源里手动修正拼音字段,不能全靠自动转换。我一般会在cities.json里给每个城市显式写好pinyin,自动转换只作为兜底。

CityDataLoader负责从assets/cities.json读数据、解析成City列表、按首字母分组、把热门城市和定位城市插到对应位置。加载放在子线程,加载完再回调主线程刷新 UI,避免主线程读文件造成启动白屏。

3. 跑起来:从导入到看到城市列表的完整步骤

3.1 环境准备与导入

这个项目是标准 Android 工程,环境要求不复杂:

项目要求
开发工具Android Studio 4.0 及以上
编译 SDKAPI 30 左右
最低支持API 21
语言Java
构建工具Gradle

导入步骤:打开 Android Studio,选「Open an existing project」,定位到解压后的工程根目录,等 Gradle 同步完成。如果同步卡在下载依赖,检查build.gradle里的仓库地址是否可达,常见做法是加国内镜像源。同步完成后,连上真机或开模拟器,点绿色运行按钮即可。

3.2 城市数据文件的格式

assets/cities.json是数据源,格式大致如下:

[ {"name": "北京", "pinyin": "beijing", "firstLetter": "B", "isHot": true}, {"name": "上海", "pinyin": "shanghai", "firstLetter": "S", "isHot": true}, {"name": "重庆", "pinyin": "chongqing", "firstLetter": "C", "isHot": false} ]

字段含义:name是显示名,pinyin用于搜索匹配,firstLetter用于分组和索引,isHot标记热门城市。注意firstLetter必须大写,且要和分组逻辑里的字母表一致,否则索引条点「C」找不到「重庆」。如果城市数据量大,建议按省份或拼音排序后存储,加载时少一次排序。

3.3 主流程代码走读

Activity 的onCreate里做了几件事:初始化视图、加载数据、设置适配器、绑定索引条回调。核心片段:

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_city_picker); rvCityList = findViewById(R.id.rv_city_list); sideIndexBar = findViewById(R.id.side_index_bar); // 子线程加载城市数据 CityDataLoader.load(this, new CityDataLoader.Callback() { @Override public void onLoaded(List<CityGroup> groups) { // 回主线程更新 UI runOnUiThread(() -> { cityGroups = groups; adapter = new CityListAdapter(cityGroups, CityPickerActivity.this); rvCityList.setLayoutManager(new LinearLayoutManager(CityPickerActivity.this)); rvCityList.setAdapter(adapter); // 索引条绑定分组字母 sideIndexBar.setLetters(extractLetters(groups)); }); } }); // 索引条触摸回调 sideIndexBar.setOnLetterTouchListener(letter -> { int pos = findPositionByLetter(letter); if (pos >= 0) { rvCityList.scrollToPosition(pos); } }); }

逻辑说明:CityDataLoader.load是异步的,回调里才拿到分组数据,避免主线程阻塞。extractLetters从分组里提取字母列表传给索引条,保证索引条只显示实际存在的字母。findPositionByLetter遍历分组找到对应字母的起始 position,scrollToPosition直接跳过去。参数上,letter是索引条回调的当前字母,pos是分组在列表中的位置,注意这里用的是分组 position 不是城市 position,因为适配器里分组标题也占一个 item。

3.4 搜索功能的实现

搜索页SearchActivity的核心是输入监听加过滤:

etSearch.addTextChangedListener(new TextWatcher() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { String keyword = s.toString().trim().toLowerCase(); if (keyword.isEmpty()) { rvResult.setVisibility(View.GONE); return; } List<City> result = new ArrayList<>(); for (City city : allCities) { // 同时匹配中文名、全拼、首字母 if (city.getName().contains(keyword) || city.getPinyin().startsWith(keyword) || city.getFirstLetter().equalsIgnoreCase(keyword)) { result.add(city); } } searchAdapter.updateData(result); rvResult.setVisibility(result.isEmpty() ? View.GONE : View.VISIBLE); } // 其他空实现省略 });

逻辑说明:输入统一转小写后比对。匹配规则有三条——中文名包含、拼音前缀匹配、首字母相等。用startsWith而不是contains匹配拼音,是因为用户输入「bei」期望的是「北京」这类前缀命中,而不是「东北」这种中间包含。allCities是加载阶段就缓存好的全量列表,搜索时只遍历不重新加载。如果城市量特别大,常见做法是加个内存索引或改用 Trie 树,但这个项目的数据量级用线性遍历足够。

4. 避坑与常见问题:索引错位、搜索卡顿、定位回填

4.1 索引条点击跳转位置不准

现象:点索引条上的「C」,列表跳到了「B」或「D」的位置。原因通常是索引条计算触摸字母时用了错误的除数,或者分组 position 和列表 position 没对齐。索引条每个字母的高度应该是viewHeight / letterCount,触摸时用(int) (event.getY() / itemHeight)算索引,再取letters.get(index)。如果列表里分组标题和城市 item 混排,findPositionByLetter返回的必须是分组标题的 position,不能返回第一个城市的 position,否则会跳过标题。

解决:在SideIndexBar的onTouchEvent里打印当前计算的 index 和 letter,和预期对比。同时检查findPositionByLetter的遍历逻辑,确保返回的是分组起始位置。

4.2 搜索输入延迟、列表卡顿

现象:在搜索框快速输入时,结果列表更新滞后,输入框有卡顿感。原因一般是每次输入都重新遍历全量城市并新建列表对象,或者拼音是现算的。如果City对象里没有缓存pinyin,每次比对都调拼音库,几千个城市遍历下来就是灾难。

解决:数据加载阶段就把pinyin和firstLetter算好存进对象。搜索时只做字符串比对,不调拼音库。另外可以加一个简单的防抖,输入停止 200ms 后再触发过滤,减少无效遍历。

4.3 定位城市不显示或重复

现象:定位成功后,定位城市没有出现在列表顶部,或者和热门城市里的同一个城市重复出现。原因是定位城市插入逻辑和热门城市插入逻辑没做去重,或者插入位置计算错误。

解决:在数据加载完成后,先检查定位城市是否已在热门列表中,如果在就只标记isLocated不重复插入。插入位置固定在分组列表的最前面,单独作为一个「当前城市」分组,不混入字母分组。

4.4 多音字城市拼音错误

现象:搜索「chongqing」搜不到「重庆」,或者「重庆」被分到了「Z」组。原因是自动拼音转换把「重」转成了「zhong」。这类多音字城市在自动转换里是玄学,不能全信。

解决:在cities.json里对多音字城市显式指定pinyin和firstLetter,加载时优先用数据源里的值,自动转换只作为缺失时的兜底。常见需要手动修正的城市包括重庆、长沙、厦门等。

4.5 列表滑动时索引条不跟随

现象:手动滑动列表到「S」区域,右侧索引条没有高亮「S」。原因是索引条只处理了触摸事件,没有监听列表滑动。这个不算 bug,但体验上会让人觉得索引条是死的。

解决:给 RecyclerView 加addOnScrollListener,在onScrolled里计算当前第一个可见 item 的分组字母,回调给索引条更新高亮。注意计算时要跳过分组标题和顶部固定区域,否则高亮会跳来跳去。

5. 二次开发:换数据源、加定位、接搜索历史的改造思路

这份代码拿来直接用没问题,但真实项目里总得改几处。我一般会从三个方向下手:数据源、定位、搜索历史。

先说换数据源。项目默认从assets/cities.json读,如果你们后端有城市接口,把CityDataLoader里的读取逻辑换成网络请求就行。关键是保持返回的数据结构一致,解析后仍然是List<City>,后面的分组和渲染逻辑不用动。改造时注意加缓存,城市数据变动不频繁,本地存一份,下次启动先读缓存再后台刷新。我一般会在CityDataLoader里加一个loadFromCache和loadFromNetwork的分支,缓存命中就直接回调,没命中再走网络。

public static void load(Context context, Callback callback) { List<City> cached = readFromCache(context); if (cached != null && !cached.isEmpty()) { callback.onLoaded(groupCities(cached)); // 后台静默刷新 refreshFromNetwork(context, callback); } else { loadFromAssets(context, callback); } }

这段逻辑的好处是启动快,用户不会盯着白屏等网络。groupCities是分组方法,把平铺列表转成List<CityGroup>。refreshFromNetwork拿到新数据后更新缓存并再次回调,界面无感刷新。

再说定位。项目里定位城市是预留了字段但没接具体定位 SDK。接入时在onCreate里发起定位,拿到城市名后回填。注意定位是异步的,可能比数据加载还慢,所以定位回调里要判断列表是否已渲染,没渲染就先把城市名存起来,等数据加载完再插入。定位失败要有兜底,默认显示「北京」或上次选择的城市,不能让顶部空着。

最后说搜索历史。这个功能用户感知很强,实现也简单。在SearchActivity里维护一个最近搜索的List<String>,每次用户点击搜索结果时存一条,用 SharedPreferences 持久化。搜索页初始状态显示历史记录,输入时隐藏,清空输入时再显示。注意历史记录要去重且限制条数,我一般留最近 10 条,超出就删最旧的。

改造完记得跑一遍完整流程:冷启动看缓存是否生效、断网看兜底是否正常、定位慢的情况下列表是否正常渲染、搜索历史点击是否能正确回填到选择结果。这几个场景覆盖了大部分线上问题。

从那以后我每次接城市选择器,都会先把数据加载和定位的时序在纸上画一遍,确认谁先谁后、谁依赖谁,再动手写代码。这个习惯帮我省了不少返工。希望帮到你。

本文还有配套的精品资源,点击获取

相关新闻

边缘AI推理的五大工程边界与系统级优化

边缘AI推理的五大工程边界与系统级优化

1. 为什么“边缘AI推理”不是把模型往设备上一塞就完事&#xff1f;“边缘AI推理的工程边界与演进逻辑”——这个标题乍看像学术论文&#xff0c;但实际是我在过去三年里踩过二十多个项目坑之后&#xff0c;用三台烧坏的Jetson Nano、四块过热关机的树莓派CM4和一份被客户退回七…

2026/10/20 20:39:12 阅读更多 →
WSL2下STM32烧录实战:usbipd配置、ST-Link直通与OpenOCD调试

WSL2下STM32烧录实战:usbipd配置、ST-Link直通与OpenOCD调试

在 WSL 里折腾 STM32 烧录&#xff0c;最让我绷不住的一个瞬间就是&#xff1a;终端里st-info --probe安静地执行完毕&#xff0c;没有任何输出&#xff1b;OpenOCD 则用一行红色报错告诉我"没有找到 ST-Link"。我低头看了一眼桌面&#xff0c;开发板电源灯亮着&…

2026/10/20 20:39:12 阅读更多 →
集体养龙虾背后:大模型落地与IT人的实战指南

集体养龙虾背后:大模型落地与IT人的实战指南

最近耳边经常听到一句话&#xff1a;大厂集体“养龙虾”。头几次我还以为是哪个新消费赛道&#xff0c;直到同事甩过来一个链接&#xff0c;才发现所谓的“龙虾”&#xff0c;就是 LLM—— Large Language Model。大家图省事&#xff0c;把 LLM 读成“龙虾”&#xff0c;于是“…

2026/10/20 20:38:10 阅读更多 →

最新新闻

全球AI模型榜前20名国产占6席:技术路线与选型指南

全球AI模型榜前20名国产占6席:技术路线与选型指南

1. 榜单背后的信号&#xff1a;国产模型进入第一梯队的真实含义第一次看到“全球AI模型榜前20名&#xff0c;国产占了6个”这个说法时&#xff0c;我的第一反应不是兴奋&#xff0c;而是想搞清楚三件事&#xff1a;这是哪个榜单、用什么指标排的、这6个模型分别强在哪。因为在大…

2026/10/20 22:39:53 阅读更多 →
多头自注意力机制详解:从原理到代码实战,用外卖订单讲透Transformer核心

多头自注意力机制详解:从原理到代码实战,用外卖订单讲透Transformer核心

1. 多头自注意力&#xff1a;别再死记公式&#xff0c;我用外卖订单给你讲明白第一次接触“多头自注意力”这个概念的时候&#xff0c;我盯着论文里的公式看了整整一个下午&#xff0c;脑子里全是“这到底是什么玩意儿”。后来做了几年深度学习相关的项目&#xff0c;踩过不少坑…

2026/10/20 22:39:53 阅读更多 →
计算器也能打穿?CTF中PHP eval黑名单绕过实战解析

计算器也能打穿?CTF中PHP eval黑名单绕过实战解析

1. 拿到题目的第一轮信息收集&#xff1a;页面、请求和异常输入探针1.1 先用肉眼和抓包把页面过一遍CTF的Web题里&#xff0c;最容易被低估的就是这种"门面干净"的题目。页面没有跳转、没有登录、没有文件上传点&#xff0c;只有一个孤零零的输入框和一个计算结果区&…

2026/10/20 22:39:53 阅读更多 →
用Python打造PPT生成工具:模板+数据+渲染三层架构实战

用Python打造PPT生成工具:模板+数据+渲染三层架构实战

1. 为什么我要自己动手做一个 PPT 生成工具 做技术分享、项目汇报、课程作业&#xff0c;甚至给客户做方案&#xff0c;PPT 几乎是绕不开的一环。我粗略算过&#xff0c;过去一年里我至少做了四十多份 PPT&#xff0c;平均每份从整理大纲到排版定稿要花三到五个小时。最让人抓狂…

2026/10/20 22:39:52 阅读更多 →
Python+TensorFlow+PyQt5人脸表情识别与情绪分类系统实战

Python+TensorFlow+PyQt5人脸表情识别与情绪分类系统实战

人脸表情识别这个方向&#xff0c;外行看起来就是“认个脸、判断一下表情”&#xff0c;真正动手做的人才知道&#xff0c;从摄像头画面到“开心、难过、惊讶”这些标签之间&#xff0c;至少隔着四座山&#xff1a;数据、模型、推理、界面。我最近把一套基于 Python TensorFlo…

2026/10/20 22:39:52 阅读更多 →
MySQL+Java+Swing学生宿舍管理系统课程设计全解析

MySQL+Java+Swing学生宿舍管理系统课程设计全解析

简介&#xff1a;面向高校数据库与Java课程设计场景&#xff0c;这套基于MySQLJavaSwing开发的学生宿舍管理系统提供了完整的可运行工程与配套文档。资源以学生信息管理、宿舍分配管理、管理员后台等核心模块为主线&#xff0c;包含数据库表设计、界面交互与业务逻辑分层实现&a…

2026/10/20 22:38:46 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →