flutter_for_openharmony城市井盖地图app实战+风险图层实现
本文详细介绍了在开源鸿蒙(OpenHarmony)平台下基于Flutter框架开发城市井盖地图APP中"风险图层"功能的实现方案。主要内容包括: 风险数据模型设计:通过RiskFactor类结构化存储风险评估信息,包含ID、名称、权重、评估时间等核心字段,并定义RiskLevel、RiskLayerType、RiskFactorType三类枚举实现风险数据的分类管理。 状态管理

本文聚焦于开源鸿蒙(OpenHarmony)平台下,基于Flutter框架开发的城市井盖地图APP中“风险图层”功能的实战实现,核心围绕风险点数据模型设计、风险图层状态管理、高级组件开发等核心环节展开,完整还原从数据建模到交互实现的全流程。
一、风险数据模型设计:结构化存储风险信息
风险评估的核心是数据的结构化表达,我们首先定义RiskFactor数据模型,用于精准存储每一项风险因素的详细评估信息:
class RiskFactor {
final String id;
final String name;
final String description;
final double weight;
final double value;
final RiskFactorType type;
final DateTime assessedAt;
final String? assessedBy;
const RiskFactor({
required this.id,
required this.name,
required this.description,
required this.weight,
required this.value,
required this.type,
required this.assessedAt,
this.assessedBy,
});
}
模型设计要点:
- 核心字段全覆盖:包含
id(唯一标识)、name(风险名称)、description(描述)等基础信息; - 量化评估字段:
weight(权重)和value(评分)支持风险值的量化计算; - 溯源字段:
assessedAt(评估时间)、assessedBy(评估人)满足溯源管理需求; - 类型枚举关联:通过
RiskFactorType枚举限定风险类型,避免数据混乱。
为了实现风险数据的分类管理,我们定义三类枚举,分别对应风险等级、图层类型、风险因素类型:
enum RiskLevel {
low,
medium,
high,
critical,
}
enum RiskLayerType {
heat,
density,
cluster,
prediction,
}
enum RiskFactorType {
structural,
environmental,
operational,
maintenance,
}
枚举设计价值:
RiskLevel:将风险划分为低/中/高/紧急四档,适配巡检优先级管理;RiskLayerType:定义热力图/密度图/聚类图/预测图四种图层类型,满足不同可视化需求;RiskFactorType:按结构/环境/运营/维护维度分类风险因素,便于针对性分析。
二、风险图层状态管理:基于Provider实现数据驱动
状态管理是交互功能的核心,我们使用Provider + ChangeNotifier实现风险图层数据的统一管理,先定义核心状态类:
class RiskLayerProvider extends ChangeNotifier {
List<RiskLayer> _layers = [];
List<RiskPoint> _allRiskPoints = [];
List<RiskPoint> _filteredRiskPoints = [];
Set<RiskLevel> _selectedRiskLevels = {RiskLevel.low, RiskLevel.medium, RiskLevel.high, RiskLevel.critical};
bool _loading = false;
String? _error;
DateTime? _lastRefreshTime;
List<RiskLayer> get layers => _layers;
List<RiskPoint> get allRiskPoints => _allRiskPoints;
List<RiskPoint> get filteredRiskPoints => _filteredRiskPoints;
}
状态设计思路:
- 分层存储数据:
_layers(图层列表)、_allRiskPoints(全量风险点)、_filteredRiskPoints(筛选后风险点)分离,降低耦合; - 筛选条件存储:
_selectedRiskLevels用Set存储选中的风险等级,支持多条件筛选; - 状态标识字段:
_loading(加载状态)、_error(错误信息)、_lastRefreshTime(刷新时间)提升交互体验。
核心的loadRiskLayers方法实现数据加载逻辑,包含加载状态管理和异常捕获:
Future<void> loadRiskLayers() async {
_loading = true;
_error = null;
notifyListeners();
try {
await Future.delayed(const Duration(seconds: 1));
_layers = _generateMockLayers();
_allRiskPoints = _generateMockRiskPoints();
_applyRiskLevelFilter();
_lastRefreshTime = DateTime.now();
_loading = false;
notifyListeners();
} catch (e) {
_error = e.toString();
_loading = false;
notifyListeners();
}
}
加载逻辑要点:
- 状态前置更新:加载前先置
_loading = true并通知刷新,保证UI及时响应; - 模拟异步加载:通过
Future.delayed模拟网络请求,适配真实场景; - 数据生成与筛选:调用模拟数据方法后,立即执行筛选逻辑,保证数据一致性;
- 异常捕获:捕获加载过程中的错误,通过
_error字段反馈给UI。
实现风险等级的切换与全选功能,支撑筛选交互:
void toggleRiskLevel(RiskLevel level) {
if (_selectedRiskLevels.contains(level)) {
_selectedRiskLevels.remove(level);
} else {
_selectedRiskLevels.add(level);
}
_applyRiskLevelFilter();
notifyListeners();
}
void setAllRiskLevels(bool selected) {
if (selected) {
_selectedRiskLevels = Set.from(RiskLevel.values);
} else {
_selectedRiskLevels.clear();
}
_applyRiskLevelFilter();
notifyListeners();
}
交互逻辑设计:
toggleRiskLevel:单个风险等级的选中/取消,操作后立即执行筛选并刷新UI;setAllRiskLevels:一键全选/全取消,简化批量操作流程;- 筛选逻辑复用:所有状态变更后都调用
_applyRiskLevelFilter,保证筛选逻辑唯一入口。
筛选核心方法_applyRiskLevelFilter,实现风险点的精准过滤:
void _applyRiskLevelFilter() {
_filteredRiskPoints = _allRiskPoints.where((point) {
return _selectedRiskLevels.contains(point.riskLevel);
}).toList();
}
筛选逻辑说明:
- 基于
where方法遍历全量风险点,匹配选中的风险等级; - 结果转为列表存储到
_filteredRiskPoints,供UI层读取; - 纯数据逻辑,与UI解耦,便于后续扩展多维度筛选。
模拟图层数据生成方法,为功能测试提供基础数据:
List<RiskLayer> _generateMockLayers() {
return [
RiskLayer(
id: 'layer_heat',
name: '风险热力图',
description: '基于风险值的热力分布图层',
riskLevel: RiskLevel.medium,
riskScore: 65.0,
riskPoints: [],
layerType: RiskLayerType.heat,
isVisible: true,
opacity: 0.7,
overlayColor: Colors.red.withOpacity(0.5),
createdAt: DateTime.now(),
),
];
}
模拟数据设计:
- 包含图层核心属性:ID、名称、描述、风险等级、可视化参数(透明度、颜色)等;
- 预设默认状态:如
isVisible: true默认显示热力图,贴近实际使用场景; - 简化数据量:单图层示例降低代码冗余,实际可扩展多图层配置。
风险点模拟生成方法,批量生成带随机属性的风险点数据:
List<RiskPoint> _generateMockRiskPoints() {
final districts = ['东城区', '西城区', '南城区', '北城区', '高新区'];
final rng = Random(42);
return List.generate(30, (index) {
final riskValue = rng.nextDouble();
final riskLevel = _getRiskLevel(riskValue);
return RiskPoint(
id: 'RISK_POINT_${index.toString().padLeft(3, '0')}',
name: '风险点${index + 1}',
latitude: 39.9042 + (rng.nextDouble() - 0.5) * 0.1,
longitude: 116.4074 + (rng.nextDouble() - 0.5) * 0.1,
riskValue: riskValue,
riskLevel: riskLevel,
);
});
}
风险点生成要点:
- 区域随机分配:基于预设片区列表,均匀分配风险点所属区域;
- 坐标随机偏移:在固定经纬度基础上小幅偏移,模拟真实地理位置;
- 风险值与等级关联:通过
_getRiskLevel将随机风险值映射为等级,保证数据逻辑一致; - 固定随机种子:
Random(42)保证每次生成的数据一致,便于测试。
风险因素模拟生成方法,为每个风险点补充详细评估维度:
List<RiskFactor> _generateMockRiskFactors(Random rng) {
return [
RiskFactor(
id: 'factor_1',
name: '结构完整性',
description: '井盖结构完整性评估',
weight: 0.4,
value: 0.3 + rng.nextDouble() * 0.4,
type: RiskFactorType.structural,
assessedAt: DateTime.now(),
assessedBy: '技术员${rng.nextInt(5) + 1}',
),
];
}
风险因素设计:
- 权重分配合理:结构完整性权重0.4,体现核心风险维度的重要性;
- 评分区间可控:
0.3 + rng.nextDouble() * 0.4限定评分在0.3~0.7之间,符合中风险区间特征; - 评估人随机分配:模拟不同技术员的评估结果,贴近实际巡检场景。
风险等级映射方法,实现数值到等级的标准化转换:
RiskLevel _getRiskLevel(double riskValue) {
if (riskValue >= 0.8) return RiskLevel.critical;
if (riskValue >= 0.6) return RiskLevel.high;
if (riskValue >= 0.3) return RiskLevel.medium;
return RiskLevel.low;
}
等级映射规则:
- 阈值分层清晰:0.8(紧急)、0.6(高)、0.3(中)、<0.3(低),适配巡检优先级;
- 顺序判断逻辑:从高阈值到低阈值判断,避免逻辑漏洞;
- 规则统一管理:单独封装方法,后续阈值调整只需修改此处。
三、高级风险图层组件:可视化与交互一体化
创建高级风险图层组件,整合状态管理与UI展示:
class AdvancedRiskLayerWidget extends StatefulWidget {
const AdvancedRiskLayerWidget({super.key});
State<AdvancedRiskLayerWidget> createState() => _AdvancedRiskLayerWidgetState();
}
class _AdvancedRiskLayerWidgetState extends State<AdvancedRiskLayerWidget> {
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
final provider = Provider.of<RiskLayerProvider>(context, listen: false);
provider.loadRiskLayers();
});
}
}
组件初始化要点:
- 延迟加载数据:通过
addPostFrameCallback保证Widget渲染完成后再加载数据,避免上下文异常; - 非监听模式获取Provider:
listen: false避免不必要的重建,提升性能; - 初始化触发加载:组件创建时自动加载数据,保证首次进入有数据展示。
组件核心构建方法,实现加载状态、空数据、正常数据的UI适配:
Widget build(BuildContext context) {
return Consumer<RiskLayerProvider>(
builder: (context, provider, child) {
return Scaffold(
appBar: AppBar(
title: const Text('风险图层'),
actions: [
IconButton(
onPressed: () => _showLayerDialog(context, provider),
icon: const Icon(Icons.layers),
),
IconButton(
onPressed: provider.refreshData,
icon: const Icon(Icons.refresh),
),
],
),
body: provider.loading
? const Center(child: CircularProgressIndicator())
: provider.filteredRiskPoints.isEmpty
? const Center(child: Text('暂无风险数据'))
: _buildContent(context, provider),
);
},
);
}
UI构建逻辑:
- Consumer监听状态:实时响应Provider的状态变更,自动重建UI;
- 加载状态展示:
CircularProgressIndicator提示用户数据加载中; - 空数据兜底:无筛选结果时显示友好提示,提升用户体验;
- 功能型AppBar:包含图层管理和数据刷新按钮,操作入口清晰。
内容区域构建方法,整合概览、筛选、列表等核心模块:
Widget _buildContent(BuildContext context, RiskLayerProvider provider) {
return RefreshIndicator(
onRefresh: provider.refreshData,
child: Column(
children: [
_buildSummaryCard(context, provider),
const SizedBox(height: 8),
_buildRiskLevelFilter(context, provider),
const SizedBox(height: 8),
_buildLayerToggle(context, provider),
const SizedBox(height: 8),
Expanded(
child: _buildRiskPointList(context, provider),
),
],
),
);
}
布局设计要点:
- 下拉刷新支持:
RefreshIndicator包裹内容,实现数据手动刷新; - 垂直布局分层:概览卡片→筛选栏→图层切换→风险点列表,符合用户视觉流;
- 间距合理:
SizedBox(height: 8)分隔模块,提升UI层次感; - 列表占满剩余空间:
Expanded保证列表自适应屏幕高度。
风险概览卡片组件,可视化展示核心统计指标:
Widget _buildSummaryCard(BuildContext context, RiskLayerProvider provider) {
final totalPoints = provider.allRiskPoints.length;
final filteredPoints = provider.filteredRiskPoints.length;
final highRiskPoints = provider.filteredRiskPoints.where((p) => p.riskLevel == RiskLevel.high || p.riskLevel == RiskLevel.critical).length;
return Card(
margin: const EdgeInsets.all(16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Text('风险概览', style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 12),
],
),
),
);
}
概览卡片设计:
- 核心指标提取:总风险点、筛选结果、高风险数量,直击业务核心;
- 视觉样式优化:卡片包裹+内边距,提升质感;
- 标题样式强化:加粗+大号字体,突出模块标题;
- 数据实时计算:基于Provider数据动态计算,保证数据准确性。
四、实战场景:按风险等级筛选井盖(核心业务功能)
1. 这个功能解决什么问题
巡检现场需要“按风险等级”快速筛选井盖,核心痛点与解决方案:
- 高风险(>=70%):优先处理,保障公共安全;
- 中风险(30%~70%):关注跟进,制定巡检计划;
- 低风险(<30%):定期巡检,降低人力成本;
- 动态切换:随时调整筛选条件,适配现场巡检需求;
- 列表展示:关键信息与风险标签,快速定位问题井盖。
该页面是典型的“多选过滤”场景,是FilterChip + where组合的最佳实践示例。
2. 相关文件一览
lib/feature_pages.dart(RiskLayerPage):核心页面逻辑,包含筛选与列表展示;lib/mock_data.dart(buildMockCovers):模拟风险点数据,支撑功能测试。
3. 风险等级状态管理
RiskLayerPage通过三个布尔值管理风险等级的选中状态,简化状态逻辑:
class _RiskLayerPageState extends State<RiskLayerPage> {
bool high = true;
bool mid = true;
bool low = true;
}
状态设计说明:
- 默认全选:初始状态
high/mid/low均为true,展示所有风险点; - 单状态单职责:每个布尔值对应一个风险档位,逻辑清晰,便于维护;
- 局部状态管理:仅在页面内生效,无需全局状态,降低复杂度。
4. 过滤逻辑实现
核心过滤逻辑基于where方法实现,状态变更时自动重新计算:
final all = buildMockCovers();
final list = all.where((e) {
if (e.risk >= 0.7) return high;
if (e.risk >= 0.3) return mid;
return low;
}).toList(growable: false);
过滤逻辑解析:
- 先获取全量数据:
buildMockCovers()生成模拟风险点列表; - 条件判断顺序:从高阈值到低阈值,避免逻辑覆盖;
- 状态关联筛选:通过布尔值控制对应风险等级的显示/隐藏;
- 不可变列表:
growable: false创建不可变列表,提升性能。
5. FilterChip 交互实现
页面顶部通过Wrap + FilterChip实现风险等级的多选交互:
Padding(
padding: const EdgeInsets.all(12),
child: Wrap(
spacing: 8,
children: [
FilterChip(
selected: high,
onSelected: (v) => setState(() => high = v),
label: const Text('高风险(>=70%)'),
),
FilterChip(
selected: mid,
onSelected: (v) => setState(() => mid = v),
label: const Text('中风险(30~70%)'),
),
],
),
)
交互设计要点:
Wrap布局:空间不足时自动换行,适配不同屏幕尺寸;- 选中状态关联:
selected绑定对应布尔值,视觉反馈选中状态; - 状态更新:
onSelected通过setState更新状态,触发UI重建; - 标签清晰:标注风险阈值,用户可明确知晓筛选范围。
6. 列表项 UI 设计
通过ListView.separated展示筛选后的风险点列表,优化视觉体验:
ListView.separated(
itemCount: list.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, i) {
final c = list[i];
return ListTile(
leading: Icon(Icons.place_rounded, color: _riskColor(c.risk)),
title: Text(c.code),
subtitle: Text('${c.district} · ${c.address}'),
trailing: Text(_riskLabel(c.risk)),
);
},
)
列表设计要点:
- 分割线优化:
separatorBuilder添加分割线,区分列表项; - 图标颜色映射:
leading图标颜色随风险等级变化,视觉直观; - 核心信息展示:标题(编号)、副标题(区域+地址)、尾部(风险标签);
- 数据绑定:直接使用筛选后的列表数据,保证展示与筛选结果一致。
7. 风险颜色映射
统一维护风险值与颜色的映射关系,保证视觉一致性:
Color _riskColor(double risk) {
if (risk >= 0.7) return Colors.red;
if (risk >= 0.3) return Colors.orange;
return Colors.green;
}
颜色映射规则:
- 高风险(红):警示性强,突出紧急处理需求;
- 中风险(橙):提醒关注,介于警示与常规之间;
- 低风险(绿):常规状态,无视觉警示;
- 单点维护:所有UI的风险颜色均调用此方法,便于统一调整。
8. 风险标签映射
将数值型风险值转换为文字标签,提升可读性:
String _riskLabel(double risk) {
if (risk >= 0.7) return '高风险';
if (risk >= 0.3) return '中风险';
return '低风险';
}
标签设计说明:
- 与颜色映射阈值一致:保证文字与颜色标签的逻辑统一;
- 简洁易懂:使用用户易理解的中文标签,降低认知成本;
- 复用性强:列表、详情页等场景均可调用,保证表述一致。
9. 小结
“风险图层”页面实现了:
- 风险等级多选过滤(
FilterChip):直观的筛选交互,适配现场巡检操作习惯; - 动态列表过滤(
where+setState):状态驱动的筛选逻辑,保证数据实时性; - 风险颜色与标签统一映射:视觉与文字标签的一致性,提升用户体验;
- 分割线与图标视觉优化:细节设计提升UI质感,降低信息认知成本。
后续如果要接入真实地图 SDK,只需把列表替换成地图图层,过滤逻辑完全不需要改动。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐
所有评论(0)