flutter_for_openharmony城市井盖地图app实战+附近井盖实现

1. 这个功能解决什么问题

现场巡检时,运维人员核心诉求是快速定位符合条件的井盖点位,"附近井盖"功能正是为解决这类场景痛点设计:

  • 片区下拉:巡检人员通常按行政区划分责任范围,下拉筛选可快速聚焦当前负责片区,避免跨区域无效查找
  • 风险滑块:高风险井盖(如破损、渗漏)需要优先处理,滑块可精准筛选出高于指定风险阈值的点位
  • 列表展示:核心信息一站式呈现,编号便于台账核对,片区/地址定位物理位置,风险百分比直观判断紧急程度
  • 点击进入详情:复用已有详情页组件,减少重复开发,保证页面交互逻辑统一

这个页面是典型的"筛选+列表"组合,涵盖了 Flutter 状态管理、UI 组件复用、数据过滤等核心知识点,适合作为入门级实战示例。

2. 相关文件一览

功能落地涉及三个核心文件,职责划分清晰:

  • lib/feature_pages.dart:核心页面 NearbyCoversPage 实现,包含UI布局、交互逻辑、筛选逻辑
  • lib/mock_data.dartbuildMockCovers 方法生成模拟数据,避免依赖真实接口即可调试功能
  • lib/models.dartManholeCover 数据模型定义,规范井盖数据结构

3. Mock 数据构造

在开发初期,真实接口未就绪时,Mock 数据是高效验证功能的关键。lib/mock_data.dart 中构造了18条点位数据,分布在5个片区,核心设计思路如下:

dart 复制代码
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之间均匀分布,既覆盖低、中、高风险,又能验证滑块筛选的准确性
dart 复制代码
    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 实现,因为涉及筛选条件的状态变更:

dart 复制代码
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. 片区下拉实现

片区下拉选择是核心筛选控件,需保证选项无重复且交互友好:

dart 复制代码
final districts = <String>['全部', ...{for (final e in _all) e.district}];
  • 去重处理:通过 Set 集合({for (final e in _all) e.district})自动去重,避免同一片区多次出现在下拉列表
  • 选项排序:"全部"固定在首位,符合用户操作习惯,便于快速重置筛选条件
  • 集合展开:... 展开运算符将Set转为List,适配 DropdownButtonFormField 的数据格式要求
dart 复制代码
DropdownButtonFormField<String>(
  value: _district,
  decoration: const InputDecoration(
    border: OutlineInputBorder(),
    labelText: '片区',
  ),
  • 样式设计:OutlineInputBorder 边框样式,与Flutter默认表单样式统一,提升UI一致性
  • 选中值绑定:value: _district 实现选中状态与页面状态双向绑定,用户选择后状态实时同步
  • 标签提示:labelText: '片区' 明确控件用途,降低用户理解成本
dart 复制代码
  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. 风险滑块实现

风险滑块用于筛选高于指定阈值的井盖,兼顾交互性和可视化:

dart 复制代码
Text('最小风险: ${(100 * _minRisk).round()}%'),
Slider(
  value: _minRisk,
  onChanged: (v) => setState(() => _minRisk = v),
),

核心设计细节:

  • 数值转换:_minRisk 是0~1的小数,乘以100并取整后展示为百分比,符合用户对"风险值"的认知习惯
  • 实时交互:onChanged 回调直接更新 _minRisk 状态,滑块拖动过程中列表实时过滤,反馈即时
  • 无额外配置:使用Slider默认的0~1取值范围,与风险值的存储格式一致,无需额外的数值映射

7. 列表过滤逻辑

过滤逻辑是页面的核心业务逻辑,需保证条件判断准确且性能高效:

dart 复制代码
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

列表项需清晰展示核心信息,同时保证视觉层次和交互性:

dart 复制代码
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 拼接片区和地址,用 · 分隔,信息层级清晰
dart 复制代码
    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 函数根据风险值返回不同颜色,红/橙/绿分别对应高/中/低风险,视觉提示更明显
dart 复制代码
    onTap: () => Navigator.of(context).push(
      MaterialPageRoute(builder: (_) => PointDetailPage(cover: c)),
    ),
  ),
)
  • 详情跳转:onTap 回调触发页面跳转,将当前井盖对象传入详情页,实现数据传递
  • 路由选择:MaterialPageRoute 是Flutter默认路由,保证跳转动画和交互符合平台规范

9. 风险颜色映射

风险颜色映射函数统一维护阈值与颜色的对应关系,便于后续维护:

dart 复制代码
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. 完整页面代码

dart 复制代码
class NearbyCoversPage extends StatefulWidget {
  const NearbyCoversPage({super.key});

