Flutter 框架跨平台鸿蒙开发 - 全国文创印章店查询应用开发教程
数据模型设计:合理的数据结构设计UI界面开发:Material Design 3风格界面功能实现地理位置服务:距离计算、位置标记用户体验优化:动画效果、加载状态、错误处理性能优化:列表优化、状态管理、内存管理扩展功能:地图集成、定位服务、本地存储测试策略:单元测试、Widget测试、集成测试这款应用不仅功能完整,而且代码结构清晰,易于维护和扩展。通过本教程的学习,你可以掌握Flutter应用开发的
·
Flutter全国文创印章店查询应用开发教程
项目概述
本教程将带你开发一个功能完整的Flutter全国文创印章店查询应用。这款应用专为文创爱好者和印章收藏者设计,提供全国范围内文创印章店的查询、导航、预约和评价功能,让用户轻松找到心仪的文创印章店。
运行效果图




应用特色
- 全国店铺覆盖:涵盖全国各地的文创印章店信息
- 智能定位系统:基于GPS定位推荐附近店铺
- 多维度筛选:按距离、评分、价格、服务类型等筛选
- 实时状态查询:显示店铺营业状态、繁忙程度等
- 在线预约服务:支持提前预约制作时间
- 用户评价系统:查看其他用户的真实评价和作品展示
- 收藏管理:收藏常用店铺,方便下次查找
- 导航集成:一键导航到目标店铺
- 价格对比:对比不同店铺的服务价格
- 作品展示:浏览店铺的精品作品案例
技术栈
- 框架:Flutter 3.x
- 语言:Dart
- UI组件:Material Design 3
- 状态管理:StatefulWidget
- 动画:AnimationController + Tween
- 数据存储:内存存储(可扩展为本地数据库)
项目结构设计
核心数据模型
1. 印章店模型(SealShop)
class SealShop {
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 ShopStatus status; // 营业状态
final List<String> photos; // 店铺照片
final List<String> features; // 特色服务
final String description; // 店铺描述
final DateTime lastUpdated; // 最后更新时间
bool isFavorite; // 是否收藏
final String ownerName; // 店主姓名
final int experienceYears; // 从业年限
final List<String> specialties; // 专长领域
}
2. 状态枚举
enum ShopStatus { open, closed, busy, appointment, maintenance }
enum ServiceType {
traditional, // 传统印章
creative, // 创意印章
corporate, // 企业印章
personal, // 个人印章
artistic, // 艺术印章
custom, // 定制印章
}
enum ReservationStatus {
pending, // 待确认
confirmed, // 已确认
inProgress, // 制作中
completed, // 已完成
cancelled, // 已取消
expired, // 已过期
}
3. 评价模型(Review)
class Review {
final String id;
final String shopId;
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;
final double serviceRating;
final double qualityRating;
final double priceRating;
}
4. 预约模型(Reservation)
class Reservation {
final String id;
final String shopId;
final String userId;
final DateTime reservationTime;
final String serviceType;
final String sealContent;
final String material;
final String size;
final double estimatedPrice;
final ReservationStatus status;
final String notes;
final DateTime createTime;
final String? cancellationReason;
final DateTime? completionTime;
}
页面架构
应用采用底部导航栏设计,包含五个主要页面:
- 地图页面:地图显示附近印章店,支持标记和筛选
- 列表页面:列表形式浏览所有店铺,支持搜索和排序
- 预约页面:管理预约记录和预约历史
- 收藏页面:管理收藏的店铺
- 个人页面:用户信息、设置和其他功能
详细实现步骤
第一步:项目初始化
创建新的Flutter项目:
flutter create seal_shop_finder
cd seal_shop_finder
第二步:主应用结构
class SealShopFinderApp extends StatelessWidget {
const SealShopFinderApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter全国文创印章店查询',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.red),
useMaterial3: true,
),
home: const SealShopFinderHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
第三步:数据初始化
创建示例印章店数据:
void _initializeData() {
_sealShops.addAll([
SealShop(
id: '1',
name: '墨韵印章工艺坊',
address: '北京市朝阳区文化街88号',
latitude: 39.9042,
longitude: 116.4074,
distance: 0.5,
phone: '010-12345678',
services: ['传统印章', '创意印章', '企业印章', '艺术印章'],
prices: {
'传统印章': 50.0,
'创意印章': 80.0,
'企业印章': 120.0,
'艺术印章': 200.0,
},
rating: 4.8,
reviewCount: 156,
operatingHours: '09:00-18:00',
status: ShopStatus.open,
photos: [],
features: ['手工雕刻', '当日取件', '免费设计', '包装精美'],
description: '专业从事印章制作20年,手工雕刻,品质保证。',
lastUpdated: DateTime.now().subtract(const Duration(minutes: 5)),
isFavorite: true,
ownerName: '张师傅',
experienceYears: 20,
specialties: ['篆刻艺术', '古文字研究', '印章设计'],
),
// 更多店铺数据...
]);
}
第四步:地图页面实现
地图视图组件
Widget _buildMapPage() {
return Stack(
children: [
// 地图容器
Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.red.shade100, Colors.red.shade50],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
child: _buildMapView(),
),
// 顶部搜索栏
Positioned(
top: 0,
left: 0,
right: 0,
child: _buildSearchBar(),
),
// 底部店铺信息卡片
if (_selectedShop != null)
Positioned(
bottom: 0,
left: 0,
right: 0,
child: _buildShopInfoCard(_selectedShop!),
),
// 右侧功能按钮
Positioned(
right: 16,
top: 100,
child: _buildMapControls(),
),
],
);
}
店铺标记组件
Widget _buildShopMarker(SealShop shop) {
final isSelected = _selectedShop?.id == shop.id;
return Positioned(
left: _getMarkerX(shop.longitude),
top: _getMarkerY(shop.latitude),
child: GestureDetector(
onTap: () => _selectShop(shop),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
width: isSelected ? 60 : 40,
height: isSelected ? 60 : 40,
decoration: BoxDecoration(
color: _getStatusColor(shop.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.store,
color: Colors.white,
size: isSelected ? 30 : 20,
),
),
),
);
}
第五步:列表页面实现
店铺卡片组件
Widget _buildShopCard(SealShop shop) {
return Card(
elevation: 4,
margin: const EdgeInsets.only(bottom: 16),
child: InkWell(
onTap: () => _showShopDetail(shop),
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(
shop.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(
shop.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(shop.status).withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Text(
_getStatusText(shop.status),
style: TextStyle(
color: _getStatusColor(shop.status),
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 4),
Text(
'${shop.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(
'${shop.rating.toStringAsFixed(1)}',
style: const TextStyle(fontWeight: FontWeight.w500),
),
Text(
' (${shop.reviewCount})',
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 12,
),
),
],
),
const Spacer(),
Text(
'起价 ¥${_getMinPrice(shop).toStringAsFixed(0)}',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.green,
),
),
],
),
const SizedBox(height: 12),
// 师傅信息
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(Icons.person, color: Colors.red, size: 16),
const SizedBox(width: 8),
Text('${shop.ownerName} • ${shop.experienceYears}年经验'),
const Spacer(),
Text(
'专业师傅',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
],
),
),
const SizedBox(height: 12),
// 服务标签
Wrap(
spacing: 8,
runSpacing: 4,
children: shop.services.take(3).map((service) => Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Text(
service,
style: const TextStyle(fontSize: 12, color: Colors.red),
),
)).toList(),
),
const SizedBox(height: 12),
// 操作按钮
Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: () => _navigateToShop(shop),
icon: const Icon(Icons.directions, size: 16),
label: const Text('导航'),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton.icon(
onPressed: () => _makeReservation(shop),
icon: const Icon(Icons.schedule, size: 16),
label: const Text('预约'),
),
),
const SizedBox(width: 8),
IconButton(
onPressed: () => _toggleFavorite(shop),
icon: Icon(
shop.isFavorite ? Icons.favorite : Icons.favorite_border,
color: shop.isFavorite ? Colors.red : Colors.grey,
),
),
],
),
],
),
),
),
);
}
第六步:预约功能实现
预约对话框
void _makeReservation(SealShop shop) {
if (shop.status == ShopStatus.closed) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('该店铺暂时关闭,无法预约')),
);
return;
}
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('预约 ${shop.name}'),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('店铺: ${shop.name}'),
const SizedBox(height: 8),
Text('地址: ${shop.address}'),
const SizedBox(height: 16),
const Text('选择服务类型:',
style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
...shop.services.map((service) => RadioListTile<String>(
title: Text(service),
subtitle: Text('¥${shop.prices[service]?.toStringAsFixed(0) ?? "0"}'),
value: service,
groupValue: _selectedService,
onChanged: (value) {
setState(() {
_selectedService = value;
});
},
)),
const SizedBox(height: 16),
TextField(
decoration: const InputDecoration(
labelText: '印章内容',
hintText: '请输入要刻制的文字',
border: OutlineInputBorder(),
),
onChanged: (value) => _sealContent = value,
),
const SizedBox(height: 16),
TextField(
decoration: const InputDecoration(
labelText: '备注',
hintText: '特殊要求或说明',
border: OutlineInputBorder(),
),
maxLines: 3,
onChanged: (value) => _reservationNotes = value,
),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
ElevatedButton(
onPressed: () {
if (_selectedService != null && _sealContent.isNotEmpty) {
_confirmReservation(shop);
Navigator.pop(context);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('请填写完整信息')),
);
}
},
child: const Text('确认预约'),
),
],
),
);
}
第七步:搜索和筛选功能
搜索栏组件
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;
});
_filterShops();
},
),
),
IconButton(
icon: const Icon(Icons.tune, color: Colors.red),
onPressed: _showFilterDialog,
),
],
),
);
}
筛选功能
void _filterShops() {
setState(() {
_filteredShops = _sealShops.where((shop) {
bool matchesSearch = _searchQuery.isEmpty ||
shop.name.toLowerCase().contains(_searchQuery.toLowerCase()) ||
shop.address.toLowerCase().contains(_searchQuery.toLowerCase()) ||
shop.services.any((service) =>
service.toLowerCase().contains(_searchQuery.toLowerCase()));
bool matchesStatus = _selectedStatus == null ||
shop.status == _selectedStatus;
bool matchesDistance = shop.distance <= _maxDistance;
bool matchesRating = shop.rating >= _minRating;
return matchesSearch && matchesStatus && matchesDistance && matchesRating;
}).toList();
});
_sortShops();
}
第八步:收藏功能
收藏切换
void _toggleFavorite(SealShop shop) {
setState(() {
shop.isFavorite = !shop.isFavorite;
if (shop.isFavorite) {
if (!_favoriteShops.contains(shop)) {
_favoriteShops.add(shop);
}
} else {
_favoriteShops.remove(shop);
}
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(shop.isFavorite ? '已添加到收藏' : '已从收藏中移除'),
duration: const Duration(seconds: 1),
),
);
}
收藏页面
Widget _buildFavoritePage() {
return Column(
children: [
if (_favoriteShops.isNotEmpty) ...[
Container(
padding: const EdgeInsets.all(16),
child: Row(
children: [
const Icon(Icons.favorite, color: Colors.red),
const SizedBox(width: 8),
Text(
'我的收藏 (${_favoriteShops.length})',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const Spacer(),
TextButton(
onPressed: _clearAllFavorites,
child: const Text('清空'),
),
],
),
),
],
Expanded(
child: _favoriteShops.isEmpty
? _buildEmptyState('暂无收藏', '收藏常用印章店,方便下次查找')
: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _favoriteShops.length,
itemBuilder: (context, index) =>
_buildShopCard(_favoriteShops[index]),
),
),
],
);
}
第九步:店铺详情页面
void _showShopDetail(SealShop shop) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => DraggableScrollableSheet(
initialChildSize: 0.7,
maxChildSize: 0.9,
minChildSize: 0.5,
builder: (context, scrollController) => Container(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 拖拽指示器
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(2),
),
),
),
const SizedBox(height: 16),
// 店铺基本信息
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
shop.name,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
shop.address,
style: TextStyle(color: Colors.grey.shade600),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
color: _getStatusColor(shop.status).withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
),
child: Text(
_getStatusText(shop.status),
style: TextStyle(
color: _getStatusColor(shop.status),
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 16),
// 评分和师傅信息
Row(
children: [
Icon(Icons.star, color: Colors.amber, size: 20),
const SizedBox(width: 4),
Text(
'${shop.rating.toStringAsFixed(1)}',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
Text(
' (${shop.reviewCount}条评价)',
style: TextStyle(color: Colors.grey.shade600),
),
const Spacer(),
Text(
'${shop.ownerName} • ${shop.experienceYears}年',
style: const TextStyle(fontWeight: FontWeight.w500),
),
],
),
const SizedBox(height: 16),
// 详细内容
Expanded(
child: ListView(
controller: scrollController,
children: [
// 店铺描述
const Text(
'店铺介绍',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(shop.description),
const SizedBox(height: 16),
// 服务项目和价格
const Text(
'服务项目',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
...shop.services.map((service) => Card(
child: ListTile(
title: Text(service),
trailing: Text(
'¥${shop.prices[service]?.toStringAsFixed(0) ?? "0"}',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.green,
),
),
),
)),
const SizedBox(height: 16),
// 专长领域
const Text(
'专长领域',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: shop.specialties.map((specialty) => Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
),
child: Text(
specialty,
style: const TextStyle(color: Colors.red),
),
)).toList(),
),
const SizedBox(height: 16),
// 特色服务
const Text(
'特色服务',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: shop.features.map((feature) => Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
),
child: Text(
feature,
style: const TextStyle(color: Colors.blue),
),
)).toList(),
),
const SizedBox(height: 16),
// 营业时间
const Text(
'营业时间',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
shop.operatingHours,
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 16),
// 联系电话
const Text(
'联系电话',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
shop.phone,
style: const TextStyle(fontSize: 16),
),
],
),
),
// 底部按钮
Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: () {
Navigator.pop(context);
_navigateToShop(shop);
},
icon: const Icon(Icons.directions),
label: const Text('导航'),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton.icon(
onPressed: () {
Navigator.pop(context);
_makeReservation(shop);
},
icon: const Icon(Icons.schedule),
label: const Text('预约'),
),
),
],
),
],
),
),
),
);
}
核心功能详解
1. 地理位置计算
double _calculateDistance(double lat1, double lon1, double lat2, double lon2) {
const double earthRadius = 6371; // 地球半径(公里)
double dLat = _degreesToRadians(lat2 - lat1);
double dLon = _degreesToRadians(lon2 - lon1);
double a = sin(dLat / 2) * sin(dLat / 2) +
cos(_degreesToRadians(lat1)) * cos(_degreesToRadians(lat2)) *
sin(dLon / 2) * sin(dLon / 2);
double c = 2 * atan2(sqrt(a), sqrt(1 - a));
return earthRadius * c;
}
double _degreesToRadians(double degrees) {
return degrees * (pi / 180);
}
2. 状态颜色系统
Color _getStatusColor(ShopStatus status) {
switch (status) {
case ShopStatus.open: return Colors.green;
case ShopStatus.closed: return Colors.grey;
case ShopStatus.busy: return Colors.orange;
case ShopStatus.appointment: return Colors.blue;
case ShopStatus.maintenance: return Colors.red;
}
}
String _getStatusText(ShopStatus status) {
switch (status) {
case ShopStatus.open: return '营业中';
case ShopStatus.closed: return '已关闭';
case ShopStatus.busy: return '繁忙';
case ShopStatus.appointment: return '仅预约';
case ShopStatus.maintenance: return '维护中';
}
}
3. 排序算法
void _sortShops() {
setState(() {
switch (_currentFilter) {
case FilterType.distance:
_filteredShops.sort((a, b) => a.distance.compareTo(b.distance));
break;
case FilterType.rating:
_filteredShops.sort((a, b) => b.rating.compareTo(a.rating));
break;
case FilterType.price:
_filteredShops.sort((a, b) => _getMinPrice(a).compareTo(_getMinPrice(b)));
break;
case FilterType.experience:
_filteredShops.sort((a, b) => b.experienceYears.compareTo(a.experienceYears));
break;
}
});
}
性能优化
1. 列表优化
使用ListView.builder实现虚拟滚动:
ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _filteredShops.length,
itemBuilder: (context, index) {
final shop = _filteredShops[index];
return _buildShopCard(shop);
},
)
2. 图片加载优化
Widget _buildShopImage(String imageUrl) {
return FadeInImage.assetNetwork(
placeholder: 'assets/images/placeholder.png',
image: imageUrl,
fit: BoxFit.cover,
fadeInDuration: const Duration(milliseconds: 300),
imageErrorBuilder: (context, error, stackTrace) {
return Container(
color: Colors.grey.shade200,
child: const Icon(Icons.image_not_supported),
);
},
);
}
3. 状态管理优化
合理使用setState,避免不必要的重建:
void _updateShopStatus(String shopId, ShopStatus newStatus) {
setState(() {
final shopIndex = _sealShops.indexWhere((shop) => shop.id == shopId);
if (shopIndex != -1) {
// 只更新特定店铺的状态
_sealShops[shopIndex] = SealShop(
// 复制所有属性,只更新status
id: _sealShops[shopIndex].id,
name: _sealShops[shopIndex].name,
// ... 其他属性
status: newStatus,
// ... 其他属性
);
}
});
}
扩展功能
1. 地图集成
可以集成Google Maps或高德地图:
dependencies:
google_maps_flutter: ^2.5.0
# 或者
amap_flutter_map: ^3.0.0
2. 定位服务
使用geolocator获取用户位置:
dependencies:
geolocator: ^10.1.0
3. 本地存储
使用shared_preferences保存用户设置:
dependencies:
shared_preferences: ^2.2.0
4. 网络请求
集成http插件实现在线数据获取:
dependencies:
http: ^1.1.0
测试策略
1. 单元测试
测试核心业务逻辑:
test('should calculate distance correctly', () {
final distance = _calculateDistance(39.9042, 116.4074, 39.9142, 116.4174);
expect(distance, greaterThan(0));
expect(distance, lessThan(2)); // 应该小于2公里
});
2. Widget测试
测试UI组件:
testWidgets('should display shop name', (WidgetTester tester) async {
await tester.pumpWidget(MyApp());
expect(find.text('墨韵印章工艺坊'), findsOneWidget);
});
3. 集成测试
测试完整用户流程:
testWidgets('should navigate to shop detail when card is tapped',
(WidgetTester tester) async {
await tester.pumpWidget(MyApp());
await tester.tap(find.text('墨韵印章工艺坊'));
await tester.pumpAndSettle();
expect(find.text('店铺介绍'), findsOneWidget);
});
部署发布
1. Android打包
flutter build apk --release
2. iOS打包
flutter build ios --release
3. 应用商店发布
准备应用图标、截图和描述,提交到各大应用商店。
总结
本教程详细介绍了Flutter全国文创印章店查询应用的完整开发过程,涵盖了:
- 数据模型设计:合理的数据结构设计
- UI界面开发:Material Design 3风格界面
- 功能实现:地图显示、搜索筛选、预约管理、收藏功能
- 地理位置服务:距离计算、位置标记
- 用户体验优化:动画效果、加载状态、错误处理
- 性能优化:列表优化、状态管理、内存管理
- 扩展功能:地图集成、定位服务、本地存储
- 测试策略:单元测试、Widget测试、集成测试
这款应用不仅功能完整,而且代码结构清晰,易于维护和扩展。通过本教程的学习,你可以掌握Flutter应用开发的核心技能,为后续开发更复杂的应用打下坚实基础。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)