Flutter 框架跨平台鸿蒙开发 - 附近文创市集查询应用开发教程
完整的市集信息展示:名称、时间、地点、主题、商家等智能搜索和筛选:多维度筛选条件详细的市集页面:商家、活动、评价信息位置服务集成:距离计算和排序活动报名功能:工作坊和表演活动报名优雅的UI设计:Material Design 3风格这款应用不仅功能丰富,而且充分考虑了文创爱好者的实际需求。通过本教程的学习,你可以掌握Flutter本地服务应用开发的核心技能。欢迎加入开源鸿蒙跨平台社区:https:
·
Flutter附近文创市集查询应用开发教程
项目概述
本教程将带你开发一个功能完整的Flutter附近文创市集查询应用。这款应用专为文创爱好者和艺术家设计,提供附近文创市集的实时信息查询、活动详情浏览、摊位预订和社区交流等功能,让用户能够轻松发现和参与各种文创活动。
运行效果图





应用特色
- 实时市集信息:展示附近正在进行和即将举办的文创市集
- 精准定位查询:基于GPS定位查找附近的文创活动
- 丰富活动详情:展示市集时间、地点、主题、参展商家等信息
- 摊位预订功能:支持在线预订市集摊位
- 社区互动:用户评价、照片分享、活动打卡
- 收藏管理:收藏感兴趣的市集和活动
- 分类筛选:按主题、时间、距离等条件筛选
- 活动提醒:设置感兴趣活动的提醒通知
技术栈
- 框架:Flutter 3.x
- 语言:Dart
- UI组件:Material Design 3
- 状态管理:StatefulWidget + Provider
- 地图服务:Google Maps(模拟实现)
- 定位服务:Geolocator(模拟实现)
- 数据存储:SharedPreferences + SQLite
- 网络请求:HTTP(模拟数据)
项目结构设计
核心数据模型
1. 文创市集模型(CreativeMarket)
class CreativeMarket {
final String id; // 唯一标识
final String name; // 市集名称
final String description; // 市集描述
final String theme; // 主题分类
final String address; // 详细地址
final double latitude; // 纬度
final double longitude; // 经度
final DateTime startDate; // 开始时间
final DateTime endDate; // 结束时间
final String organizer; // 主办方
final String contact; // 联系方式
final List<String> images; // 市集图片
final List<Vendor> vendors; // 参展商家
final List<String> tags; // 标签
final MarketStatus status; // 市集状态
final double entryFee; // 入场费
final int expectedVisitors; // 预计参观人数
bool isFavorite; // 是否收藏
final double distance; // 距离(公里)
final List<Activity> activities; // 相关活动
final double rating; // 评分
final int reviewCount; // 评价数量
}
2. 参展商家模型(Vendor)
class Vendor {
final String id;
final String name;
final String description;
final String category;
final List<String> products;
final String contact;
final List<String> images;
final String boothNumber;
final bool isBookable;
final double boothFee;
}
3. 活动模型(Activity)
class Activity {
final String id;
final String name;
final String description;
final DateTime startTime;
final DateTime endTime;
final String location;
final String type; // workshop, performance, exhibition
final bool requiresRegistration;
final int maxParticipants;
final int currentParticipants;
}
4. 市集状态枚举
enum MarketStatus {
upcoming, // 即将开始
ongoing, // 进行中
ended, // 已结束
cancelled, // 已取消
}
页面架构
应用采用底部导航栏设计,包含四个主要页面:
- 首页:附近市集和推荐活动
- 发现页面:市集搜索和分类浏览
- 我的活动:收藏和参与的活动
- 个人中心:用户信息和设置
详细实现步骤
第一步:项目初始化
创建新的Flutter项目:
flutter create creative_market_finder
cd creative_market_finder
第二步:主应用结构
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter附近文创市集查询',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const CreativeMarketFinderHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
第三步:数据初始化
创建示例市集数据:
void _initializeMarkets() {
_markets = [
CreativeMarket(
id: '1',
name: '春日文创集市',
description: '汇聚本地原创设计师和手工艺人的春季文创市集,展示各种独特的手工艺品、原创设计和文创产品。',
theme: '手工艺品',
address: '北京市朝阳区798艺术区',
latitude: 39.9847,
longitude: 116.4963,
startDate: DateTime.now().add(const Duration(days: 2)),
endDate: DateTime.now().add(const Duration(days: 4)),
organizer: '798艺术区管委会',
contact: '010-64381784',
images: ['assets/images/market1_1.jpg', 'assets/images/market1_2.jpg'],
vendors: [
Vendor(
id: '1',
name: '手作工坊',
description: '专注于传统手工艺品制作',
category: '手工艺品',
products: ['陶瓷', '编织品', '木雕'],
contact: '13800138001',
images: ['assets/images/vendor1.jpg'],
boothNumber: 'A01',
isBookable: true,
boothFee: 200.0,
),
],
tags: ['原创', '手工', '艺术'],
status: MarketStatus.upcoming,
entryFee: 0.0,
expectedVisitors: 500,
isFavorite: true,
distance: 2.3,
activities: [
Activity(
id: '1',
name: '陶艺体验工作坊',
description: '学习基础陶艺制作技巧',
startTime: DateTime.now().add(const Duration(days: 2, hours: 10)),
endTime: DateTime.now().add(const Duration(days: 2, hours: 12)),
location: 'A区工作坊',
type: 'workshop',
requiresRegistration: true,
maxParticipants: 20,
currentParticipants: 8,
),
],
rating: 4.8,
reviewCount: 156,
),
CreativeMarket(
id: '2',
name: '青年设计师市集',
description: '展示年轻设计师的创新作品,包括服装设计、产品设计、平面设计等多个领域。',
theme: '设计创新',
address: '上海市黄浦区新天地',
latitude: 31.2222,
longitude: 121.4767,
startDate: DateTime.now().subtract(const Duration(days: 1)),
endDate: DateTime.now().add(const Duration(days: 1)),
organizer: '上海设计师协会',
contact: '021-63912345',
images: ['assets/images/market2_1.jpg'],
vendors: [],
tags: ['设计', '创新', '青年'],
status: MarketStatus.ongoing,
entryFee: 20.0,
expectedVisitors: 800,
isFavorite: false,
distance: 1.8,
activities: [],
rating: 4.6,
reviewCount: 203,
),
CreativeMarket(
id: '3',
name: '复古文化市集',
description: '以复古文化为主题的文创市集,展示vintage风格的服饰、饰品和生活用品。',
theme: '复古文化',
address: '广州市天河区太古汇',
latitude: 23.1291,
longitude: 113.3240,
startDate: DateTime.now().add(const Duration(days: 7)),
endDate: DateTime.now().add(const Duration(days: 9)),
organizer: '广州文创联盟',
contact: '020-38751234',
images: ['assets/images/market3_1.jpg'],
vendors: [],
tags: ['复古', '文化', 'vintage'],
status: MarketStatus.upcoming,
entryFee: 15.0,
expectedVisitors: 600,
isFavorite: true,
distance: 5.2,
activities: [],
rating: 4.4,
reviewCount: 98,
),
CreativeMarket(
id: '4',
name: '科技文创展',
description: '结合科技与文创的创新展览,展示AR/VR艺术作品、数字艺术和智能设计产品。',
theme: '科技创新',
address: '深圳市南山区华侨城创意文化园',
latitude: 22.5431,
longitude: 113.9340,
startDate: DateTime.now().add(const Duration(days: 14)),
endDate: DateTime.now().add(const Duration(days: 16)),
organizer: '深圳科技文创协会',
contact: '0755-26781234',
images: ['assets/images/market4_1.jpg'],
vendors: [],
tags: ['科技', '数字艺术', '创新'],
status: MarketStatus.upcoming,
entryFee: 30.0,
expectedVisitors: 1000,
isFavorite: false,
distance: 8.7,
activities: [],
rating: 4.9,
reviewCount: 67,
),
CreativeMarket(
id: '5',
name: '民族文化市集',
description: '展示各民族传统文化和手工艺品的文创市集,传承和弘扬民族文化。',
theme: '民族文化',
address: '成都市锦江区宽窄巷子',
latitude: 30.6598,
longitude: 104.0633,
startDate: DateTime.now().subtract(const Duration(days: 3)),
endDate: DateTime.now().subtract(const Duration(days: 1)),
organizer: '成都民族文化促进会',
contact: '028-86541234',
images: ['assets/images/market5_1.jpg'],
vendors: [],
tags: ['民族', '传统', '文化'],
status: MarketStatus.ended,
entryFee: 10.0,
expectedVisitors: 700,
isFavorite: true,
distance: 12.5,
activities: [],
rating: 4.7,
reviewCount: 134,
),
];
}
第四步:首页设计
位置信息和搜索栏
Widget _buildHomePage() {
final ongoingMarkets = _markets.where((market) =>
market.status == MarketStatus.ongoing).toList();
final upcomingMarkets = _markets.where((market) =>
market.status == MarketStatus.upcoming).toList();
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 位置和搜索栏
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.deepPurple.shade300, Colors.deepPurple.shade500],
),
),
child: SafeArea(
child: Column(
children: [
Row(
children: [
const Icon(Icons.location_on, color: Colors.white),
const SizedBox(width: 8),
Expanded(
child: Text(
_currentLocation,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
IconButton(
icon: const Icon(Icons.refresh, color: Colors.white),
onPressed: _refreshLocation,
),
],
),
const SizedBox(height: 16),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(25),
),
child: TextField(
decoration: const InputDecoration(
hintText: '搜索文创市集...',
prefixIcon: Icon(Icons.search),
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 20,
vertical: 15,
),
),
onTap: () => setState(() => _selectedIndex = 1),
readOnly: true,
),
),
],
),
),
),
// 快速筛选
_buildQuickFilters(),
// 正在进行的市集
if (ongoingMarkets.isNotEmpty) ...[
_buildSectionTitle('正在进行'),
SizedBox(
height: 280,
child: ListView.builder(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: ongoingMarkets.length,
itemBuilder: (context, index) {
return Container(
width: 300,
margin: const EdgeInsets.only(right: 12),
child: _buildMarketCard(ongoingMarkets[index]),
);
},
),
),
],
// 即将开始的市集
if (upcomingMarkets.isNotEmpty) ...[
_buildSectionTitle('即将开始'),
...upcomingMarkets.take(3).map((market) =>
_buildMarketCard(market)
),
],
// 热门主题
_buildSectionTitle('热门主题'),
_buildPopularThemes(),
const SizedBox(height: 20),
],
),
);
}
快速筛选按钮
Widget _buildQuickFilters() {
final filters = [
{'name': '附近', 'icon': Icons.near_me, 'color': Colors.blue},
{'name': '进行中', 'icon': Icons.access_time, 'color': Colors.green},
{'name': '免费', 'icon': Icons.money_off, 'color': Colors.orange},
{'name': '本周末', 'icon': Icons.weekend, 'color': Colors.purple},
];
return Container(
height: 80,
padding: const EdgeInsets.symmetric(vertical: 16),
child: ListView.builder(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: filters.length,
itemBuilder: (context, index) {
final filter = filters[index];
return Container(
width: 80,
margin: const EdgeInsets.only(right: 12),
child: InkWell(
onTap: () => _applyQuickFilter(filter['name'] as String),
child: Column(
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: (filter['color'] as Color).withOpacity(0.1),
borderRadius: BorderRadius.circular(24),
),
child: Icon(
filter['icon'] as IconData,
color: filter['color'] as Color,
size: 24,
),
),
const SizedBox(height: 4),
Text(
filter['name'] as String,
style: const TextStyle(fontSize: 12),
),
],
),
),
);
},
),
);
}
第五步:市集卡片设计
Widget _buildMarketCard(CreativeMarket market) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
elevation: 4,
child: InkWell(
onTap: () => _showMarketDetails(market),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 市集头部信息
Row(
children: [
// 市集图片
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
_getThemeColor(market.theme),
_getThemeColor(market.theme).withOpacity(0.7),
],
),
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Icon(
Icons.palette,
size: 40,
color: Colors.white,
),
),
),
const SizedBox(width: 16),
// 市集信息
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
market.name,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
GestureDetector(
onTap: () => _toggleFavorite(market),
child: Icon(
market.isFavorite ? Icons.favorite : Icons.favorite_border,
color: market.isFavorite ? Colors.red : Colors.grey,
),
),
],
),
const SizedBox(height: 4),
Row(
children: [
Icon(
Icons.star,
size: 16,
color: Colors.amber.shade600,
),
const SizedBox(width: 4),
Text(
'${market.rating}',
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(width: 4),
Text('(${market.reviewCount}条评价)'),
],
),
const SizedBox(height: 4),
Row(
children: [
Icon(
Icons.location_on,
size: 16,
color: Colors.grey.shade600,
),
const SizedBox(width: 4),
Text(
'${market.distance.toStringAsFixed(1)}km',
style: TextStyle(color: Colors.grey.shade600),
),
const SizedBox(width: 16),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: _getStatusColor(market.status),
borderRadius: BorderRadius.circular(10),
),
child: Text(
_getStatusText(market.status),
style: const TextStyle(
color: Colors.white,
fontSize: 12,
),
),
),
],
),
],
),
),
],
),
const SizedBox(height: 12),
// 市集描述
Text(
market.description,
style: TextStyle(
color: Colors.grey.shade700,
fontSize: 14,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 12),
// 时间信息
Row(
children: [
Icon(
Icons.schedule,
size: 16,
color: Colors.grey.shade600,
),
const SizedBox(width: 4),
Text(
_formatDateRange(market.startDate, market.endDate),
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 12,
),
),
const Spacer(),
if (market.entryFee > 0)
Text(
'¥${market.entryFee.toStringAsFixed(0)}',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.red,
),
)
else
const Text(
'免费',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.green,
),
),
],
),
const SizedBox(height: 12),
// 主题标签
Wrap(
spacing: 8,
runSpacing: 4,
children: market.tags.take(3).map((tag) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: _getThemeColor(market.theme).withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: _getThemeColor(market.theme).withOpacity(0.3),
),
),
child: Text(
tag,
style: TextStyle(
color: _getThemeColor(market.theme),
fontSize: 12,
),
),
);
}).toList(),
),
],
),
),
),
);
}
第六步:市集详情页面
class MarketDetailPage extends StatefulWidget {
final CreativeMarket market;
const MarketDetailPage({super.key, required this.market});
State<MarketDetailPage> createState() => _MarketDetailPageState();
}
class _MarketDetailPageState extends State<MarketDetailPage>
with TickerProviderStateMixin {
late TabController _tabController;
void initState() {
super.initState();
_tabController = TabController(length: 4, vsync: this);
}
void dispose() {
_tabController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
// 市集头部信息
SliverAppBar(
expandedHeight: 300,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
title: Text(widget.market.name),
background: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
_getThemeColor(widget.market.theme),
_getThemeColor(widget.market.theme).withOpacity(0.8),
],
),
),
child: Stack(
children: [
const Center(
child: Icon(
Icons.palette,
size: 100,
color: Colors.white54,
),
),
Positioned(
bottom: 20,
left: 20,
right: 20,
child: _buildMarketBasicInfo(),
),
],
),
),
),
actions: [
IconButton(
icon: Icon(
widget.market.isFavorite ? Icons.favorite : Icons.favorite_border,
color: widget.market.isFavorite ? Colors.red : Colors.white,
),
onPressed: _toggleFavorite,
),
IconButton(
icon: const Icon(Icons.share),
onPressed: _shareMarket,
),
],
),
// Tab栏
SliverPersistentHeader(
pinned: true,
delegate: _SliverTabBarDelegate(
TabBar(
controller: _tabController,
tabs: const [
Tab(text: '详情'),
Tab(text: '商家'),
Tab(text: '活动'),
Tab(text: '评价'),
],
),
),
),
// Tab内容
SliverFillRemaining(
child: TabBarView(
controller: _tabController,
children: [
_buildDetailTab(),
_buildVendorsTab(),
_buildActivitiesTab(),
_buildReviewsTab(),
],
),
),
],
),
bottomNavigationBar: _buildBottomActions(),
);
}
Widget _buildMarketBasicInfo() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.9),
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Icon(
Icons.star,
color: Colors.amber.shade600,
size: 20,
),
const SizedBox(width: 4),
Text(
'${widget.market.rating}',
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
const SizedBox(width: 8),
Text('${widget.market.reviewCount}条评价'),
const Spacer(),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: _getStatusColor(widget.market.status),
borderRadius: BorderRadius.circular(12),
),
child: Text(
_getStatusText(widget.market.status),
style: const TextStyle(
color: Colors.white,
fontSize: 12,
),
),
),
],
),
const SizedBox(height: 8),
Row(
children: [
const Icon(Icons.location_on, size: 16),
const SizedBox(width: 4),
Text('距离 ${widget.market.distance.toStringAsFixed(1)}km'),
const SizedBox(width: 16),
const Icon(Icons.people, size: 16),
const SizedBox(width: 4),
Text('预计${widget.market.expectedVisitors}人参观'),
],
),
],
),
);
}
Widget _buildDetailTab() {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 市集介绍
const Text(
'市集介绍',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Text(
widget.market.description,
style: const TextStyle(fontSize: 16, height: 1.5),
),
const SizedBox(height: 24),
// 基本信息
const Text(
'基本信息',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
_buildInfoRow(Icons.location_on, '地址', widget.market.address),
_buildInfoRow(Icons.schedule, '时间',
_formatDateRange(widget.market.startDate, widget.market.endDate)),
_buildInfoRow(Icons.business, '主办方', widget.market.organizer),
_buildInfoRow(Icons.phone, '联系方式', widget.market.contact),
_buildInfoRow(Icons.attach_money, '入场费',
widget.market.entryFee > 0 ? '¥${widget.market.entryFee.toStringAsFixed(0)}' : '免费'),
const SizedBox(height: 24),
// 主题标签
const Text(
'主题标签',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
Wrap(
spacing: 12,
runSpacing: 8,
children: widget.market.tags.map((tag) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
decoration: BoxDecoration(
color: _getThemeColor(widget.market.theme).withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: _getThemeColor(widget.market.theme).withOpacity(0.3),
),
),
child: Text(
tag,
style: TextStyle(
color: _getThemeColor(widget.market.theme),
fontWeight: FontWeight.bold,
),
),
);
}).toList(),
),
const SizedBox(height: 24),
// 地图位置
const Text(
'位置地图',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
Container(
height: 200,
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.map, size: 48, color: Colors.grey),
SizedBox(height: 8),
Text('地图加载中...', style: TextStyle(color: Colors.grey)),
],
),
),
),
],
),
);
}
Widget _buildVendorsTab() {
return widget.market.vendors.isEmpty
? const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.store, size: 64, color: Colors.grey),
SizedBox(height: 16),
Text('暂无商家信息', style: TextStyle(color: Colors.grey)),
],
),
)
: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: widget.market.vendors.length,
itemBuilder: (context, index) {
final vendor = widget.market.vendors[index];
return _buildVendorCard(vendor);
},
);
}
Widget _buildVendorCard(Vendor vendor) {
return Card(
margin: const EdgeInsets.only(bottom: 12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: _getThemeColor(widget.market.theme).withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Icon(
Icons.store,
size: 24,
color: _getThemeColor(widget.market.theme),
),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
vendor.name,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
'摊位号:${vendor.boothNumber}',
style: TextStyle(color: Colors.grey.shade600),
),
],
),
),
if (vendor.isBookable)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(12),
),
child: const Text(
'可预订',
style: TextStyle(
color: Colors.white,
fontSize: 12,
),
),
),
],
),
const SizedBox(height: 12),
Text(
vendor.description,
style: TextStyle(color: Colors.grey.shade700),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
children: vendor.products.map((product) {
return Chip(
label: Text(product),
backgroundColor: Colors.grey.shade100,
);
}).toList(),
),
],
),
),
);
}
Widget _buildActivitiesTab() {
return widget.market.activities.isEmpty
? const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.event, size: 64, color: Colors.grey),
SizedBox(height: 16),
Text('暂无相关活动', style: TextStyle(color: Colors.grey)),
],
),
)
: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: widget.market.activities.length,
itemBuilder: (context, index) {
final activity = widget.market.activities[index];
return _buildActivityCard(activity);
},
);
}
Widget _buildActivityCard(Activity activity) {
return Card(
margin: const EdgeInsets.only(bottom: 12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
_getActivityIcon(activity.type),
color: _getThemeColor(widget.market.theme),
),
const SizedBox(width: 8),
Expanded(
child: Text(
activity.name,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
if (activity.requiresRegistration)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: Colors.orange,
borderRadius: BorderRadius.circular(12),
),
child: const Text(
'需报名',
style: TextStyle(
color: Colors.white,
fontSize: 12,
),
),
),
],
),
const SizedBox(height: 8),
Text(
activity.description,
style: TextStyle(color: Colors.grey.shade700),
),
const SizedBox(height: 8),
Row(
children: [
Icon(Icons.schedule, size: 16, color: Colors.grey.shade600),
const SizedBox(width: 4),
Text(
_formatTimeRange(activity.startTime, activity.endTime),
style: TextStyle(color: Colors.grey.shade600),
),
const SizedBox(width: 16),
Icon(Icons.location_on, size: 16, color: Colors.grey.shade600),
const SizedBox(width: 4),
Text(
activity.location,
style: TextStyle(color: Colors.grey.shade600),
),
],
),
if (activity.requiresRegistration) ...[
const SizedBox(height: 8),
Row(
children: [
Icon(Icons.people, size: 16, color: Colors.grey.shade600),
const SizedBox(width: 4),
Text(
'${activity.currentParticipants}/${activity.maxParticipants}人',
style: TextStyle(color: Colors.grey.shade600),
),
const Spacer(),
ElevatedButton(
onPressed: activity.currentParticipants < activity.maxParticipants
? () => _registerActivity(activity)
: null,
child: Text(
activity.currentParticipants < activity.maxParticipants
? '立即报名'
: '已满员',
),
),
],
),
],
],
),
),
);
}
Widget _buildReviewsTab() {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.rate_review, size: 64, color: Colors.grey),
SizedBox(height: 16),
Text('暂无评价信息', style: TextStyle(color: Colors.grey)),
],
),
);
}
Widget _buildBottomActions() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, -2),
),
],
),
child: Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: _contactOrganizer,
icon: const Icon(Icons.phone),
label: const Text('联系主办方'),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton.icon(
onPressed: _navigateToMarket,
icon: const Icon(Icons.navigation),
label: const Text('导航前往'),
),
),
],
),
);
}
// 工具方法和事件处理...
}
核心功能详解
1. 位置服务模拟
void _getCurrentLocation() async {
// 模拟获取当前位置
setState(() {
_currentLocation = '北京市朝阳区';
_userLatitude = 39.9370;
_userLongitude = 116.4560;
});
// 更新市集距离
_updateMarketDistances();
}
void _updateMarketDistances() {
for (var market in _markets) {
// 简单的距离计算模拟
final latDiff = (market.latitude - _userLatitude).abs();
final lngDiff = (market.longitude - _userLongitude).abs();
market.distance = (latDiff + lngDiff) * 100; // 简化计算
}
// 按距离排序
_markets.sort((a, b) => a.distance.compareTo(b.distance));
setState(() {});
}
2. 搜索和筛选算法
void _performSearch(String query) {
if (query.isEmpty) {
setState(() {
_filteredMarkets = List.from(_markets);
});
return;
}
setState(() {
_filteredMarkets = _markets.where((market) {
final nameMatch = market.name.toLowerCase().contains(query.toLowerCase());
final descMatch = market.description.toLowerCase().contains(query.toLowerCase());
final themeMatch = market.theme.toLowerCase().contains(query.toLowerCase());
final tagMatch = market.tags.any((tag) =>
tag.toLowerCase().contains(query.toLowerCase()));
final organizerMatch = market.organizer.toLowerCase().contains(query.toLowerCase());
return nameMatch || descMatch || themeMatch || tagMatch || organizerMatch;
}).toList();
});
}
3. 主题颜色系统
Color _getThemeColor(String theme) {
switch (theme) {
case '手工艺品': return Colors.brown;
case '设计创新': return Colors.blue;
case '复古文化': return Colors.amber;
case '科技创新': return Colors.purple;
case '民族文化': return Colors.red;
case '艺术展览': return Colors.pink;
case '音乐表演': return Colors.orange;
case '美食文化': return Colors.green;
default: return Colors.deepPurple;
}
}
Color _getStatusColor(MarketStatus status) {
switch (status) {
case MarketStatus.upcoming: return Colors.blue;
case MarketStatus.ongoing: return Colors.green;
case MarketStatus.ended: return Colors.grey;
case MarketStatus.cancelled: return Colors.red;
}
}
String _getStatusText(MarketStatus status) {
switch (status) {
case MarketStatus.upcoming: return '即将开始';
case MarketStatus.ongoing: return '进行中';
case MarketStatus.ended: return '已结束';
case MarketStatus.cancelled: return '已取消';
}
}
性能优化
1. 列表优化
使用ListView.builder实现虚拟滚动:
ListView.builder(
itemCount: markets.length,
itemExtent: 250, // 固定高度提高性能
itemBuilder: (context, index) {
return _buildMarketCard(markets[index]);
},
)
2. 图片缓存
使用渐变色作为占位符,减少内存使用:
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
_getThemeColor(market.theme),
_getThemeColor(market.theme).withOpacity(0.7),
],
),
),
)
3. 状态管理优化
合理使用setState,避免不必要的重建:
void _updateMarketList() {
if (mounted) {
setState(() {
// 只更新必要的状态
});
}
}
扩展功能
1. 真实地图集成
可以集成Google Maps或高德地图:
dependencies:
google_maps_flutter: ^2.5.0
2. 位置服务
集成真实的位置服务:
dependencies:
geolocator: ^10.1.0
3. 推送通知
使用flutter_local_notifications:
dependencies:
flutter_local_notifications: ^16.1.0
4. 社交分享
集成社交分享功能:
dependencies:
share_plus: ^7.2.1
测试策略
1. 单元测试
测试核心业务逻辑:
test('should filter markets by status correctly', () {
final markets = [
CreativeMarket(status: MarketStatus.ongoing),
CreativeMarket(status: MarketStatus.upcoming),
CreativeMarket(status: MarketStatus.ended),
];
final ongoingMarkets = markets.where((market) =>
market.status == MarketStatus.ongoing).toList();
expect(ongoingMarkets.length, equals(1));
});
2. Widget测试
测试UI组件:
testWidgets('should display market name', (WidgetTester tester) async {
await tester.pumpWidget(MyApp());
expect(find.text('春日文创集市'), findsOneWidget);
});
部署发布
1. Android打包
flutter build apk --release
2. iOS打包
flutter build ios --release
总结
本教程详细介绍了Flutter附近文创市集查询应用的完整开发过程,涵盖了:
- 完整的市集信息展示:名称、时间、地点、主题、商家等
- 智能搜索和筛选:多维度筛选条件
- 详细的市集页面:商家、活动、评价信息
- 收藏管理功能:个人收藏夹
- 位置服务集成:距离计算和排序
- 活动报名功能:工作坊和表演活动报名
- 优雅的UI设计:Material Design 3风格
这款应用不仅功能丰富,而且充分考虑了文创爱好者的实际需求。通过本教程的学习,你可以掌握Flutter本地服务应用开发的核心技能。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)