  @override
  State<NearbyCoversPage> createState() => _NearbyCoversPageState();
}
  • 页面基础结构:StatefulWidget 是实现状态变更的基础,构造方法添加 super.key 符合Flutter最佳实践
  • 状态类关联:createState 方法返回自定义状态类,实现页面逻辑与UI分离
dart 复制代码
class _NearbyCoversPageState extends State<NearbyCoversPage> {
  late final List<ManholeCover> _all = buildMockCovers();
  String _district = '全部';
  double _minRisk = 0.0;

  @override
  Widget build(BuildContext context) {
    final districts = <String>['全部', ...{for (final e in _all) e.district}];
  • 状态初始化:在状态类中初始化Mock数据和筛选条件,保证页面加载时状态就绪
  • 下拉选项生成:在 build 方法中生成片区选项,确保每次状态变更后选项列表实时更新
dart 复制代码
    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规范
dart 复制代码
      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 让下拉框占满剩余宽度,保证控件布局均衡
dart 复制代码
                    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 左对齐,符合阅读习惯
dart 复制代码
          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
dart 复制代码
                    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. 附近井盖数据模型

为了更好地管理附近井盖数据,我们需要定义一个完整的数据模型,覆盖运维场景的所有核心信息:

dart 复制代码
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状态管理的最佳实践
dart 复制代码
  final double riskLevel;
  final ManholeStatus status;
  final ManholeType type;
  final double distance;
  final DateTime createdAt;
  • 风险与状态:riskLevel(风险等级)、status(井盖状态)、type(井盖类型),满足筛选和分类需求
  • 距离信息:distance(与当前位置距离),是"附近井盖"功能的核心字段
  • 时间信息:createdAt(创建时间),记录井盖建档时间,便于追溯
dart 复制代码
  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 关键字标记必填字段,强制调用者传入核心数据,避免空值异常
dart 复制代码
    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值
  • 字段完整性:覆盖井盖全生命周期的核心信息,无需频繁修改模型即可适配多数运维场景
dart 复制代码
class NearbyFilter {
  final String district;
  final double minRisk;
  final double maxDistance;
  final List<ManholeStatus> statusFilter;

过滤器模型设计:

  • 基础筛选:district(片区)、minRisk(最小风险)、maxDistance(最大距离),覆盖核心筛选维度
  • 多维度筛选:statusFilter(状态筛选)支持多选,满足复杂的筛选需求
dart 复制代码
  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(升序/降序),满足列表排序需求
  • 默认值设计:所有字段设置合理默认值,避免筛选时出现空值,降低使用成本
dart 复制代码
    this.statusFilter = const [],
    this.typeFilter = const [],
    this.searchKeyword = '',
    this.sortBy = SortType.distance,
    this.ascending = true,
  });
}
  • 空列表默认值:statusFilter/typeFilter 默认空列表,代表不筛选状态/类型
  • 排序默认值:默认按距离升序排列,符合"附近井盖"按距离由近到远展示的核心诉求
dart 复制代码
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(片区分布),展示各片区井盖数量,便于区域管理
dart 复制代码
enum ManholeStatus {
  normal,
  damaged,
  maintenance,
  replaced,
  abandoned,
}

enum ManholeType {
  standard,
  heavy,
  composite,
  smart,
  decorative,
}

enum SortType {
  distance,
  risk,
  code,
  district,
  lastInspection,
}

枚举设计:

  • 状态枚举:覆盖井盖全生命周期状态,从正常使用到废弃,满足状态筛选需求
  • 类型枚举:包含标准、重型、复合材料等常见井盖类型,适配不同场景的井盖分类
  • 排序枚举:支持距离、风险、编号、片区、最后巡检时间排序,覆盖用户核心排序需求

12. 附近井盖状态管理

使用 Provider 管理附近井盖数据,实现状态共享和统一管理:

dart 复制代码
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(错误信息),覆盖数据加载全流程
dart 复制代码
  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方法暴露,避免外部直接修改状态,保证状态管理的可控性
dart 复制代码
  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数据 → 应用筛选条件 → 计算统计数据,流程清晰且符合真实业务逻辑
dart 复制代码
      
      _loading = false;
      notifyListeners();
    } catch (e) {
      _error = e.toString();
      _loading = false;
      notifyListeners();
    }
  }
  • 异常处理:捕获加载过程中的异常,设置错误信息并更新加载状态,避免页面崩溃
  • 状态通知:关键节点调用 notifyListeners,保证UI实时响应状态变更
dart 复制代码
  void updateFilter(NearbyFilter filter) {
    _filter = filter;
    _applyFilter();
    notifyListeners();
  }

  void updateLocation(double lat, double lng) {
    _currentLat = lat;
    _currentLng = lng;
    _recalculateDistances();
    _applyFilter();
    notifyListeners();
  }

