Flutter 框架跨平台鸿蒙开发 - 附近自助洗车点查询应用开发教程
open, // 营业中closed, // 已关闭busy, // 繁忙maintenance, // 维护中full, // 已满pending, // 待确认confirmed, // 已确认inProgress, // 进行中completed, // 已完成cancelled, // 已取消expired, // 已过期distance, // 按距离rating, // 按评分pric
·
Flutter附近自助洗车点查询应用开发教程
项目概述
本教程将带你开发一个功能完整的Flutter附近自助洗车点查询应用。这款应用专为车主设计,提供智能洗车点定位、实时状态查询、预约服务和用户评价功能,让车主能够快速找到最适合的自助洗车服务点。
运行效果图




应用特色
- 智能定位系统:基于GPS定位,自动搜索附近的自助洗车点
- 实时状态查询:显示洗车点的营业状态、设备可用性和排队情况
- 多维度筛选:按距离、价格、评分、服务类型等条件筛选
- 预约服务功能:支持在线预约洗车时间,避免排队等待
- 用户评价系统:查看其他用户的真实评价和照片分享
- 导航集成:一键导航到选定的洗车点
- 收藏管理:收藏常用洗车点,快速访问
- 价格对比:对比不同洗车点的服务价格和优惠活动
技术栈
- 框架:Flutter 3.x
- 语言:Dart
- UI组件:Material Design 3
- 状态管理:StatefulWidget
- 地图服务:模拟地图功能(可集成高德地图、百度地图等)
- 数据存储:内存存储(可扩展为本地数据库)
项目结构设计
核心数据模型
1. 洗车点模型(CarWashStation)
class CarWashStation {
final String id; // 唯一标识
final String name; // 洗车点名称
final String address; // 详细地址
final double latitude; // 纬度
final double longitude; // 经度
final double distance; // 距离用户的距离(公里)
final String phone; // 联系电话
final List<String> services; // 提供的服务类型
final Map<String, double> prices; // 服务价格
final double rating; // 平均评分
final int reviewCount; // 评价数量
final String operatingHours; // 营业时间
final StationStatus status; // 当前状态
final List<String> photos; // 照片列表
final List<String> features; // 特色功能
final int availableSpots; // 可用车位数
final int totalSpots; // 总车位数
final DateTime lastUpdated; // 最后更新时间
bool isFavorite; // 是否收藏
}
2. 用户评价模型(Review)
class Review {
final String id;
final String stationId;
final String userId;
final String userName;
final String userAvatar;
final double rating;
final String content;
final List<String> photos;
final DateTime createTime;
final List<String> tags; // 评价标签
final String serviceType; // 使用的服务类型
final bool isRecommended; // 是否推荐
}
3. 预约记录模型(Reservation)
class Reservation {
final String id;
final String stationId;
final String userId;
final DateTime reservationTime;
final String serviceType;
final double estimatedPrice;
final ReservationStatus status;
final String notes;
final DateTime createTime;
final String? cancellationReason;
}
4. 服务类型模型(ServiceType)
class ServiceType {
final String id;
final String name;
final String description;
final double basePrice;
final int estimatedDuration; // 预计耗时(分钟)
final List<String> includes; // 包含的服务项目
final String icon;
}
枚举定义
enum StationStatus {
open, // 营业中
closed, // 已关闭
busy, // 繁忙
maintenance, // 维护中
full, // 已满
}
enum ReservationStatus {
pending, // 待确认
confirmed, // 已确认
inProgress, // 进行中
completed, // 已完成
cancelled, // 已取消
expired, // 已过期
}
enum FilterType {
distance, // 按距离
rating, // 按评分
price, // 按价格
availability, // 按可用性
}
enum ServiceCategory {
basic, // 基础洗车
premium, // 精洗
interior, // 内饰清洁
waxing, // 打蜡
detailing, // 精细护理
}
页面架构
应用采用底部导航栏设计,包含五个主要页面:
- 地图页面:显示附近洗车点的地图分布和实时信息
- 列表页面:以列表形式展示洗车点详细信息
- 预约页面:管理用户的预约记录和预约服务
- 收藏页面:管理收藏的洗车点和快速访问
- 个人页面:用户信息、设置和应用功能
详细实现步骤
第一步:项目初始化
创建新的Flutter项目:
flutter create car_wash_finder
cd car_wash_finder
第二步:主应用结构
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: '附近自助洗车点查询',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const CarWashFinderHomePage(),
);
}
}
第三步:数据初始化
创建示例洗车点数据:
void _initializeData() {
_carWashStations = [
CarWashStation(
id: '1',
name: '蓝天自助洗车',
address: '北京市朝阳区建国路88号',
latitude: 39.9042,
longitude: 116.4074,
distance: 0.8,
phone: '010-12345678',
services: ['基础洗车', '精洗', '内饰清洁', '打蜡'],
prices: {
'基础洗车': 15.0,
'精洗': 25.0,
'内饰清洁': 20.0,
'打蜡': 35.0,
},
rating: 4.5,
reviewCount: 128,
operatingHours: '24小时营业',
status: StationStatus.open,
photos: [],
features: ['24小时营业', '免费停车', '移动支付', '会员优惠'],
availableSpots: 3,
totalSpots: 6,
lastUpdated: DateTime.now().subtract(const Duration(minutes: 5)),
isFavorite: true,
),
// 更多洗车点数据...
];
}
第四步:地图页面实现
地图主界面
Widget _buildMapPage() {
return Stack(
children: [
// 地图容器
Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue.shade100, Colors.blue.shade50],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
child: _buildMapView(),
),
// 顶部搜索栏
Positioned(
top: 0,
left: 0,
right: 0,
child: _buildSearchBar(),
),
// 底部洗车点信息卡片
if (_selectedStation != null)
Positioned(
bottom: 0,
left: 0,
right: 0,
child: _buildStationInfoCard(_selectedStation!),
),
// 右侧功能按钮
Positioned(
right: 16,
top: 100,
child: _buildMapControls(),
),
],
);
}
Widget _buildMapView() {
return Container(
child: Stack(
children: [
// 模拟地图背景
Container(
decoration: BoxDecoration(
color: Colors.grey.shade200,
image: const DecorationImage(
image: AssetImage('assets/map_pattern.png'),
repeat: ImageRepeat.repeat,
opacity: 0.1,
),
),
),
// 洗车点标记
..._filteredStations.map((station) => _buildStationMarker(station)),
// 用户位置标记
_buildUserLocationMarker(),
],
),
);
}
Widget _buildStationMarker(CarWashStation station) {
final isSelected = _selectedStation?.id == station.id;
return Positioned(
left: _getMarkerX(station.longitude),
top: _getMarkerY(station.latitude),
child: GestureDetector(
onTap: () => _selectStation(station),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
width: isSelected ? 60 : 40,
height: isSelected ? 60 : 40,
decoration: BoxDecoration(
color: _getStatusColor(station.status),
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 3),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 8,
spreadRadius: 2,
),
],
),
child: Icon(
Icons.local_car_wash,
color: Colors.white,
size: isSelected ? 30 : 20,
),
),
),
);
}
搜索栏组件
Widget _buildSearchBar() {
return Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(25),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
spreadRadius: 2,
),
],
),
child: Row(
children: [
const Icon(Icons.search, color: Colors.grey),
const SizedBox(width: 12),
Expanded(
child: TextField(
decoration: const InputDecoration(
hintText: '搜索附近洗车点...',
border: InputBorder.none,
),
onChanged: (value) {
setState(() {
_searchQuery = value;
});
_filterStations();
},
),
),
IconButton(
icon: const Icon(Icons.tune, color: Colors.blue),
onPressed: _showFilterDialog,
),
],
),
);
}
第五步:列表页面实现
洗车点列表
Widget _buildListPage() {
return Column(
children: [
// 排序和筛选栏
Container(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: DropdownButtonFormField<FilterType>(
decoration: const InputDecoration(
labelText: '排序方式',
border: OutlineInputBorder(),
),
value: _currentFilter,
items: FilterType.values.map((filter) => DropdownMenuItem(
value: filter,
child: Text(_getFilterText(filter)),
)).toList(),
onChanged: (value) {
setState(() {
_currentFilter = value!;
});
_sortStations();
},
),
),
const SizedBox(width: 12),
ElevatedButton.icon(
onPressed: _showFilterDialog,
icon: const Icon(Icons.filter_list),
label: const Text('筛选'),
),
],
),
),
// 洗车点列表
Expanded(
child: _filteredStations.isEmpty
? _buildEmptyState()
: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _filteredStations.length,
itemBuilder: (context, index) => _buildStationCard(_filteredStations[index]),
),
),
],
);
}
Widget _buildStationCard(CarWashStation station) {
return Card(
elevation: 4,
margin: const EdgeInsets.only(bottom: 16),
child: InkWell(
onTap: () => _showStationDetail(station),
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题行
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
station.name,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
Row(
children: [
Icon(Icons.location_on, size: 16, color: Colors.grey.shade600),
const SizedBox(width: 4),
Expanded(
child: Text(
station.address,
style: TextStyle(color: Colors.grey.shade600, fontSize: 14),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: _getStatusColor(station.status).withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Text(
_getStatusText(station.status),
style: TextStyle(
color: _getStatusColor(station.status),
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 4),
Text(
'${station.distance.toStringAsFixed(1)}km',
style: TextStyle(color: Colors.grey.shade600, fontSize: 12),
),
],
),
],
),
const SizedBox(height: 12),
// 评分和价格信息
Row(
children: [
Row(
children: [
Icon(Icons.star, color: Colors.amber, size: 16),
const SizedBox(width: 4),
Text('${station.rating.toStringAsFixed(1)}',
style: const TextStyle(fontWeight: FontWeight.w500)),
Text(' (${station.reviewCount})',
style: TextStyle(color: Colors.grey.shade600, fontSize: 12)),
],
),
const Spacer(),
Text('起价 ¥${_getMinPrice(station).toStringAsFixed(0)}',
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.green)),
],
),
const SizedBox(height: 12),
// 服务标签
Wrap(
spacing: 8,
runSpacing: 4,
children: station.services.take(3).map((service) => Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Text(
service,
style: const TextStyle(fontSize: 12, color: Colors.blue),
),
)).toList(),
),
const SizedBox(height: 12),
// 操作按钮
Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: () => _navigateToStation(station),
icon: const Icon(Icons.directions, size: 16),
label: const Text('导航'),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton.icon(
onPressed: () => _makeReservation(station),
icon: const Icon(Icons.schedule, size: 16),
label: const Text('预约'),
),
),
const SizedBox(width: 8),
IconButton(
onPressed: () => _toggleFavorite(station),
icon: Icon(
station.isFavorite ? Icons.favorite : Icons.favorite_border,
color: station.isFavorite ? Colors.red : Colors.grey,
),
),
],
),
],
),
),
),
);
}
第六步:预约功能实现
预约页面
Widget _buildReservationPage() {
return DefaultTabController(
length: 2,
child: Column(
children: [
const TabBar(
tabs: [
Tab(text: '我的预约'),
Tab(text: '预约历史'),
],
),
Expanded(
child: TabBarView(
children: [
_buildActiveReservations(),
_buildReservationHistory(),
],
),
),
],
),
);
}
Widget _buildActiveReservations() {
final activeReservations = _reservations.where((r) =>
r.status == ReservationStatus.pending ||
r.status == ReservationStatus.confirmed ||
r.status == ReservationStatus.inProgress).toList();
return activeReservations.isEmpty
? _buildEmptyState('暂无活跃预约', '快去预约洗车服务吧!')
: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: activeReservations.length,
itemBuilder: (context, index) => _buildReservationCard(activeReservations[index]),
);
}
Widget _buildReservationCard(Reservation reservation) {
final station = _getStationById(reservation.stationId);
if (station == null) return const SizedBox();
return Card(
elevation: 4,
margin: const EdgeInsets.only(bottom: 16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 预约状态和时间
Row(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: _getReservationStatusColor(reservation.status).withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
),
child: Text(
_getReservationStatusText(reservation.status),
style: TextStyle(
color: _getReservationStatusColor(reservation.status),
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
const Spacer(),
Text(
_formatDateTime(reservation.reservationTime),
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
),
],
),
const SizedBox(height: 12),
// 洗车点信息
Row(
children: [
Icon(Icons.local_car_wash, color: Colors.blue, size: 20),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(station.name, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
Text(station.address, style: TextStyle(color: Colors.grey.shade600, fontSize: 14)),
],
),
),
],
),
const SizedBox(height: 12),
// 服务信息
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('服务类型', style: TextStyle(color: Colors.grey.shade600, fontSize: 12)),
Text(reservation.serviceType, style: const TextStyle(fontWeight: FontWeight.w500)),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text('预估费用', style: TextStyle(color: Colors.grey.shade600, fontSize: 12)),
Text('¥${reservation.estimatedPrice.toStringAsFixed(0)}',
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.green)),
],
),
],
),
),
if (reservation.notes.isNotEmpty) ...[
const SizedBox(height: 8),
Text('备注: ${reservation.notes}', style: TextStyle(color: Colors.grey.shade600, fontSize: 14)),
],
const SizedBox(height: 12),
// 操作按钮
Row(
children: [
if (reservation.status == ReservationStatus.pending || reservation.status == ReservationStatus.confirmed) ...[
Expanded(
child: OutlinedButton(
onPressed: () => _cancelReservation(reservation),
child: const Text('取消预约'),
),
),
const SizedBox(width: 12),
],
Expanded(
child: ElevatedButton.icon(
onPressed: () => _navigateToStation(station),
icon: const Icon(Icons.directions, size: 16),
label: const Text('导航前往'),
),
),
],
),
],
),
),
);
}
第七步:洗车点详情页面
class StationDetailPage extends StatefulWidget {
final CarWashStation station;
final Function(CarWashStation) onFavoriteToggle;
const StationDetailPage({
super.key,
required this.station,
required this.onFavoriteToggle,
});
State<StationDetailPage> createState() => _StationDetailPageState();
}
class _StationDetailPageState extends State<StationDetailPage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.station.name),
actions: [
IconButton(
icon: Icon(
widget.station.isFavorite ? Icons.favorite : Icons.favorite_border,
color: widget.station.isFavorite ? Colors.red : null,
),
onPressed: () {
widget.onFavoriteToggle(widget.station);
setState(() {});
},
),
],
),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 照片轮播
_buildPhotoCarousel(),
// 基本信息
_buildBasicInfo(),
// 服务价格
_buildServicePrices(),
// 特色功能
_buildFeatures(),
// 用户评价
_buildReviews(),
],
),
),
bottomNavigationBar: _buildBottomActions(),
);
}
Widget _buildBasicInfo() {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
widget.station.name,
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: _getStatusColor(widget.station.status).withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
),
child: Text(
_getStatusText(widget.station.status),
style: TextStyle(
color: _getStatusColor(widget.station.status),
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 12),
// 评分和距离
Row(
children: [
Icon(Icons.star, color: Colors.amber, size: 20),
const SizedBox(width: 4),
Text('${widget.station.rating.toStringAsFixed(1)}',
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
Text(' (${widget.station.reviewCount}条评价)',
style: TextStyle(color: Colors.grey.shade600)),
const Spacer(),
Icon(Icons.location_on, color: Colors.grey.shade600, size: 16),
const SizedBox(width: 4),
Text('${widget.station.distance.toStringAsFixed(1)}km',
style: TextStyle(color: Colors.grey.shade600)),
],
),
const SizedBox(height: 12),
// 地址和联系方式
_buildInfoRow(Icons.location_on, widget.station.address),
_buildInfoRow(Icons.phone, widget.station.phone),
_buildInfoRow(Icons.access_time, widget.station.operatingHours),
const SizedBox(height: 12),
// 车位信息
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(Icons.local_parking, color: Colors.blue),
const SizedBox(width: 8),
Text('车位情况: '),
Text('${widget.station.availableSpots}/${widget.station.totalSpots}',
style: const TextStyle(fontWeight: FontWeight.bold)),
const Spacer(),
Text(
widget.station.availableSpots > 0 ? '有空位' : '已满',
style: TextStyle(
color: widget.station.availableSpots > 0 ? Colors.green : Colors.red,
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
);
}
Widget _buildInfoRow(IconData icon, String text) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
children: [
Icon(icon, size: 16, color: Colors.grey.shade600),
const SizedBox(width: 8),
Expanded(child: Text(text, style: TextStyle(color: Colors.grey.shade700))),
],
),
);
}
Widget _buildBottomActions() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
spreadRadius: 2,
),
],
),
child: Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: () => _navigateToStation(),
icon: const Icon(Icons.directions),
label: const Text('导航'),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton.icon(
onPressed: () => _makeReservation(),
icon: const Icon(Icons.schedule),
label: const Text('立即预约'),
),
),
],
),
);
}
}
核心功能详解
1. 地理位置计算
实现距离计算和附近搜索:
class LocationService {
static double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
const double earthRadius = 6371; // 地球半径(公里)
double dLat = _toRadians(lat2 - lat1);
double dLon = _toRadians(lon2 - lon1);
double a = sin(dLat / 2) * sin(dLat / 2) +
cos(_toRadians(lat1)) * cos(_toRadians(lat2)) *
sin(dLon / 2) * sin(dLon / 2);
double c = 2 * atan2(sqrt(a), sqrt(1 - a));
return earthRadius * c;
}
static double _toRadians(double degree) {
return degree * pi / 180;
}
static List<CarWashStation> findNearbyStations(
List<CarWashStation> stations,
double userLat,
double userLon,
double radiusKm,
) {
return stations.where((station) {
double distance = calculateDistance(userLat, userLon, station.latitude, station.longitude);
return distance <= radiusKm;
}).toList();
}
}
2. 智能筛选系统
实现多维度筛选功能:
class StationFilter {
static List<CarWashStation> filterStations(
List<CarWashStation> stations, {
double? maxDistance,
double? minRating,
double? maxPrice,
List<String>? requiredServices,
StationStatus? status,
bool? hasAvailableSpots,
}) {
return stations.where((station) {
// 距离筛选
if (maxDistance != null && station.distance > maxDistance) {
return false;
}
// 评分筛选
if (minRating != null && station.rating < minRating) {
return false;
}
// 价格筛选
if (maxPrice != null) {
double minStationPrice = station.prices.values.reduce(min);
if (minStationPrice > maxPrice) {
return false;
}
}
// 服务类型筛选
if (requiredServices != null && requiredServices.isNotEmpty) {
bool hasAllServices = requiredServices.every((service) =>
station.services.contains(service));
if (!hasAllServices) {
return false;
}
}
// 状态筛选
if (status != null && station.status != status) {
return false;
}
// 车位可用性筛选
if (hasAvailableSpots == true && station.availableSpots <= 0) {
return false;
}
return true;
}).toList();
}
}
3. 预约管理系统
实现预约创建和管理:
class ReservationManager {
static Future<Reservation> createReservation({
required String stationId,
required String userId,
required DateTime reservationTime,
required String serviceType,
required double estimatedPrice,
String notes = '',
}) async {
final reservation = Reservation(
id: DateTime.now().millisecondsSinceEpoch.toString(),
stationId: stationId,
userId: userId,
reservationTime: reservationTime,
serviceType: serviceType,
estimatedPrice: estimatedPrice,
status: ReservationStatus.pending,
notes: notes,
createTime: DateTime.now(),
cancellationReason: null,
);
// 模拟网络请求
await Future.delayed(const Duration(seconds: 1));
return reservation;
}
static Future<bool> cancelReservation(String reservationId, String reason) async {
// 模拟取消预约
await Future.delayed(const Duration(milliseconds: 500));
return true;
}
static bool canCancelReservation(Reservation reservation) {
// 预约时间前30分钟可以取消
final now = DateTime.now();
final timeDiff = reservation.reservationTime.difference(now);
return timeDiff.inMinutes > 30;
}
}
性能优化
1. 地图渲染优化
使用虚拟化技术优化大量标记点的渲染:
class MapRenderer {
static List<CarWashStation> getVisibleStations(
List<CarWashStation> stations,
double centerLat,
double centerLon,
double zoomLevel,
) {
// 根据缩放级别计算可见范围
double visibleRadius = _calculateVisibleRadius(zoomLevel);
return stations.where((station) {
double distance = LocationService.calculateDistance(
centerLat, centerLon, station.latitude, station.longitude);
return distance <= visibleRadius;
}).toList();
}
static double _calculateVisibleRadius(double zoomLevel) {
// 根据缩放级别返回可见半径
return 10.0 / zoomLevel; // 简化计算
}
}
2. 数据缓存策略
实现智能数据缓存:
class DataCache {
static final Map<String, CacheItem> _cache = {};
static const Duration cacheExpiry = Duration(minutes: 5);
static void cacheStationData(String key, List<CarWashStation> data) {
_cache[key] = CacheItem(data: data, timestamp: DateTime.now());
}
static List<CarWashStation>? getCachedStationData(String key) {
final item = _cache[key];
if (item == null) return null;
if (DateTime.now().difference(item.timestamp) > cacheExpiry) {
_cache.remove(key);
return null;
}
return item.data;
}
static void clearExpiredCache() {
final now = DateTime.now();
_cache.removeWhere((key, item) =>
now.difference(item.timestamp) > cacheExpiry);
}
}
class CacheItem {
final List<CarWashStation> data;
final DateTime timestamp;
CacheItem({required this.data, required this.timestamp});
}
扩展功能
1. 地图服务集成
可以集成高德地图或百度地图SDK:
dependencies:
flutter:
sdk: flutter
amap_flutter_map: ^3.0.0 # 高德地图
# 或者
flutter_baidu_mapapi_map: ^6.0.0 # 百度地图
2. 定位服务
使用geolocator插件获取用户位置:
dependencies:
geolocator: ^10.1.0
3. 推送通知
集成firebase_messaging实现预约提醒:
dependencies:
firebase_messaging: ^14.7.9
总结
本教程详细介绍了Flutter附近自助洗车点查询应用的完整开发过程,涵盖了:
- 数据模型设计:洗车点信息、用户评价、预约记录的合理建模
- UI界面开发:Material Design 3风格的现代化界面
- 功能实现:地图展示、列表查询、预约管理、收藏功能
- 地理位置服务:距离计算、附近搜索、导航集成
- 性能优化:地图渲染优化、数据缓存策略
- 扩展功能:地图SDK集成、定位服务、推送通知
这款应用不仅功能完整,而且代码结构清晰,易于维护和扩展。通过本教程的学习,你可以掌握Flutter应用开发的核心技能,为后续开发更复杂的位置服务类应用打下坚实基础。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)