Flutter 框架跨平台鸿蒙开发 - 附近停车场查询
运行效果图String id;String?imageUrl;String?openHours;基础信息包括ID、名称、地址、经纬度坐标;运营信息包括类型、总车位、空位数、价格、评分;服务信息包括设施列表、营业状态、营业时间;距离信息记录与用户的距离,用于排序展示。本项目成功实现了一款功能完整、界面现代的停车场查询应用,涵盖了位置服务类应用开发的核心要素。通过Flutter框架的应用,实现了跨平台
Flutter附近停车场查询
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
项目概述
运行效果图


一、项目背景与目标
随着城市化进程的加速和汽车保有量的持续增长,停车难已成为城市交通的重要痛点。驾驶员在陌生区域往往需要花费大量时间寻找停车位,不仅浪费时间和燃油,还增加了交通拥堵和环境污染。本项目基于Flutter框架开发一款附近停车场查询应用,旨在帮助用户快速找到附近的停车场,实时了解空位情况和收费标准,提升停车效率和出行体验。
项目的核心目标涵盖多个维度:构建完整的停车场信息展示系统,实现精准的位置定位和距离计算,设计直观的用户界面,打造流畅的交互体验,以及确保应用的稳定性和性能表现。通过本项目的开发,不仅能够深入理解Flutter在位置服务类应用中的应用,更能掌握列表渲染、筛选排序、底部弹窗等核心技术要点。
二、技术选型与架构设计
技术栈分析
本项目选用Flutter作为开发框架,主要基于以下考量:Flutter的跨平台特性能够同时支持Android和iOS平台,降低开发成本;声明式UI编程范式能够高效构建复杂的列表和弹窗界面;丰富的Widget组件库为应用UI开发提供了坚实基础;优秀的性能表现确保了列表滚动的流畅性。
Dart语言作为Flutter的开发语言,具备强类型、异步编程支持、优秀的性能表现等特性。项目采用单文件架构,将所有应用逻辑集中在main.dart文件中,这种设计既便于代码管理,又利于理解应用整体架构。
架构层次划分
应用架构采用分层设计思想,主要分为以下几个层次:
数据模型层:定义应用中的核心数据结构,包括ParkingLot(停车场实体)、ParkingType(停车场类型)、SortType(排序类型)等类和枚举。这些模型类封装了停车场的状态和行为,构成了应用逻辑的基础。
业务逻辑层:实现应用的核心功能逻辑,包括停车场加载、筛选过滤、排序计算、详情展示等。这一层是应用的心脏,决定了应用的功能性和可用性。
渲染表现层:负责应用界面的绘制和UI展示,使用Flutter的Material Design组件库实现现代化的界面设计,通过ListView和ModalBottomSheet组件实现列表和弹窗展示。
状态管理层:管理应用的各种状态,包括停车场列表、筛选条件、排序方式、当前页面索引等,确保应用状态的一致性和可预测性。
核心功能模块详解
一、停车场数据模型
数据属性定义
停车场实体封装了完整的信息属性:
class ParkingLot {
String id;
String name;
String address;
double latitude;
double longitude;
ParkingType type;
int totalSpaces;
int availableSpaces;
double pricePerHour;
double rating;
int reviewCount;
List<String> features;
String? imageUrl;
bool isOpen;
String? openHours;
double distance;
}
基础信息包括ID、名称、地址、经纬度坐标;运营信息包括类型、总车位、空位数、价格、评分;服务信息包括设施列表、营业状态、营业时间;距离信息记录与用户的距离,用于排序展示。
计算属性实现
停车场实体提供了多个计算属性,动态计算展示所需的数据:
类型信息:根据停车场类型返回对应的文本、图标和颜色:
String get typeText {
switch (type) {
case ParkingType.underground:
return '地下停车场';
case ParkingType.ground:
return '地面停车场';
case ParkingType.roadside:
return '路侧停车';
case ParkingType.mall:
return '商场停车场';
}
}
IconData get typeIcon {
switch (type) {
case ParkingType.underground:
return Icons.downhill_skiing;
case ParkingType.ground:
return Icons.local_parking;
case ParkingType.roadside:
return Icons.add_road;
case ParkingType.mall:
return Icons.shopping_cart;
}
}
Color get typeColor {
switch (type) {
case ParkingType.underground:
return Colors.indigo;
case ParkingType.ground:
return Colors.green;
case ParkingType.roadside:
return Colors.orange;
case ParkingType.mall:
return Colors.purple;
}
}
占用率计算:计算当前停车位的占用比例:
double get occupancyRate =>
totalSpaces > 0 ? (totalSpaces - availableSpaces) / totalSpaces : 0;
占用率等于已用车位除以总车位,用于判断停车场的拥挤程度。
可用性状态:根据占用率返回对应的颜色和文本:
Color get availabilityColor {
if (occupancyRate < 0.5) return Colors.green;
if (occupancyRate < 0.8) return Colors.orange;
return Colors.red;
}
String get availabilityText {
if (availableSpaces == 0) return '已满';
if (occupancyRate < 0.5) return '充足';
if (occupancyRate < 0.8) return '紧张';
return '即将满';
}
占用率低于50%显示绿色表示充足,50%-80%显示橙色表示紧张,高于80%显示红色表示即将满或已满。
二、搜索筛选系统
搜索功能实现
搜索功能支持按名称和地址进行模糊匹配:
List<ParkingLot> get _filteredParkingLots {
var result = List<ParkingLot>.from(_parkingLots);
if (_searchQuery.isNotEmpty) {
result = result.where((p) {
return p.name.toLowerCase().contains(_searchQuery.toLowerCase()) ||
p.address.toLowerCase().contains(_searchQuery.toLowerCase());
}).toList();
}
return result;
}
搜索时将查询字符串和目标字符串都转换为小写,实现大小写不敏感的匹配。搜索范围包括停车场名称和地址,提供灵活的查找方式。
筛选功能实现
筛选功能支持按类型和空位状态进行过滤:
if (_filterType != null) {
result = result.where((p) => p.type == _filterType).toList();
}
if (_showAvailableOnly) {
result = result.where((p) => p.availableSpaces > 0).toList();
}
类型筛选通过枚举值匹配实现,空位筛选通过判断空位数是否大于零实现。两种筛选条件可以叠加,实现精确过滤。
排序功能实现
排序功能支持按距离、价格、空位、评分四种方式排序:
switch (_sortType) {
case SortType.distance:
result.sort((a, b) => a.distance.compareTo(b.distance));
break;
case SortType.price:
result.sort((a, b) => a.pricePerHour.compareTo(b.pricePerHour));
break;
case SortType.available:
result.sort((a, b) => b.availableSpaces.compareTo(a.availableSpaces));
break;
case SortType.rating:
result.sort((a, b) => b.rating.compareTo(a.rating));
break;
}
距离和价格按升序排序,空位和评分按降序排序,符合用户的实际需求。
三、停车场列表展示
列表卡片设计
停车场列表使用卡片布局,每个卡片展示完整的信息:
Widget _buildParkingCard(ParkingLot parking) {
return Card(
margin: const EdgeInsets.only(bottom: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: InkWell(
onTap: () => _showParkingDetail(parking),
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 头部:类型图标、名称、状态标签
// 地址:位置图标、地址文本、距离
// 信息:空位、价格、评分、导航按钮
// 标签:服务设施列表
],
),
),
),
);
}
卡片采用圆角设计,点击时显示水波纹效果。内容分为四个区域:头部显示类型图标、名称和状态标签;地址行显示位置信息和距离;信息行显示空位、价格、评分和导航按钮;标签行显示服务设施。
状态标签展示
状态标签根据可用性显示不同颜色:
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: parking.availabilityColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Text(
parking.availabilityText,
style: TextStyle(
color: parking.availabilityColor,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
)
标签使用圆角胶囊样式,背景色为对应颜色的10%透明度,文字使用对应颜色,提供清晰的视觉反馈。
服务设施标签
服务设施使用标签组展示:
Wrap(
spacing: 8,
runSpacing: 4,
children: parking.features.take(4).map((feature) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: BorderRadius.circular(10),
),
child: Text(
feature,
style: TextStyle(color: Colors.grey.shade700, fontSize: 11),
),
);
}).toList(),
)
使用Wrap组件实现自动换行,最多显示4个标签,避免卡片过长。标签使用灰色背景,简洁美观。
四、详情弹窗展示
弹窗结构设计
详情弹窗使用DraggableScrollableSheet实现可拖拽效果:
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => DraggableScrollableSheet(
initialChildSize: 0.6,
minChildSize: 0.3,
maxChildSize: 0.9,
expand: false,
builder: (context, scrollController) => Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
child: SingleChildScrollView(
controller: scrollController,
child: Column(
children: [
// 拖拽指示器
// 停车场详情内容
],
),
),
),
),
);
弹窗初始高度为屏幕的60%,最小高度30%,最大高度90%,用户可以通过拖拽调整高度。顶部有圆角和拖拽指示器,符合Material Design规范。
详情信息展示
详情弹窗展示完整的停车场信息:
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 头部:类型图标和名称
Row(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: parking.typeColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(parking.typeIcon, color: parking.typeColor, size: 32),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(parking.name, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
Text(parking.typeText, style: TextStyle(color: Colors.grey.shade600)),
],
),
),
],
),
// 地址、营业时间、空位、价格、评分
// 服务设施
// 操作按钮
],
)
信息按层次展示,头部突出显示名称和类型,中间展示详细属性,底部提供操作按钮。
操作按钮设计
详情弹窗底部提供电话和导航两个操作按钮:
Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: () => _callParking(parking),
icon: const Icon(Icons.phone),
label: const Text('电话'),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25),
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton.icon(
onPressed: () => _navigateToParking(parking),
icon: const Icon(Icons.navigation),
label: const Text('导航'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25),
),
),
),
),
],
)
电话按钮使用描边样式,导航按钮使用填充样式,视觉上突出导航功能。按钮使用圆角胶囊形状,符合现代UI设计趋势。
五、筛选弹窗实现
弹窗结构设计
筛选弹窗使用StatefulBuilder管理内部状态:
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => StatefulBuilder(
builder: (context, setModalState) => Container(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题和重置按钮
// 类型筛选
// 空位筛选
// 确定按钮
],
),
),
),
);
StatefulBuilder允许弹窗内部独立管理状态,修改筛选条件时只刷新弹窗内容,不影响主页面。
类型筛选实现
类型筛选使用FilterChip组件:
Wrap(
spacing: 8,
children: [
_buildFilterChip('全部', null, setModalState),
_buildFilterChip('地下停车场', ParkingType.underground, setModalState),
_buildFilterChip('地面停车场', ParkingType.ground, setModalState),
_buildFilterChip('路侧停车', ParkingType.roadside, setModalState),
_buildFilterChip('商场停车场', ParkingType.mall, setModalState),
],
)
Widget _buildFilterChip(String label, ParkingType? type, StateSetter setModalState) {
final isSelected = _filterType == type;
return FilterChip(
label: Text(label),
selected: isSelected,
onSelected: (_) {
setModalState(() => _filterType = type);
setState(() => _filterType = type);
},
selectedColor: Colors.blue.shade100,
checkmarkColor: Colors.blue,
);
}
FilterChip组件自带选中状态样式,选中时显示蓝色背景和对勾图标。点击时同时更新弹窗状态和主页面状态,确保数据一致性。
空位筛选实现
空位筛选使用Switch组件:
Row(
children: [
const Text('仅显示有空位', style: TextStyle(fontWeight: FontWeight.bold)),
const Spacer(),
Switch(
value: _showAvailableOnly,
onChanged: (value) {
setModalState(() => _showAvailableOnly = value);
setState(() => _showAvailableOnly = value);
},
activeColor: Colors.blue,
),
],
)
开关组件提供直观的开闭状态切换,激活时显示蓝色,与整体设计风格统一。
六、地图视图实现
地图占位设计
由于实际地图需要集成第三方SDK,当前版本使用占位视图:
Container(
color: Colors.grey.shade200,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.map, size: 80, color: Colors.grey.shade400),
const SizedBox(height: 16),
Text('地图视图', style: TextStyle(fontSize: 18, color: Colors.grey.shade500)),
const SizedBox(height: 8),
Text('显示附近停车场位置', style: TextStyle(color: Colors.grey.shade400)),
],
),
),
)
占位视图使用灰色背景和地图图标,提示用户这是地图区域,后续可以集成高德地图或百度地图SDK。
统计信息卡片
地图视图底部显示统计信息卡片:
Positioned(
bottom: 16,
left: 16,
right: 16,
child: Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Row(
children: [
const Icon(Icons.location_on, color: Colors.blue),
const SizedBox(width: 8),
const Expanded(
child: Text(
'当前位置: 朝阳区建国路88号',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
],
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildMapStat('附近', '${_parkingLots.length}个', Colors.blue),
_buildMapStat('空位', '${_parkingLots.fold(0, (sum, p) => sum + p.availableSpaces)}个', Colors.green),
_buildMapStat('平均价格', '¥${(_parkingLots.fold(0.0, (sum, p) => sum + p.pricePerHour) / _parkingLots.length).toStringAsFixed(1)}/时', Colors.orange),
],
),
],
),
),
),
)
统计信息包括附近停车场数量、总空位数和平均价格,使用fold方法进行聚合计算,提供快速的数据概览。
UI界面开发
一、主界面布局
主界面采用底部导航栏设计,包含四个主要页面:
BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) => setState(() => _currentIndex = index),
selectedItemColor: Colors.blue,
unselectedItemColor: Colors.grey,
type: BottomNavigationBarType.fixed,
items: const [
BottomNavigationBarItem(icon: Icon(Icons.search), label: '搜索'),
BottomNavigationBarItem(icon: Icon(Icons.map), label: '地图'),
BottomNavigationBarItem(icon: Icon(Icons.favorite), label: '收藏'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: '我的'),
],
)
底部导航栏使用蓝色作为选中颜色,固定类型确保所有标签都显示文字。四个页面分别是搜索、地图、收藏和个人中心,覆盖了应用的主要功能入口。
二、搜索页面布局
搜索页面分为三个区域:搜索栏、排序栏和列表区域:
Column(
children: [
_buildSearchBar(),
_buildSortBar(),
Expanded(
child: _filteredParkingLots.isEmpty
? _buildEmptyState()
: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _filteredParkingLots.length,
itemBuilder: (context, index) {
return _buildParkingCard(_filteredParkingLots[index]);
},
),
),
],
)
搜索栏固定在顶部,排序栏紧随其后,列表区域占据剩余空间。空状态时显示提示信息,有数据时显示列表。
三、搜索栏设计
搜索栏使用圆角输入框:
Container(
padding: const EdgeInsets.all(16),
color: Colors.white,
child: TextField(
decoration: InputDecoration(
hintText: '搜索停车场名称或地址',
prefixIcon: const Icon(Icons.search),
suffixIcon: _searchQuery.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
setState(() => _searchQuery = '');
},
)
: null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
),
filled: true,
fillColor: Colors.grey.shade100,
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
),
onChanged: (value) {
setState(() => _searchQuery = value);
},
),
)
输入框使用灰色填充背景和圆角边框,左侧显示搜索图标,有输入时右侧显示清除按钮。输入变化时实时更新搜索结果。
四、排序栏设计
排序栏使用FilterChip组件:
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
color: Colors.white,
child: Row(
children: [
const Text('排序: ', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(width: 8),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
_buildSortChip('距离', SortType.distance),
const SizedBox(width: 8),
_buildSortChip('价格', SortType.price),
const SizedBox(width: 8),
_buildSortChip('空位', SortType.available),
const SizedBox(width: 8),
_buildSortChip('评分', SortType.rating),
],
),
),
),
],
),
)
排序选项使用水平滚动布局,避免屏幕宽度不足时换行。选中的排序方式显示蓝色背景和对勾图标。
性能优化方案
一、列表渲染优化
列表使用ListView.builder实现按需渲染:
ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _filteredParkingLots.length,
itemBuilder: (context, index) {
return _buildParkingCard(_filteredParkingLots[index]);
},
)
只有可见区域的卡片才会被创建和渲染,大幅降低了内存占用和渲染开销。对于大量数据的场景,性能提升明显。
二、筛选计算优化
筛选计算使用getter方法,只在访问时计算:
List<ParkingLot> get _filteredParkingLots {
var result = List<ParkingLot>.from(_parkingLots);
// 筛选逻辑
return result;
}
筛选结果不会缓存,但Flutter的重建机制会避免不必要的重复计算。对于复杂的筛选逻辑,可以考虑使用computed属性或状态管理方案进行优化。
三、状态管理优化
应用状态采用setState()方法管理,确保状态的一致性和可预测性:
setState(() {
_sortType = type;
});
状态更新批量执行,减少了不必要的重绘次数。对于复杂的状态管理,可以考虑使用Provider、Riverpod等状态管理方案。
测试方案与步骤
一、功能测试
功能测试旨在验证应用各项功能是否按预期工作。测试用例应覆盖所有核心功能模块,确保应用逻辑的正确性。
搜索功能测试:验证搜索框输入是否正常;测试按名称搜索是否正确;测试按地址搜索是否正确;检查清除按钮是否有效。
筛选功能测试:验证类型筛选是否生效;测试空位筛选是否正确;检查重置按钮是否有效。
排序功能测试:验证距离排序是否正确;测试价格排序是否正确;测试空位排序是否正确;测试评分排序是否正确。
详情展示测试:验证详情弹窗是否正常显示;测试弹窗拖拽是否流畅;检查信息展示是否完整。
二、性能测试
性能测试关注应用的运行效率,确保在各种情况下都能流畅运行。
列表滚动测试:测试大量数据时的列表滚动性能,确保无卡顿。
搜索响应测试:测试搜索输入的响应速度,确保实时更新。
弹窗动画测试:测试弹窗打开和关闭的动画流畅度。
三、兼容性测试
兼容性测试确保应用在不同环境下都能正常运行。
多平台测试:在Android、iOS等平台分别测试应用功能,验证跨平台一致性。
屏幕适配测试:测试应用在不同屏幕尺寸下的表现,确保布局正确。
横竖屏测试:测试应用在横竖屏切换时的表现,确保界面正常。
四、用户体验测试
用户体验测试关注应用的易用性和美观度。
操作便捷性测试:邀请用户试用应用,收集对操作流程的反馈,评估交互设计的合理性。
视觉体验测试:评估应用的视觉效果,包括色彩搭配、图标设计、布局美观度等。
响应速度测试:测试应用的响应速度,确保操作反馈及时,提升用户体验。
项目总结与展望
一、项目成果总结
本项目成功实现了一款功能完整、界面现代的停车场查询应用,涵盖了位置服务类应用开发的核心要素。通过Flutter框架的应用,实现了跨平台的应用体验,证明了Flutter在实用工具类应用开发领域的可行性。
项目采用模块化设计思想,将应用功能划分为数据模型、搜索筛选、列表展示、详情弹窗、地图视图等独立模块,各模块职责明确,耦合度低,便于维护和扩展。
代码实现注重性能优化和用户体验,通过按需渲染、状态管理等手段,确保了应用在各种情况下的流畅运行。
二、技术亮点总结
智能状态标识:根据停车场占用率动态计算状态颜色和文本,提供直观的可用性提示。
多维度筛选排序:支持按类型筛选、空位筛选,以及距离、价格、空位、评分四种排序方式,满足不同用户需求。
可拖拽详情弹窗:使用DraggableScrollableSheet实现可拖拽高度的详情弹窗,提升用户交互体验。
实时搜索响应:搜索框输入实时更新结果,提供即时的搜索反馈。
统计信息聚合:地图视图展示停车场数量、总空位数、平均价格等统计信息,提供快速数据概览。
三、未来优化方向
地图SDK集成:集成高德地图或百度地图SDK,实现真实的地图展示和导航功能。
定位服务集成:获取用户当前位置,计算真实的距离和导航路线。
实时数据对接:对接停车场管理系统API,获取实时的空位数据和价格信息。
预约停车功能:支持提前预约车位,避免到达后无位的尴尬。
停车记录功能:记录用户的停车历史,提供停车费用统计和位置记忆。
支付功能集成:集成移动支付,实现停车费的在线缴纳。
优惠活动推送:推送停车场的优惠活动信息,帮助用户节省停车费用。
车位分享功能:支持私人车位分享,盘活闲置停车资源。
四、开发经验总结
通过本项目的开发,积累了宝贵的Flutter应用开发经验:
数据模型设计的重要性:良好的数据模型设计是应用开发的基础,合理的属性划分和计算属性设计能够简化UI层的逻辑,提高代码的可维护性。
用户体验的核心地位:工具类应用最终服务于用户,用户体验是评判应用质量的核心标准。从搜索的实时响应到详情的完整展示,每个细节都需要精心打磨。
性能优化的持续性:性能优化不是一次性工作,需要在开发过程中持续关注,通过性能分析工具定位瓶颈,针对性优化。
跨平台兼容性的挑战:跨平台开发需要考虑不同平台的差异,包括屏幕尺寸、系统字体、交互习惯等,确保应用在各平台上的表现一致。
本项目为Flutter位置服务应用开发提供了一个完整的实践案例,展示了如何实现搜索筛选、列表展示、详情弹窗等核心功能,希望能够为相关开发者提供参考和启发,推动Flutter在实用工具类应用开发领域的应用和发展。
更多推荐


所有评论(0)