状态更新方法:

  • 筛选更新:updateFilter 接收新筛选条件,应用筛选并通知UI更新
  • 位置更新:updateLocation 接收新坐标,重新计算距离后再筛选,保证距离相关的筛选和排序准确
dart 复制代码
  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;
      }

筛选逻辑实现:

  • 片区筛选:非"全部"片区时,仅匹配指定片区的井盖
  • 风险筛选:过滤掉风险等级低于最小阈值的井盖
  • 距离筛选:过滤掉距离超过最大阈值的井盖,保证只展示"附近"的井盖
dart 复制代码
      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();
  }
  • 状态/类型筛选:筛选条件非空时,仅匹配包含的状态/类型,空列表时不筛选
  • 关键词搜索:忽略大小写,匹配井盖编号或地址,提升搜索的易用性
  • 排序执行:筛选完成后执行排序,保证列表展示顺序符合用户要求
dart 复制代码
  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;

排序逻辑实现:

  • 距离排序:按距离升序/降序排列,满足"由近到远"或"由远到近"的查看需求
  • 风险排序:按风险等级升序/降序排列,便于优先处理高风险井盖
dart 复制代码
      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;
  • 编号排序:按井盖编号字母序排列,便于台账式查找
  • 片区排序:按片区名称字母序排列,便于按区域归类查看
dart 复制代码
      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年),避免排序异常,满足按巡检时间筛选的需求
dart 复制代码
  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条数据,覆盖足够的筛选和排序场景
  • 坐标生成:基于当前位置偏移,模拟真实的附近井盖地理位置分布
dart 复制代码
      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格式统一且唯一
  • 名称/地址:按片区+序号的格式生成,符合真实井盖的命名习惯
dart 复制代码
        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的极端情况
  • 状态/类型分配:循环分配枚举值,保证各状态/类型都有数据,便于测试筛选功能
dart 复制代码
        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),
        },
      );
    });
  }
  • 时间生成:创建时间和巡检时间按天偏移,模拟真实的时间分布
  • 扩展属性:深度、直径、重量等属性按固定范围偏移,符合真实井盖的参数范围
dart 复制代码
  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公式计算两点间的球面距离,符合真实的地理距离计算规则
  • 单位转换:地球半径以米为单位,后续转换为公里,符合"附近井盖"的距离展示习惯
  • 弧度转换:经纬度差值转换为弧度,保证三角函数计算的准确性
dart 复制代码
    final double c = 2 * atan2(sqrt(a), sqrt(1 - a));
    return earthRadius * c / 1000; // 转换为公里
  }

  double _toRadians(double degrees) {
    return degrees * pi / 180;
  }
  • 公式收尾:完成Haversine公式计算,转换为公里单位,便于用户理解
  • 弧度转换方法:抽离为独立方法,提升代码复用性和可读性
dart 复制代码
  void _recalculateDistances() {
    for (final manhole in _nearbyManholes) {
      final distance = _calculateDistance(
        _currentLat,
        _currentLng,
        manhole.latitude,
        manhole.longitude,
      );
      // 更新距离(这里需要使用可变对象或重新创建对象)
    }
  }

距离重计算逻辑:

  • 遍历所有井盖:用户位置变更时,重新计算每个井盖的距离
  • 备注说明:提示需要使用可变对象或重建对象,因为当前模型字段为final,保证代码的可维护性
dart 复制代码
  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异常,保证统计数据的有效性
dart 复制代码
    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
dart 复制代码
    _stats = NearbyStats(
      totalCount: totalCount,
      highRiskCount: highRiskCount,
      maintenanceCount: maintenanceCount,
      averageRisk: averageRisk,
      maxDistance: maxDistance,
      districtDistribution: districtDistribution,
    );
  }
}
  • 统计对象生成:将计算结果封装为 NearbyStats 对象,便于UI层统一获取和展示统计数据

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
哈哈浩丶5 分钟前
OP-TEE-OS:综述
android·linux·驱动开发
九狼JIULANG8 分钟前
Flutter SSE 流式响应用 Dio 实现 OpenAI 兼容接口的逐 Token 输出
flutter
恋猫de小郭10 小时前
你是不是觉得 R8 很讨厌,但 Android 为什么选择 R8 ?也许你对 R8 还不够了解
android·前端·flutter
前端不太难10 小时前
Flutter 页面切换后为什么会“状态丢失”或“状态常驻”?
flutter·状态模式
松叶似针11 小时前
Flutter三方库适配OpenHarmony【secure_application】— pubspec.yaml 多平台配置与依赖管理
flutter·harmonyos
城东米粉儿12 小时前
Android Glide 笔记
android
城东米粉儿12 小时前
Android TheRouter 笔记
android
城东米粉儿18 小时前
Android AIDL 笔记
android
城东米粉儿18 小时前
Android 进程间传递大数据 笔记
android
城东米粉儿19 小时前
Android KMP 笔记
android