Flutter 框架跨平台鸿蒙开发 - 民宿预订助手:打造完整的短租平台应用
本教程完整展示了如何使用Flutter开发一个功能丰富的民宿预订助手应用。应用包含了房源浏览、搜索筛选、预订管理、用户交互等核心功能,采用了现代化的UI设计和良好的用户体验。
Flutter民宿预订助手:打造完整的短租平台应用
项目概述
在共享经济蓬勃发展的今天,民宿短租已成为旅行住宿的重要选择。本教程将带你使用Flutter开发一个功能完整的民宿预订助手应用,涵盖房源浏览、搜索筛选、预订管理、用户交互等核心功能。
运行效果图




应用特色
- 房源探索:精美的房源卡片展示,支持多维度筛选
- 智能搜索:基于位置、日期、价格的智能搜索系统
- 预订管理:完整的预订流程和状态管理
- 用户体验:直观的导航设计和流畅的交互体验
- 数据模拟:完整的数据模型和模拟数据生成
技术架构
核心技术栈
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
项目结构
lib/
├── main.dart # 应用入口和主要逻辑
├── models/ # 数据模型(集成在main.dart中)
│ ├── bnb_property.dart # 房源模型
│ ├── booking.dart # 预订模型
│ ├── review.dart # 评价模型
│ └── search_filter.dart # 搜索筛选模型
├── screens/ # 页面组件(集成在main.dart中)
│ ├── explore_page.dart # 探索页面
│ ├── wishlist_page.dart # 心愿单页面
│ ├── trips_page.dart # 旅程页面
│ ├── inbox_page.dart # 消息页面
│ └── profile_page.dart # 个人页面
└── widgets/ # 自定义组件(集成在main.dart中)
├── property_card.dart # 房源卡片
├── search_bar.dart # 搜索栏
└── booking_card.dart # 预订卡片
数据模型设计
房源模型(BnbProperty)
房源模型是应用的核心数据结构,包含了房源的所有基本信息:
class BnbProperty {
final String id; // 房源唯一标识
final String title; // 房源标题
final String description; // 房源描述
final String location; // 详细地址
final String city; // 所在城市
final String country; // 所在国家
final double price; // 每晚价格
final String currency; // 货币单位
final List<String> images; // 房源图片列表
final String hostName; // 房东姓名
final String hostAvatar; // 房东头像
final double rating; // 评分
final int reviewCount; // 评价数量
final int maxGuests; // 最大房客数
final int bedrooms; // 卧室数量
final int bathrooms; // 浴室数量
final List<String> amenities; // 设施列表
final String propertyType; // 房源类型
final bool isInstantBook; // 是否支持即时预订
final bool isSuperhost; // 是否为超赞房东
final List<String> houseRules;// 房屋规则
final String checkIn; // 入住时间
final String checkOut; // 退房时间
final double latitude; // 纬度
final double longitude; // 经度
final bool isFavorite; // 是否收藏
final List<String> tags; // 标签列表
}
预订模型(Booking)
预订模型管理用户的预订信息和状态:
class Booking {
final String id; // 预订唯一标识
final String propertyId; // 关联房源ID
final String propertyTitle; // 房源标题
final String propertyImage; // 房源图片
final DateTime checkInDate; // 入住日期
final DateTime checkOutDate; // 退房日期
final int guests; // 房客数量
final double totalPrice; // 总价格
final String status; // 预订状态
final DateTime bookingDate; // 预订时间
final String hostName; // 房东姓名
final String guestName; // 房客姓名
final String guestEmail; // 房客邮箱
final String guestPhone; // 房客电话
final String specialRequests; // 特殊要求
}
预订状态包括:
pending:待确认confirmed:已确认cancelled:已取消completed:已完成
评价模型(Review)
评价模型存储用户对房源的评价信息:
class Review {
final String id; // 评价唯一标识
final String propertyId; // 关联房源ID
final String guestName; // 评价者姓名
final String guestAvatar; // 评价者头像
final double rating; // 总体评分
final String comment; // 评价内容
final DateTime reviewDate; // 评价时间
final List<String> photos; // 评价图片
final Map<String, double> categoryRatings; // 分类评分
}
分类评分包括:
cleanliness:清洁度accuracy:准确性communication:沟通location:位置checkin:入住体验value:性价比
搜索筛选模型(SearchFilter)
搜索筛选模型定义了搜索和筛选的条件:
class SearchFilter {
final String? location; // 目的地
final DateTime? checkIn; // 入住日期
final DateTime? checkOut; // 退房日期
final int guests; // 房客数量
final double? minPrice; // 最低价格
final double? maxPrice; // 最高价格
final String? propertyType; // 房源类型
final List<String> amenities; // 必需设施
final bool instantBookOnly; // 仅即时预订
final bool superhostOnly; // 仅超赞房东
}
应用主体结构
应用入口
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: '民宿预订助手',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
useMaterial3: true,
),
home: const BnbHomePage(),
);
}
}
应用采用Material Design 3设计规范,使用青色(teal)作为主题色,营造清新自然的视觉体验。
主页面结构
主页面使用IndexedStack实现底部导航的页面切换:
class BnbHomePage extends StatefulWidget {
State<BnbHomePage> createState() => _BnbHomePageState();
}
class _BnbHomePageState extends State<BnbHomePage> {
int _selectedIndex = 0;
final List<BnbProperty> _properties = [];
final List<BnbProperty> _favoriteProperties = [];
final List<Booking> _bookings = [];
final List<Review> _reviews = [];
SearchFilter _currentFilter = SearchFilter();
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('民宿预订助手'),
backgroundColor: Colors.teal.withValues(alpha: 0.1),
actions: [
IconButton(
onPressed: () => _showSearchDialog(),
icon: const Icon(Icons.search),
),
IconButton(
onPressed: () => _showFilterDialog(),
icon: const Icon(Icons.tune),
),
],
),
body: IndexedStack(
index: _selectedIndex,
children: [
_buildExplorePage(),
_buildWishlistPage(),
_buildTripsPage(),
_buildInboxPage(),
_buildProfilePage(),
],
),
bottomNavigationBar: NavigationBar(
selectedIndex: _selectedIndex,
onDestinationSelected: (index) {
setState(() => _selectedIndex = index);
},
destinations: const [
NavigationDestination(icon: Icon(Icons.explore), label: '探索'),
NavigationDestination(icon: Icon(Icons.favorite), label: '心愿单'),
NavigationDestination(icon: Icon(Icons.luggage), label: '旅程'),
NavigationDestination(icon: Icon(Icons.message), label: '消息'),
NavigationDestination(icon: Icon(Icons.person), label: '个人'),
],
),
);
}
}
数据生成与管理
房源数据生成
应用使用模拟数据生成器创建丰富的房源信息:
void _generateProperties() {
final propertyTitles = [
'海景别墅 - 私人海滩',
'市中心精品公寓',
'山间小木屋',
'古城内传统四合院',
'湖边度假屋',
// ... 更多标题
];
final descriptions = [
'享受私人海滩和无敌海景,完美的度假体验',
'位于市中心黄金地段,交通便利,设施齐全',
'远离城市喧嚣,在大自然中放松身心',
// ... 更多描述
];
final cities = ['三亚', '北京', '上海', '杭州', '成都', '厦门', '青岛', '大理', '丽江', '桂林'];
final propertyTypes = ['整套房子', '独立房间', '合住房间', '精品酒店'];
final amenities = ['WiFi', '空调', '洗衣机', '厨房', '停车位', '游泳池', '健身房', '阳台', '花园', '烧烤设备'];
final tags = ['海景', '市中心', '安静', '家庭友好', '商务', '浪漫', '宠物友好', '无障碍'];
final random = Random();
for (int i = 0; i < 15; i++) {
// 随机选择设施和标签
final selectedAmenities = <String>[];
final selectedTags = <String>[];
for (int j = 0; j < 5 + random.nextInt(5); j++) {
final amenity = amenities[random.nextInt(amenities.length)];
if (!selectedAmenities.contains(amenity)) {
selectedAmenities.add(amenity);
}
}
for (int j = 0; j < 2 + random.nextInt(3); j++) {
final tag = tags[random.nextInt(tags.length)];
if (!selectedTags.contains(tag)) {
selectedTags.add(tag);
}
}
final city = cities[random.nextInt(cities.length)];
_properties.add(BnbProperty(
id: 'property_$i',
title: propertyTitles[i],
description: descriptions[random.nextInt(descriptions.length)],
location: '$city${['区', '市', '县'][random.nextInt(3)]}${['中心', '海边', '山区', '古城'][random.nextInt(4)]}',
city: city,
country: '中国',
price: 200 + random.nextInt(800).toDouble(),
currency: '¥',
images: ['property_${i + 1}_1.jpg', 'property_${i + 1}_2.jpg', 'property_${i + 1}_3.jpg'],
hostName: ['张先生', '李女士', '王先生', '陈女士', '刘先生'][random.nextInt(5)],
hostAvatar: 'host_${i % 5 + 1}.jpg',
rating: 4.0 + random.nextDouble() * 1.0,
reviewCount: random.nextInt(200),
maxGuests: 2 + random.nextInt(6),
bedrooms: 1 + random.nextInt(3),
bathrooms: 1 + random.nextInt(2),
amenities: selectedAmenities,
propertyType: propertyTypes[random.nextInt(propertyTypes.length)],
isInstantBook: random.nextBool(),
isSuperhost: random.nextBool(),
houseRules: ['禁止吸烟', '禁止聚会', '22:00后保持安静', '爱护房屋设施'],
checkIn: '15:00',
checkOut: '11:00',
latitude: 39.9 + random.nextDouble() * 10,
longitude: 116.4 + random.nextDouble() * 10,
isFavorite: random.nextBool(),
tags: selectedTags,
));
}
}
预订数据生成
预订数据生成器创建不同状态的预订记录:
void _generateBookings() {
final guestNames = ['张三', '李四', '王五', '赵六', '钱七'];
final statuses = ['pending', 'confirmed', 'cancelled', 'completed'];
final random = Random();
for (int i = 0; i < 8; i++) {
final property = _properties[random.nextInt(_properties.length)];
final checkIn = DateTime.now().add(Duration(days: random.nextInt(60) - 30));
final nights = 1 + random.nextInt(7);
final checkOut = checkIn.add(Duration(days: nights));
final guests = 1 + random.nextInt(property.maxGuests);
_bookings.add(Booking(
id: 'booking_$i',
propertyId: property.id,
propertyTitle: property.title,
propertyImage: property.images.first,
checkInDate: checkIn,
checkOutDate: checkOut,
guests: guests,
totalPrice: property.price * nights,
status: statuses[random.nextInt(statuses.length)],
bookingDate: DateTime.now().subtract(Duration(days: random.nextInt(30))),
hostName: property.hostName,
guestName: guestNames[random.nextInt(guestNames.length)],
guestEmail: 'guest${i + 1}@example.com',
guestPhone: '138${random.nextInt(99999999).toString().padLeft(8, '0')}',
specialRequests: i % 3 == 0 ? '希望提供婴儿床' : '',
));
}
}
核心功能实现
探索页面
探索页面是应用的核心功能,展示房源列表和搜索功能:
Widget _buildExplorePage() {
final filteredProperties = _filterProperties();
return Column(
children: [
_buildSearchBar(),
Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: filteredProperties.length,
itemBuilder: (context, index) {
return _buildPropertyCard(filteredProperties[index]);
},
),
),
],
);
}
搜索栏设计
搜索栏提供快速访问搜索功能的入口:
Widget _buildSearchBar() {
return Container(
margin: const EdgeInsets.all(16),
child: Card(
child: InkWell(
onTap: () => _showSearchDialog(),
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.search, color: Colors.teal),
const SizedBox(width: 8),
Text(
_currentFilter.location ?? '你想去哪里?',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: _currentFilter.location != null ? Colors.black : Colors.grey[600],
),
),
],
),
const SizedBox(height: 8),
Row(
children: [
Expanded(
child: Text(
_currentFilter.checkIn != null && _currentFilter.checkOut != null
? '${_formatDate(_currentFilter.checkIn!)} - ${_formatDate(_currentFilter.checkOut!)}'
: '添加日期',
style: TextStyle(
fontSize: 14,
color: _currentFilter.checkIn != null ? Colors.black87 : Colors.grey[600],
),
),
),
const SizedBox(width: 16),
Text(
'${_currentFilter.guests}位房客',
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
],
),
],
),
),
),
),
);
}
房源卡片设计
房源卡片是展示房源信息的核心组件:
Widget _buildPropertyCard(BnbProperty property) {
return Card(
margin: const EdgeInsets.only(bottom: 16),
child: InkWell(
onTap: () => _showPropertyDetail(property),
borderRadius: BorderRadius.circular(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 房源图片区域
Container(
height: 200,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.teal.withValues(alpha: 0.1),
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
),
child: Stack(
children: [
Center(
child: Icon(
Icons.home,
size: 80,
color: Colors.teal.withValues(alpha: 0.3),
),
),
// 房源类型标签
Positioned(
top: 12,
left: 12,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.teal.withValues(alpha: 0.9),
borderRadius: BorderRadius.circular(12),
),
child: Text(
property.propertyType,
style: const TextStyle(
fontSize: 12,
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
),
),
// 超赞房东标签
if (property.isSuperhost)
Positioned(
top: 12,
right: 60,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.orange.withValues(alpha: 0.9),
borderRadius: BorderRadius.circular(12),
),
child: const Text(
'超赞房东',
style: TextStyle(
fontSize: 12,
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
),
),
// 收藏按钮
Positioned(
top: 12,
right: 12,
child: IconButton(
onPressed: () {
setState(() {
// 切换收藏状态
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(property.isFavorite ? '已取消收藏' : '已添加收藏'),
),
);
},
icon: Icon(
property.isFavorite ? Icons.favorite : Icons.favorite_border,
color: Colors.red,
),
style: IconButton.styleFrom(
backgroundColor: Colors.white.withValues(alpha: 0.9),
),
),
),
],
),
),
// 房源信息区域
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
property.title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
Row(
children: [
Icon(Icons.star, color: Colors.orange, size: 16),
const SizedBox(width: 4),
Text(
property.rating.toStringAsFixed(1),
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.orange,
),
),
const SizedBox(width: 4),
Text(
'(${property.reviewCount})',
style: TextStyle(
fontSize: 12,
color: Colors.grey[500],
),
),
],
),
],
),
const SizedBox(height: 8),
Text(
property.location,
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
const SizedBox(height: 8),
Text(
property.description,
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 12),
Row(
children: [
Icon(Icons.people, size: 16, color: Colors.grey[600]),
const SizedBox(width: 4),
Text(
'${property.maxGuests}位房客',
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
const SizedBox(width: 16),
Icon(Icons.bed, size: 16, color: Colors.grey[600]),
const SizedBox(width: 4),
Text(
'${property.bedrooms}间卧室',
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
const SizedBox(width: 16),
Icon(Icons.bathtub, size: 16, color: Colors.grey[600]),
const SizedBox(width: 4),
Text(
'${property.bathrooms}间浴室',
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Text(
'${property.currency}${property.price.toInt()}',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.teal,
),
),
const Text(
' / 晚',
style: TextStyle(fontSize: 14, color: Colors.grey),
),
const Spacer(),
// 标签展示
if (property.tags.isNotEmpty)
Wrap(
spacing: 4,
children: property.tags.take(2).map((tag) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: Colors.teal.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
),
child: Text(
tag,
style: const TextStyle(fontSize: 10, color: Colors.teal),
),
);
}).toList(),
),
],
),
],
),
),
],
),
),
);
}
搜索与筛选功能
搜索对话框
搜索对话框提供目的地和日期选择功能:
void _showSearchDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('搜索房源'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
decoration: const InputDecoration(
labelText: '目的地',
hintText: '输入城市或地区',
prefixIcon: Icon(Icons.location_on),
),
onChanged: (value) {
setState(() {
_currentFilter = SearchFilter(
location: value.isEmpty ? null : value,
checkIn: _currentFilter.checkIn,
checkOut: _currentFilter.checkOut,
guests: _currentFilter.guests,
// ... 其他筛选条件
);
});
},
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: TextButton(
onPressed: () async {
final date = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime.now(),
lastDate: DateTime.now().add(const Duration(days: 365)),
);
if (date != null) {
setState(() {
_currentFilter = SearchFilter(
location: _currentFilter.location,
checkIn: date,
checkOut: _currentFilter.checkOut,
guests: _currentFilter.guests,
// ... 其他筛选条件
);
});
}
},
child: Text(_currentFilter.checkIn != null
? _formatDate(_currentFilter.checkIn!)
: '入住日期'),
),
),
Expanded(
child: TextButton(
onPressed: () async {
final date = await showDatePicker(
context: context,
initialDate: _currentFilter.checkIn?.add(const Duration(days: 1)) ??
DateTime.now().add(const Duration(days: 1)),
firstDate: _currentFilter.checkIn?.add(const Duration(days: 1)) ??
DateTime.now().add(const Duration(days: 1)),
lastDate: DateTime.now().add(const Duration(days: 365)),
);
if (date != null) {
setState(() {
_currentFilter = SearchFilter(
location: _currentFilter.location,
checkIn: _currentFilter.checkIn,
checkOut: date,
guests: _currentFilter.guests,
// ... 其他筛选条件
);
});
}
},
child: Text(_currentFilter.checkOut != null
? _formatDate(_currentFilter.checkOut!)
: '退房日期'),
),
),
],
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
TextButton(
onPressed: () {
Navigator.pop(context);
setState(() {});
},
child: const Text('搜索'),
),
],
),
);
}
筛选功能实现
筛选功能根据用户设定的条件过滤房源:
List<BnbProperty> _filterProperties() {
return _properties.where((property) {
// 位置筛选
if (_currentFilter.location != null && _currentFilter.location!.isNotEmpty) {
if (!property.location.contains(_currentFilter.location!) &&
!property.city.contains(_currentFilter.location!)) {
return false;
}
}
// 价格筛选
if (_currentFilter.minPrice != null && property.price < _currentFilter.minPrice!) {
return false;
}
if (_currentFilter.maxPrice != null && property.price > _currentFilter.maxPrice!) {
return false;
}
// 房客数量筛选
if (property.maxGuests < _currentFilter.guests) {
return false;
}
// 房源类型筛选
if (_currentFilter.propertyType != null &&
property.propertyType != _currentFilter.propertyType) {
return false;
}
// 设施筛选
if (_currentFilter.amenities.isNotEmpty) {
for (String amenity in _currentFilter.amenities) {
if (!property.amenities.contains(amenity)) {
return false;
}
}
}
// 即时预订筛选
if (_currentFilter.instantBookOnly && !property.isInstantBook) {
return false;
}
// 超赞房东筛选
if (_currentFilter.superhostOnly && !property.isSuperhost) {
return false;
}
return true;
}).toList();
}
心愿单功能
心愿单页面展示用户收藏的房源:
Widget _buildWishlistPage() {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'我的心愿单',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Text(
'共${_favoriteProperties.length}个收藏',
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
),
const SizedBox(height: 16),
Expanded(
child: _favoriteProperties.isEmpty
? const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.favorite_border, size: 64, color: Colors.grey),
SizedBox(height: 16),
Text('还没有收藏的房源', style: TextStyle(color: Colors.grey)),
SizedBox(height: 8),
Text('去探索页面发现心仪的房源吧!', style: TextStyle(color: Colors.grey)),
],
),
)
: ListView.builder(
itemCount: _favoriteProperties.length,
itemBuilder: (context, index) {
return _buildFavoritePropertyCard(_favoriteProperties[index]);
},
),
),
],
),
);
}
旅程管理
旅程页面使用TabBar展示不同状态的预订:
Widget _buildTripsPage() {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'我的旅程',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
DefaultTabController(
length: 3,
child: Column(
children: [
const TabBar(
tabs: [
Tab(text: '即将到来'),
Tab(text: '进行中'),
Tab(text: '已完成'),
],
),
const SizedBox(height: 16),
SizedBox(
height: 500,
child: TabBarView(
children: [
_buildBookingsList('pending'),
_buildBookingsList('confirmed'),
_buildBookingsList('completed'),
],
),
),
],
),
),
],
),
);
}
预订卡片设计
预订卡片展示预订的详细信息和状态:
Widget _buildBookingCard(Booking booking) {
return Card(
margin: const EdgeInsets.only(bottom: 12),
child: InkWell(
onTap: () => _showBookingDetail(booking),
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.teal.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
),
child: const Icon(Icons.home, color: Colors.teal, size: 30),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
booking.propertyTitle,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
'房东:${booking.hostName}',
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: booking.statusColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
),
child: Text(
booking.statusText,
style: TextStyle(
fontSize: 12,
color: booking.statusColor,
fontWeight: FontWeight.w500,
),
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Icon(Icons.calendar_today, size: 16, color: Colors.grey[600]),
const SizedBox(width: 4),
Text(
'${_formatDate(booking.checkInDate)} - ${_formatDate(booking.checkOutDate)}',
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
const SizedBox(width: 16),
Icon(Icons.nights_stay, size: 16, color: Colors.grey[600]),
const SizedBox(width: 4),
Text(
'${booking.nights}晚',
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
const SizedBox(width: 16),
Icon(Icons.people, size: 16, color: Colors.grey[600]),
const SizedBox(width: 4),
Text(
'${booking.guests}位房客',
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
],
),
const SizedBox(height: 8),
Row(
children: [
Text(
'总价:¥${booking.totalPrice.toInt()}',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.teal,
),
),
const Spacer(),
if (booking.status == 'pending')
TextButton(
onPressed: () => _cancelBooking(booking),
child: const Text('取消预订', style: TextStyle(color: Colors.red)),
),
],
),
],
),
),
),
);
}
预订流程
房源详情对话框
房源详情对话框展示房源的完整信息:
void _showPropertyDetail(BnbProperty property) {
showDialog(
context: context,
builder: (context) => Dialog(
child: Container(
width: MediaQuery.of(context).size.width * 0.9,
height: MediaQuery.of(context).size.height * 0.8,
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
property.title,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
),
IconButton(
onPressed: () => Navigator.pop(context),
icon: const Icon(Icons.close),
),
],
),
const SizedBox(height: 16),
Container(
height: 200,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.teal.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
),
child: const Icon(Icons.home, size: 80, color: Colors.teal),
),
const SizedBox(height: 16),
Expanded(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 评分和价格
Row(
children: [
Icon(Icons.star, color: Colors.orange, size: 20),
const SizedBox(width: 4),
Text(
property.rating.toStringAsFixed(1),
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(width: 8),
Text('(${property.reviewCount}条评价)'),
const Spacer(),
Text(
'${property.currency}${property.price.toInt()}/晚',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.teal,
),
),
],
),
const SizedBox(height: 16),
Text(
property.location,
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
),
const SizedBox(height: 16),
Text(
property.description,
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 16),
// 房源信息
const Text(
'房源信息',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Row(
children: [
_buildInfoChip(Icons.people, '${property.maxGuests}位房客'),
const SizedBox(width: 8),
_buildInfoChip(Icons.bed, '${property.bedrooms}间卧室'),
const SizedBox(width: 8),
_buildInfoChip(Icons.bathtub, '${property.bathrooms}间浴室'),
],
),
const SizedBox(height: 16),
// 房源设施
const Text(
'房源设施',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: property.amenities.map((amenity) {
return Chip(
label: Text(amenity),
backgroundColor: Colors.teal.withValues(alpha: 0.1),
);
}).toList(),
),
const SizedBox(height: 16),
// 房东信息
const Text(
'房东信息',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Row(
children: [
CircleAvatar(
backgroundColor: Colors.teal.withValues(alpha: 0.1),
child: Text(
property.hostName.substring(0, 1),
style: const TextStyle(color: Colors.teal, fontWeight: FontWeight.bold),
),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
property.hostName,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
if (property.isSuperhost)
const Text(
'超赞房东',
style: TextStyle(color: Colors.orange, fontSize: 12),
),
],
),
],
),
],
),
),
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
_showBookingDialog(property);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.teal,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
),
child: const Text('立即预订', style: TextStyle(fontSize: 16)),
),
),
],
),
),
),
);
}
预订对话框
预订对话框允许用户选择日期和房客数量:
void _showBookingDialog(BnbProperty property) {
DateTime? checkIn;
DateTime? checkOut;
int guests = 1;
showDialog(
context: context,
builder: (context) => StatefulBuilder(
builder: (context, setDialogState) => AlertDialog(
title: const Text('预订房源'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Expanded(
child: TextButton(
onPressed: () async {
final date = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime.now(),
lastDate: DateTime.now().add(const Duration(days: 365)),
);
if (date != null) {
setDialogState(() => checkIn = date);
}
},
child: Text(checkIn != null ? _formatDate(checkIn!) : '入住日期'),
),
),
Expanded(
child: TextButton(
onPressed: () async {
final date = await showDatePicker(
context: context,
initialDate: checkIn?.add(const Duration(days: 1)) ??
DateTime.now().add(const Duration(days: 1)),
firstDate: checkIn?.add(const Duration(days: 1)) ??
DateTime.now().add(const Duration(days: 1)),
lastDate: DateTime.now().add(const Duration(days: 365)),
);
if (date != null) {
setDialogState(() => checkOut = date);
}
},
child: Text(checkOut != null ? _formatDate(checkOut!) : '退房日期'),
),
),
],
),
const SizedBox(height: 16),
Row(
children: [
const Text('房客数量:'),
IconButton(
onPressed: guests > 1 ? () => setDialogState(() => guests--) : null,
icon: const Icon(Icons.remove),
),
Text('$guests'),
IconButton(
onPressed: guests < property.maxGuests ? () => setDialogState(() => guests++) : null,
icon: const Icon(Icons.add),
),
],
),
if (checkIn != null && checkOut != null)
Padding(
padding: const EdgeInsets.only(top: 16),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('${property.currency}${property.price.toInt()} × ${checkOut!.difference(checkIn!).inDays}晚'),
Text('${property.currency}${(property.price * checkOut!.difference(checkIn!).inDays).toInt()}'),
],
),
const Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('总价', style: TextStyle(fontWeight: FontWeight.bold)),
Text(
'${property.currency}${(property.price * checkOut!.difference(checkIn!).inDays).toInt()}',
style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.teal),
),
],
),
],
),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
TextButton(
onPressed: checkIn != null && checkOut != null ? () {
_createBooking(property, checkIn!, checkOut!, guests);
Navigator.pop(context);
} : null,
child: const Text('确认预订'),
),
],
),
),
);
}
创建预订
创建预订方法将新预订添加到预订列表:
void _createBooking(BnbProperty property, DateTime checkIn, DateTime checkOut, int guests) {
final booking = Booking(
id: 'booking_${_bookings.length}',
propertyId: property.id,
propertyTitle: property.title,
propertyImage: property.images.first,
checkInDate: checkIn,
checkOutDate: checkOut,
guests: guests,
totalPrice: property.price * checkOut.difference(checkIn).inDays,
status: 'pending',
bookingDate: DateTime.now(),
hostName: property.hostName,
guestName: '张三',
guestEmail: 'zhangsan@example.com',
guestPhone: '13800138000',
specialRequests: '',
);
setState(() {
_bookings.add(booking);
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('预订成功!请等待房东确认')),
);
}
消息功能
消息页面展示与房东的沟通记录:
Widget _buildInboxPage() {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'消息',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
Expanded(
child: ListView.builder(
itemCount: 5,
itemBuilder: (context, index) {
return _buildMessageCard(index);
},
),
),
],
),
);
}
Widget _buildMessageCard(int index) {
final messages = [
{'name': '张先生', 'message': '您好,请问房间有WiFi吗?', 'time': '2小时前', 'unread': true},
{'name': '李女士', 'message': '感谢您的入住,欢迎下次再来!', 'time': '1天前', 'unread': false},
{'name': '王先生', 'message': '您的预订已确认', 'time': '2天前', 'unread': false},
{'name': '陈女士', 'message': '房间钥匙在门口的密码盒里', 'time': '3天前', 'unread': false},
{'name': '刘先生', 'message': '欢迎入住,有问题随时联系', 'time': '5天前', 'unread': false},
];
final message = messages[index];
return Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.teal.withValues(alpha: 0.1),
child: Text(
(message['name']! as String).substring(0, 1),
style: const TextStyle(color: Colors.teal, fontWeight: FontWeight.bold),
),
),
title: Text(
message['name']! as String,
style: TextStyle(
fontWeight: message['unread'] as bool ? FontWeight.bold : FontWeight.normal,
),
),
subtitle: Text(
message['message']! as String,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
trailing: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
message['time']! as String,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
if (message['unread'] as bool)
Container(
margin: const EdgeInsets.only(top: 4),
width: 8,
height: 8,
decoration: const BoxDecoration(
color: Colors.teal,
shape: BoxShape.circle,
),
),
],
),
onTap: () {
// 打开聊天页面
},
),
);
}
个人中心
个人中心页面提供用户信息管理和应用设置:
Widget _buildProfilePage() {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'个人中心',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 24),
// 用户信息卡片
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
CircleAvatar(
radius: 30,
backgroundColor: Colors.teal.withValues(alpha: 0.1),
child: const Icon(Icons.person, size: 30, color: Colors.teal),
),
const SizedBox(width: 16),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'张三',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
SizedBox(height: 4),
Text(
'zhangsan@example.com',
style: TextStyle(color: Colors.grey),
),
SizedBox(height: 4),
Text(
'已验证用户',
style: TextStyle(color: Colors.green, fontSize: 12),
),
],
),
),
IconButton(
onPressed: () {
// 编辑个人信息
},
icon: const Icon(Icons.edit),
),
],
),
),
),
const SizedBox(height: 24),
// 功能菜单
Expanded(
child: ListView(
children: [
_buildMenuTile(Icons.person, '个人信息', '管理您的个人资料'),
_buildMenuTile(Icons.security, '账户安全', '密码和安全设置'),
_buildMenuTile(Icons.payment, '支付方式', '管理支付和账单'),
_buildMenuTile(Icons.notifications, '通知设置', '推送和邮件通知'),
_buildMenuTile(Icons.language, '语言设置', '选择您的语言'),
_buildMenuTile(Icons.help, '帮助中心', '常见问题和客服'),
_buildMenuTile(Icons.info, '关于我们', '版本信息和条款'),
const Divider(),
_buildMenuTile(Icons.logout, '退出登录', '', isLogout: true),
],
),
),
],
),
);
}
Widget _buildMenuTile(IconData icon, String title, String subtitle, {bool isLogout = false}) {
return ListTile(
leading: Icon(
icon,
color: isLogout ? Colors.red : Colors.teal,
),
title: Text(
title,
style: TextStyle(
color: isLogout ? Colors.red : null,
fontWeight: FontWeight.w500,
),
),
subtitle: subtitle.isNotEmpty ? Text(subtitle) : null,
trailing: const Icon(Icons.chevron_right),
onTap: () {
if (isLogout) {
_showLogoutDialog();
} else {
// 处理其他菜单项
}
},
);
}
用户体验优化
响应式设计
应用采用响应式设计,适配不同屏幕尺寸:
// 房源详情对话框的响应式设计
Container(
width: MediaQuery.of(context).size.width * 0.9,
height: MediaQuery.of(context).size.height * 0.8,
// ...
)
// 房源卡片的自适应布局
Container(
height: 200,
width: double.infinity,
// ...
)
交互反馈
应用提供丰富的交互反馈:
// 点击反馈
InkWell(
onTap: () => _showPropertyDetail(property),
borderRadius: BorderRadius.circular(12),
child: // ...
)
// 状态提示
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('预订成功!请等待房东确认')),
);
// 加载状态
CircularProgressIndicator()
视觉设计
应用采用现代化的视觉设计:
// 卡片阴影效果
Card(
elevation: 2,
shadowColor: Colors.black.withValues(alpha: 0.1),
// ...
)
// 渐变背景
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.teal.withValues(alpha: 0.1), Colors.white],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
// ...
)
// 圆角设计
BorderRadius.circular(12)
性能优化
列表优化
使用ListView.builder实现高效的列表渲染:
ListView.builder(
itemCount: filteredProperties.length,
itemBuilder: (context, index) {
return _buildPropertyCard(filteredProperties[index]);
},
)
状态管理
合理使用setState进行状态更新:
setState(() {
_favoriteProperties.remove(property);
});
内存管理
及时释放不需要的资源:
void dispose() {
// 清理资源
super.dispose();
}
扩展功能建议
地图集成
集成地图功能显示房源位置:
// 使用google_maps_flutter包
GoogleMap(
initialCameraPosition: CameraPosition(
target: LatLng(property.latitude, property.longitude),
zoom: 15,
),
markers: {
Marker(
markerId: MarkerId(property.id),
position: LatLng(property.latitude, property.longitude),
),
},
)
支付集成
集成支付功能:
// 使用stripe_payment或其他支付SDK
void _processPayment() async {
// 支付处理逻辑
}
推送通知
集成推送通知功能:
// 使用firebase_messaging
void _setupPushNotifications() {
// 推送通知设置
}
离线支持
添加离线数据缓存:
// 使用shared_preferences或sqflite
void _cacheData() {
// 数据缓存逻辑
}
总结
本教程完整展示了如何使用Flutter开发一个功能丰富的民宿预订助手应用。应用包含了房源浏览、搜索筛选、预订管理、用户交互等核心功能,采用了现代化的UI设计和良好的用户体验。
主要特点
- 完整的功能体系:涵盖了民宿预订的完整流程
- 优秀的用户体验:直观的界面设计和流畅的交互
- 灵活的数据模型:支持复杂的业务逻辑
- 可扩展的架构:便于后续功能扩展
学习收获
通过本教程,你将掌握:
- Flutter应用的整体架构设计
- 复杂数据模型的设计和管理
- 用户界面的设计和实现
- 状态管理和数据流控制
- 用户交互和体验优化
这个民宿预订助手应用为你提供了一个完整的Flutter开发实践案例,可以作为学习Flutter开发的重要参考,也可以作为实际项目开发的基础框架。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐
所有评论(0)