flutter_for_openharmony城市井盖地图app实战+附近井盖实现
本文介绍了一个基于Flutter开发的井盖巡检系统功能模块,重点实现了"附近井盖"筛选功能。该功能包含片区下拉选择、风险滑块筛选和列表展示三大核心组件,支持运维人员快速定位符合条件的目标井盖。文章详细解析了页面架构设计、Mock数据构造、状态管理、UI组件实现等关键技术点,展示了如何通过Flutter实现高效的数据筛选与可视化展示,为移动端巡检工具开发提供了实用参考方案。

1. 这个功能解决什么问题
现场巡检时,运维人员核心诉求是快速定位符合条件的井盖点位,“附近井盖”功能正是为解决这类场景痛点设计:
- 片区下拉:巡检人员通常按行政区划分责任范围,下拉筛选可快速聚焦当前负责片区,避免跨区域无效查找
- 风险滑块:高风险井盖(如破损、渗漏)需要优先处理,滑块可精准筛选出高于指定风险阈值的点位
- 列表展示:核心信息一站式呈现,编号便于台账核对,片区/地址定位物理位置,风险百分比直观判断紧急程度
- 点击进入详情:复用已有详情页组件,减少重复开发,保证页面交互逻辑统一
这个页面是典型的“筛选+列表”组合,涵盖了 Flutter 状态管理、UI 组件复用、数据过滤等核心知识点,适合作为入门级实战示例。
2. 相关文件一览
功能落地涉及三个核心文件,职责划分清晰:
lib/feature_pages.dart:核心页面NearbyCoversPage实现,包含UI布局、交互逻辑、筛选逻辑lib/mock_data.dart:buildMockCovers方法生成模拟数据,避免依赖真实接口即可调试功能lib/models.dart:ManholeCover数据模型定义,规范井盖数据结构
3. Mock 数据构造
在开发初期,真实接口未就绪时,Mock 数据是高效验证功能的关键。lib/mock_data.dart 中构造了18条点位数据,分布在5个片区,核心设计思路如下:
List<ManholeCover> buildMockCovers() {
const districts = ['东城区', '西城区', '南城区', '北城区', '高新区'];
return List<ManholeCover>.generate(18, (i) {
final d = districts[i % districts.length];
final code = 'MH-${(1000 + i).toString()}';
final risk = ((i * 7) % 100) / 100.0;
- 片区分配:通过
i % districts.length实现5个片区循环分配,确保每个片区都有数据,覆盖筛选场景 - 编号生成:固定前缀
MH-+ 递增数字,符合井盖台账的编号规范,便于识别 - 风险值设计:
(i * 7) % 100 / 100.0让风险值在0~1之间均匀分布,既覆盖低、中、高风险,又能验证滑块筛选的准确性
final x = ((i * 11) % 100) / 100.0;
final y = ((i * 17) % 100) / 100.0;
return ManholeCover(
id: _uuid.v4(),
code: code,
district: d,
address: '$d ${10 + i}号路口',
risk: risk,
x: x,
y: y,
);
});
}
- 坐标模拟:x/y 坐标通过不同质数(11、17)取模生成,避免坐标重复,模拟真实的地理位置分布
- 地址拼接:片区+路口编号的格式,符合真实井盖地址的命名习惯,提升数据真实性
- UUID 生成:
_uuid.v4()生成唯一ID,模拟真实业务中井盖的唯一标识,为后续详情页跳转提供基础
4. 页面主体结构
NearbyCoversPage 作为核心页面,采用 StatefulWidget 实现,因为涉及筛选条件的状态变更:
class _NearbyCoversPageState extends State<NearbyCoversPage> {
late final List<ManholeCover> _all = buildMockCovers();
String _district = '全部';
double _minRisk = 0.0;
}
核心状态设计要点:
_all变量:使用late final初始化,保证页面加载时仅构造一次Mock数据,避免重复生成导致性能损耗_district初始值:默认设为“全部”,确保页面首次加载展示所有片区的井盖,符合用户默认查看全量数据的习惯_minRisk初始值:默认0.0,即无风险阈值限制,与“全部”片区配合,实现初始状态展示所有点位
5. 片区下拉实现
片区下拉选择是核心筛选控件,需保证选项无重复且交互友好:
final districts = <String>['全部', ...{for (final e in _all) e.district}];
- 去重处理:通过
Set集合({for (final e in _all) e.district})自动去重,避免同一片区多次出现在下拉列表 - 选项排序:“全部”固定在首位,符合用户操作习惯,便于快速重置筛选条件
- 集合展开:
...展开运算符将Set转为List,适配DropdownButtonFormField的数据格式要求
DropdownButtonFormField<String>(
value: _district,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: '片区',
),
- 样式设计:
OutlineInputBorder边框样式,与Flutter默认表单样式统一,提升UI一致性 - 选中值绑定:
value: _district实现选中状态与页面状态双向绑定,用户选择后状态实时同步 - 标签提示:
labelText: '片区'明确控件用途,降低用户理解成本
items: districts
.map((e) => DropdownMenuItem(value: e, child: Text(e)))
.toList(growable: false),
onChanged: (v) => setState(() => _district = v ?? '全部'),
)
- 性能优化:
toList(growable: false)创建不可变列表,避免列表被意外修改,同时减少内存占用 - 空值兜底:
v ?? '全部'处理用户可能的空选择(如下拉框异常),保证状态不会出现null值 - 状态更新:
setState触发页面重建,筛选条件变更后列表实时刷新
6. 风险滑块实现
风险滑块用于筛选高于指定阈值的井盖,兼顾交互性和可视化:
Text('最小风险: ${(100 * _minRisk).round()}%'),
Slider(
value: _minRisk,
onChanged: (v) => setState(() => _minRisk = v),
),
核心设计细节:
- 数值转换:
_minRisk是0~1的小数,乘以100并取整后展示为百分比,符合用户对“风险值”的认知习惯 - 实时交互:
onChanged回调直接更新_minRisk状态,滑块拖动过程中列表实时过滤,反馈即时 - 无额外配置:使用Slider默认的0~1取值范围,与风险值的存储格式一致,无需额外的数值映射
7. 列表过滤逻辑
过滤逻辑是页面的核心业务逻辑,需保证条件判断准确且性能高效:
final list = _all.where((e) {
final okDistrict = _district == '全部' || e.district == _district;
final okRisk = e.risk >= _minRisk;
return okDistrict && okRisk;
}).toList(growable: false);
过滤规则拆解:
- 片区判断:
_district == '全部'时跳过片区筛选,否则仅匹配指定片区的井盖,逻辑简洁且覆盖所有场景 - 风险判断:
e.risk >= _minRisk严格匹配“高于等于阈值”的条件,符合用户“只看高风险”的核心诉求 - 组合条件:
&&逻辑与保证只有同时满足片区和风险条件的井盖才会被展示 - 性能优化:
toList(growable: false)生成不可变列表,避免后续操作修改过滤结果
8. 列表项 UI
列表项需清晰展示核心信息,同时保证视觉层次和交互性:
Card(
margin: const EdgeInsets.fromLTRB(12, 6, 12, 6),
child: ListTile(
title: Text(c.code),
subtitle: Text('${c.district} · ${c.address}'),
UI设计要点:
- 容器选择:
Card组件包裹列表项,增加阴影和圆角,提升视觉层次感,区分不同井盖条目 - 边距控制:
EdgeInsets.fromLTRB(12, 6, 12, 6)上下6px、左右12px的边距,避免条目过于紧凑或松散 - 标题设计:
title展示井盖编号,是核心标识;subtitle拼接片区和地址,用·分隔,信息层级清晰
trailing: CircularPercentIndicator(
radius: 18,
lineWidth: 4,
percent: c.risk.clamp(0.0, 1.0),
center: Text('${(c.risk * 100).round()}%'),
progressColor: _riskColor(c.risk),
),
- 风险可视化:
CircularPercentIndicator圆形进度条直观展示风险百分比,比纯文字更易识别 - 数值安全:
c.risk.clamp(0.0, 1.0)限制风险值范围,避免异常值导致进度条展示错误 - 颜色映射:
_riskColor函数根据风险值返回不同颜色,红/橙/绿分别对应高/中/低风险,视觉提示更明显
onTap: () => Navigator.of(context).push(
MaterialPageRoute(builder: (_) => PointDetailPage(cover: c)),
),
),
)
- 详情跳转:
onTap回调触发页面跳转,将当前井盖对象传入详情页,实现数据传递 - 路由选择:
MaterialPageRoute是Flutter默认路由,保证跳转动画和交互符合平台规范
9. 风险颜色映射
风险颜色映射函数统一维护阈值与颜色的对应关系,便于后续维护:
Color _riskColor(double risk) {
if (risk >= 0.7) return Colors.red;
if (risk >= 0.3) return Colors.orange;
return Colors.green;
}
设计思路:
- 阈值划分:0.7和0.3作为分界点,将风险分为高(≥0.7)、中(0.3~0.7)、低(<0.3)三档,符合运维场景的风险分级标准
- 颜色选择:红色(高风险)、橙色(中风险)、绿色(低风险)是行业通用的风险警示色,用户认知成本低
- 维护性:所有风险颜色的逻辑集中在该函数,后续调整阈值或颜色仅需修改此处,无需逐个修改列表项
10. 完整页面代码
class NearbyCoversPage extends StatefulWidget {
const NearbyCoversPage({super.key});
State<NearbyCoversPage> createState() => _NearbyCoversPageState();
}
- 页面基础结构:
StatefulWidget是实现状态变更的基础,构造方法添加super.key符合Flutter最佳实践 - 状态类关联:
createState方法返回自定义状态类,实现页面逻辑与UI分离
class _NearbyCoversPageState extends State<NearbyCoversPage> {
late final List<ManholeCover> _all = buildMockCovers();
String _district = '全部';
double _minRisk = 0.0;
Widget build(BuildContext context) {
final districts = <String>['全部', ...{for (final e in _all) e.district}];
- 状态初始化:在状态类中初始化Mock数据和筛选条件,保证页面加载时状态就绪
- 下拉选项生成:在
build方法中生成片区选项,确保每次状态变更后选项列表实时更新
final list = _all.where((e) {
final okDistrict = _district == '全部' || e.district == _district;
final okRisk = e.risk >= _minRisk;
return okDistrict && okRisk;
}).toList(growable: false);
return Scaffold(
appBar: AppBar(title: const Text('附近井盖')),
- 过滤逻辑执行:在
build方法中执行过滤,每次setState都会重新计算筛选结果 - 页面骨架:
Scaffold作为页面根布局,AppBar设置页面标题,符合Material Design规范
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
Expanded(
child: DropdownButtonFormField<String>(
value: _district,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: '片区',
),
- 布局结构:
Column分为筛选区和列表区,Padding控制筛选区与页面边缘的间距 - 行布局:
Row包裹片区下拉和风险滑块,实现横向排列,充分利用页面宽度 - 占比控制:
Expanded让下拉框占满剩余宽度,保证控件布局均衡
items: districts
.map((e) => DropdownMenuItem(value: e, child: Text(e)))
.toList(growable: false),
onChanged: (v) => setState(() => _district = v ?? '全部'),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('最小风险: ${(100 * _minRisk).round()}%'),
Slider(
value: _minRisk,
onChanged: (v) => setState(() => _minRisk = v),
),
],
),
),
],
),
),
- 间距控制:
SizedBox(width: 12)分隔下拉框和滑块,避免控件重叠,提升UI美观度 - 滑块布局:
Column让滑块和数值文本垂直排列,CrossAxisAlignment.start左对齐,符合阅读习惯
Expanded(
child: ListView.builder(
itemCount: list.length,
itemBuilder: (context, i) {
final c = list[i];
return Card(
margin: const EdgeInsets.fromLTRB(12, 6, 12, 6),
child: ListTile(
title: Text(c.code),
subtitle: Text('${c.district} · ${c.address}'),
- 列表区布局:
Expanded让列表占满剩余高度,避免列表高度不足导致滚动异常 - 列表构建:
ListView.builder按需构建列表项,提升大数据量下的性能 - 数据绑定:
itemBuilder中获取过滤后的井盖对象,绑定到列表项UI
trailing: CircularPercentIndicator(
radius: 18,
lineWidth: 4,
percent: c.risk.clamp(0.0, 1.0),
center: Text('${(c.risk * 100).round()}%'),
progressColor: _riskColor(c.risk),
),
onTap: () => Navigator.of(context).push(
MaterialPageRoute(builder: (_) => PointDetailPage(cover: c)),
),
),
);
},
),
),
],
),
);
}
}
- 进度条配置:圆形进度条的半径、线宽等参数经过调试,保证在列表项中展示比例协调
- 详情页跳转:点击列表项时传递井盖对象,实现详情页的数据渲染
- 布局闭合:完整的嵌套布局保证页面结构合法,无布局溢出问题
11. 附近井盖数据模型
为了更好地管理附近井盖数据,我们需要定义一个完整的数据模型,覆盖运维场景的所有核心信息:
class NearbyManhole {
final String id;
final String code;
final String name;
final String district;
final String address;
final double latitude;
final double longitude;
模型设计原则:
- 核心标识:
id(唯一ID)、code(井盖编号)、name(井盖名称),满足台账管理的基础需求 - 位置信息:
district(片区)、address(详细地址)、latitude/longitude(经纬度),覆盖定位场景 - 不可变设计:所有字段使用
final,保证数据不可随意修改,符合Flutter状态管理的最佳实践
final double riskLevel;
final ManholeStatus status;
final ManholeType type;
final double distance;
final DateTime createdAt;
- 风险与状态:
riskLevel(风险等级)、status(井盖状态)、type(井盖类型),满足筛选和分类需求 - 距离信息:
distance(与当前位置距离),是“附近井盖”功能的核心字段 - 时间信息:
createdAt(创建时间),记录井盖建档时间,便于追溯
final DateTime? lastInspectionAt;
final String? material;
final String? manufacturer;
final Map<String, dynamic> properties;
const NearbyManhole({
required this.id,
required this.code,
required this.name,
- 可选字段:
lastInspectionAt(最后巡检时间)、material(材质)、manufacturer(生产厂家),使用可空类型适配非必填场景 - 扩展字段:
properties为Map类型,存储深度、直径、重量等个性化属性,提升模型扩展性 - 构造方法:
required关键字标记必填字段,强制调用者传入核心数据,避免空值异常
required this.district,
required this.address,
required this.latitude,
required this.longitude,
required this.riskLevel,
required this.status,
required this.type,
required this.distance,
required this.createdAt,
this.lastInspectionAt,
this.material,
this.manufacturer,
this.properties = const {},
});
}
- 构造方法兜底:
properties默认值为空Map,避免调用时未传入导致null值 - 字段完整性:覆盖井盖全生命周期的核心信息,无需频繁修改模型即可适配多数运维场景
class NearbyFilter {
final String district;
final double minRisk;
final double maxDistance;
final List<ManholeStatus> statusFilter;
过滤器模型设计:
- 基础筛选:
district(片区)、minRisk(最小风险)、maxDistance(最大距离),覆盖核心筛选维度 - 多维度筛选:
statusFilter(状态筛选)支持多选,满足复杂的筛选需求
final List<ManholeType> typeFilter;
final String searchKeyword;
final SortType sortBy;
final bool ascending;
const NearbyFilter({
this.district = '全部',
this.minRisk = 0.0,
this.maxDistance = 5.0,
- 扩展筛选:
typeFilter(类型筛选)、searchKeyword(关键词搜索),覆盖精细化筛选场景 - 排序配置:
sortBy(排序维度)、ascending(升序/降序),满足列表排序需求 - 默认值设计:所有字段设置合理默认值,避免筛选时出现空值,降低使用成本
this.statusFilter = const [],
this.typeFilter = const [],
this.searchKeyword = '',
this.sortBy = SortType.distance,
this.ascending = true,
});
}
- 空列表默认值:
statusFilter/typeFilter默认空列表,代表不筛选状态/类型 - 排序默认值:默认按距离升序排列,符合“附近井盖”按距离由近到远展示的核心诉求
class NearbyStats {
final int totalCount;
final int highRiskCount;
final int maintenanceCount;
final double averageRisk;
final double maxDistance;
final Map<String, int> districtDistribution;
const NearbyStats({
required this.totalCount,
required this.highRiskCount,
required this.maintenanceCount,
required this.averageRisk,
required this.maxDistance,
required this.districtDistribution,
});
}
统计模型设计:
- 核心统计:
totalCount(总数)、highRiskCount(高风险数)、maintenanceCount(待维护数),满足运维统计需求 - 均值与极值:
averageRisk(平均风险)、maxDistance(最大距离),提供数据概览 - 分布统计:
districtDistribution(片区分布),展示各片区井盖数量,便于区域管理
enum ManholeStatus {
normal,
damaged,
maintenance,
replaced,
abandoned,
}
enum ManholeType {
standard,
heavy,
composite,
smart,
decorative,
}
enum SortType {
distance,
risk,
code,
district,
lastInspection,
}
枚举设计:
- 状态枚举:覆盖井盖全生命周期状态,从正常使用到废弃,满足状态筛选需求
- 类型枚举:包含标准、重型、复合材料等常见井盖类型,适配不同场景的井盖分类
- 排序枚举:支持距离、风险、编号、片区、最后巡检时间排序,覆盖用户核心排序需求
12. 附近井盖状态管理
使用 Provider 管理附近井盖数据,实现状态共享和统一管理:
class NearbyManholeProvider extends ChangeNotifier {
List<NearbyManhole> _nearbyManholes = [];
List<NearbyManhole> _filteredManholes = [];
NearbyFilter _filter = const NearbyFilter();
NearbyStats? _stats;
bool _loading = false;
状态管理设计:
- 数据存储:
_nearbyManholes(原始数据)、_filteredManholes(筛选后数据),分离原始数据和展示数据 - 筛选配置:
_filter存储当前筛选条件,保证筛选逻辑可追溯 - 辅助状态:
_stats(统计数据)、_loading(加载状态)、_error(错误信息),覆盖数据加载全流程
String? _error;
double _currentLat = 39.9042;
double _currentLng = 116.4074;
List<NearbyManhole> get nearbyManholes => _nearbyManholes;
List<NearbyManhole> get filteredManholes => _filteredManholes;
NearbyFilter get filter => _filter;
NearbyStats? get stats => _stats;
bool get loading => _loading;
String? get error => _error;
- 位置信息:
_currentLat/_currentLng默认设为北京坐标,模拟用户当前位置,便于距离计算 - 只读访问:所有状态通过getter方法暴露,避免外部直接修改状态,保证状态管理的可控性
Future<void> loadNearbyManholes() async {
_loading = true;
_error = null;
notifyListeners();
try {
await Future.delayed(const Duration(seconds: 1));
_nearbyManholes = _generateMockNearbyManholes();
_applyFilter();
_calculateStats();
数据加载逻辑:
- 加载状态:开始加载时设置
_loading = true,清空错误信息,通知监听者更新UI - 模拟延迟:
Future.delayed模拟网络请求延迟,贴近真实接口调用场景 - 数据流程:生成Mock数据 → 应用筛选条件 → 计算统计数据,流程清晰且符合真实业务逻辑
_loading = false;
notifyListeners();
} catch (e) {
_error = e.toString();
_loading = false;
notifyListeners();
}
}
- 异常处理:捕获加载过程中的异常,设置错误信息并更新加载状态,避免页面崩溃
- 状态通知:关键节点调用
notifyListeners,保证UI实时响应状态变更
void updateFilter(NearbyFilter filter) {
_filter = filter;
_applyFilter();
notifyListeners();
}
void updateLocation(double lat, double lng) {
_currentLat = lat;
_currentLng = lng;
_recalculateDistances();
_applyFilter();
notifyListeners();
}
状态更新方法:
- 筛选更新:
updateFilter接收新筛选条件,应用筛选并通知UI更新 - 位置更新:
updateLocation接收新坐标,重新计算距离后再筛选,保证距离相关的筛选和排序准确
void _applyFilter() {
_filteredManholes = _nearbyManholes.where((manhole) {
if (_filter.district != '全部' && manhole.district != _filter.district) {
return false;
}
if (manhole.riskLevel < _filter.minRisk) {
return false;
}
if (manhole.distance > _filter.maxDistance) {
return false;
}
筛选逻辑实现:
- 片区筛选:非“全部”片区时,仅匹配指定片区的井盖
- 风险筛选:过滤掉风险等级低于最小阈值的井盖
- 距离筛选:过滤掉距离超过最大阈值的井盖,保证只展示“附近”的井盖
if (_filter.statusFilter.isNotEmpty && !_filter.statusFilter.contains(manhole.status)) {
return false;
}
if (_filter.typeFilter.isNotEmpty && !_filter.typeFilter.contains(manhole.type)) {
return false;
}
if (_filter.searchKeyword.isNotEmpty &&
!manhole.code.toLowerCase().contains(_filter.searchKeyword.toLowerCase()) &&
!manhole.address.toLowerCase().contains(_filter.searchKeyword.toLowerCase())) {
return false;
}
return true;
}).toList();
_sortFilteredManholes();
}
- 状态/类型筛选:筛选条件非空时,仅匹配包含的状态/类型,空列表时不筛选
- 关键词搜索:忽略大小写,匹配井盖编号或地址,提升搜索的易用性
- 排序执行:筛选完成后执行排序,保证列表展示顺序符合用户要求
void _sortFilteredManholes() {
switch (_filter.sortBy) {
case SortType.distance:
_filteredManholes.sort((a, b) => _filter.ascending
? a.distance.compareTo(b.distance)
: b.distance.compareTo(a.distance));
break;
case SortType.risk:
_filteredManholes.sort((a, b) => _filter.ascending
? a.riskLevel.compareTo(b.riskLevel)
: b.riskLevel.compareTo(a.riskLevel));
break;
排序逻辑实现:
- 距离排序:按距离升序/降序排列,满足“由近到远”或“由远到近”的查看需求
- 风险排序:按风险等级升序/降序排列,便于优先处理高风险井盖
case SortType.code:
_filteredManholes.sort((a, b) => _filter.ascending
? a.code.compareTo(b.code)
: b.code.compareTo(a.code));
break;
case SortType.district:
_filteredManholes.sort((a, b) => _filter.ascending
? a.district.compareTo(b.district)
: b.district.compareTo(a.district));
break;
- 编号排序:按井盖编号字母序排列,便于台账式查找
- 片区排序:按片区名称字母序排列,便于按区域归类查看
case SortType.lastInspection:
_filteredManholes.sort((a, b) {
final aTime = a.lastInspectionAt ?? DateTime(1970);
final bTime = b.lastInspectionAt ?? DateTime(1970);
return _filter.ascending
? aTime.compareTo(bTime)
: bTime.compareTo(aTime);
});
break;
}
}
- 巡检时间排序:处理空值(默认1970年),避免排序异常,满足按巡检时间筛选的需求
List<NearbyManhole> _generateMockNearbyManholes() {
const districts = ['东城区', '西城区', '南城区', '北城区', '高新区'];
const materials = ['铸铁', '复合材料', '水泥', '不锈钢'];
const manufacturers = ['厂家A', '厂家B', '厂家C', '厂家D'];
return List<NearbyManhole>.generate(25, (index) {
final district = districts[index % districts.length];
final lat = _currentLat + (index % 20 - 10) * 0.001;
final lng = _currentLng + (index % 20 - 10) * 0.001;
Mock数据生成逻辑:
- 基础数据:定义片区、材质、厂家等基础数据列表,保证数据多样性
- 数量控制:生成25条数据,覆盖足够的筛选和排序场景
- 坐标生成:基于当前位置偏移,模拟真实的附近井盖地理位置分布
final distance = _calculateDistance(_currentLat, _currentLng, lat, lng);
return NearbyManhole(
id: 'MANHOLE_${index.toString().padLeft(3, '0')}',
code: 'MH-${(1000 + index).toString()}',
name: '${district}井盖${index + 1}',
district: district,
address: '${district}${index + 1}号路口',
- 距离计算:调用
_calculateDistance方法,基于经纬度计算真实的球面距离 - ID生成:固定前缀+补零数字,保证ID格式统一且唯一
- 名称/地址:按片区+序号的格式生成,符合真实井盖的命名习惯
latitude: lat,
longitude: lng,
riskLevel: 0.1 + (index % 90) / 100.0,
status: ManholeStatus.values[index % ManholeStatus.values.length],
type: ManholeType.values[index % ManholeType.values.length],
distance: distance,
- 风险值生成:0.1~1.0的范围,覆盖低、中、高风险,避免全为0或1的极端情况
- 状态/类型分配:循环分配枚举值,保证各状态/类型都有数据,便于测试筛选功能
createdAt: DateTime.now().subtract(Duration(days: index % 365)),
lastInspectionAt: DateTime.now().subtract(Duration(days: index % 30)),
material: materials[index % materials.length],
manufacturer: manufacturers[index % manufacturers.length],
properties: {
'depth': 2.0 + (index % 10) * 0.1,
'diameter': 0.6 + (index % 5) * 0.05,
'weight': 40 + (index % 80),
},
);
});
}
- 时间生成:创建时间和巡检时间按天偏移,模拟真实的时间分布
- 扩展属性:深度、直径、重量等属性按固定范围偏移,符合真实井盖的参数范围
double _calculateDistance(double lat1, double lng1, double lat2, double lng2) {
const double earthRadius = 6371000; // 地球半径(米)
final double dLat = _toRadians(lat2 - lat1);
final double dLng = _toRadians(lng2 - lng1);
final double a = sin(dLat / 2) * sin(dLat / 2) +
cos(_toRadians(lat1)) * cos(_toRadians(lat2)) *
sin(dLng / 2) * sin(dLng / 2);
距离计算逻辑:
- 球面距离公式:使用Haversine公式计算两点间的球面距离,符合真实的地理距离计算规则
- 单位转换:地球半径以米为单位,后续转换为公里,符合“附近井盖”的距离展示习惯
- 弧度转换:经纬度差值转换为弧度,保证三角函数计算的准确性
final double c = 2 * atan2(sqrt(a), sqrt(1 - a));
return earthRadius * c / 1000; // 转换为公里
}
double _toRadians(double degrees) {
return degrees * pi / 180;
}
- 公式收尾:完成Haversine公式计算,转换为公里单位,便于用户理解
- 弧度转换方法:抽离为独立方法,提升代码复用性和可读性
void _recalculateDistances() {
for (final manhole in _nearbyManholes) {
final distance = _calculateDistance(
_currentLat,
_currentLng,
manhole.latitude,
manhole.longitude,
);
// 更新距离(这里需要使用可变对象或重新创建对象)
}
}
距离重计算逻辑:
- 遍历所有井盖:用户位置变更时,重新计算每个井盖的距离
- 备注说明:提示需要使用可变对象或重建对象,因为当前模型字段为final,保证代码的可维护性
void _calculateStats() {
final totalCount = _filteredManholes.length;
final highRiskCount = _filteredManholes.where((m) => m.riskLevel >= 0.7).length;
final maintenanceCount = _filteredManholes.where((m) => m.status == ManholeStatus.maintenance).length;
final averageRisk = totalCount > 0
? _filteredManholes.map((m) => m.riskLevel).reduce((a, b) => a + b) / totalCount
: 0.0;
统计数据计算:
- 基础统计:总数、高风险数、待维护数,直接通过过滤和计数实现
- 平均风险:处理空列表情况,避免除以0异常,保证统计数据的有效性
final maxDistance = _filteredManholes.isEmpty
? 0.0
: _filteredManholes.map((m) => m.distance).reduce((a, b) => a > b ? a : b);
final districtDistribution = <String, int>{};
for (final manhole in _filteredManholes) {
districtDistribution[manhole.district] = (districtDistribution[manhole.district] ?? 0) + 1;
}
- 极值计算:最大距离通过reduce方法获取,处理空列表情况
- 片区分布:遍历筛选后的数据,统计各片区井盖数量,生成分布Map
_stats = NearbyStats(
totalCount: totalCount,
highRiskCount: highRiskCount,
maintenanceCount: maintenanceCount,
averageRisk: averageRisk,
maxDistance: maxDistance,
districtDistribution: districtDistribution,
);
}
}
- 统计对象生成:将计算结果封装为
NearbyStats对象,便于UI层统一获取和展示统计数据
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)