Flutter框架跨平台鸿蒙开发——二手物品交易APP的开发流程
本文介绍了使用Flutter框架开发跨平台鸿蒙二手交易APP的流程。APP核心功能包括物品浏览、搜索、分类筛选、发布和管理二手物品等。开发环境配置包括Flutter SDK、Dart SDK和IDE的安装。文章详细展示了数据模型设计和服务层实现代码,包括SecondHandItem类定义和物品服务管理。该APP适用于希望买卖二手物品或关注资源循环利用的用户群体,实现了"一次编写,多处运行
Flutter框架跨平台鸿蒙开发——
🚀运行效果展示



Flutter框架跨平台鸿蒙开发——二手物品交易APP的开发流程
前言
随着移动互联网的快速发展,跨平台开发技术已经成为移动应用开发的重要趋势。Flutter作为Google推出的开源UI工具包,凭借其"一次编写,多处运行"的优势,已经成为跨平台开发的主流选择。同时,鸿蒙OS作为华为自主研发的分布式操作系统,也在不断发展壮大,为开发者提供了更广阔的应用场景。
本文将详细介绍如何使用Flutter框架开发一款跨平台的二手物品交易APP,并成功运行在鸿蒙OS上。通过本文的学习,读者将了解Flutter跨平台开发的基本流程,以及如何针对鸿蒙OS进行适配和优化。
APP介绍
应用概述
二手物品交易APP是一款为用户提供二手物品买卖服务的移动应用,旨在帮助用户便捷地发布、浏览和购买二手物品,促进资源的循环利用。
核心功能
- 物品浏览:用户可以浏览平台上的二手物品,查看物品的详细信息。
- 物品搜索:用户可以通过关键词搜索感兴趣的物品。
- 分类筛选:用户可以按照物品分类进行筛选,快速找到目标物品。
- 发布物品:用户可以发布自己的二手物品,包括物品图片、标题、价格、描述等信息。
- 物品详情:用户可以查看物品的详细信息,包括图片、描述、价格、发布者信息等。
- 联系卖家:用户可以通过APP联系物品的发布者,进行交易协商。
- 个人中心:用户可以管理自己发布的物品,查看已售出的物品等。
目标用户
- 买家:希望以较低价格购买二手物品的用户。
- 卖家:希望出售闲置物品获取收益的用户。
- 环保主义者:关注资源循环利用的用户。
开发环境搭建
技术栈选择
- 前端框架:Flutter 3.0+
- 开发语言:Dart
- 状态管理:Provider(本文使用 setState 进行简单状态管理)
- 网络请求:Dio(本文使用模拟数据)
- UI组件:Flutter内置组件
环境配置
- 安装Flutter SDK:从Flutter官网下载并安装Flutter SDK,配置环境变量。
- 安装Dart SDK:Flutter SDK已包含Dart SDK。
- 安装IDE:推荐使用Android Studio或VS Code,并安装Flutter和Dart插件。
- 配置鸿蒙开发环境:参考华为官方文档,安装DevEco Studio和相关依赖。
- 创建Flutter项目:使用
flutter create命令创建新项目。
核心功能实现及代码展示
1. 数据模型设计
首先,我们需要设计二手物品的数据模型,定义物品的基本信息结构。
/// 二手物品数据模型
/// 用于存储和管理二手物品的详细信息
class SecondHandItem {
/// 物品唯一标识符
final String id;
/// 物品标题
final String title;
/// 物品描述
final String description;
/// 物品价格
final double price;
/// 物品图片URL
final String imageUrl;
/// 物品分类
final String category;
/// 发布者ID
final String publisherId;
/// 发布者名称
final String publisherName;
/// 发布时间
final DateTime publishTime;
/// 物品状态(全新、九成新、八成新等)
final String condition;
/// 交易地点
final String location;
/// 是否已售出
final bool isSold;
/// 构造函数
SecondHandItem({
required this.id,
required this.title,
required this.description,
required this.price,
required this.imageUrl,
required this.category,
required this.publisherId,
required this.publisherName,
required this.publishTime,
required this.condition,
required this.location,
this.isSold = false,
});
/// 从JSON创建SecondHandItem实例
factory SecondHandItem.fromJson(Map<String, dynamic> json) {
return SecondHandItem(
id: json['id'],
title: json['title'],
description: json['description'],
price: json['price'],
imageUrl: json['imageUrl'],
category: json['category'],
publisherId: json['publisherId'],
publisherName: json['publisherName'],
publishTime: DateTime.parse(json['publishTime']),
condition: json['condition'],
location: json['location'],
isSold: json['isSold'] ?? false,
);
}
/// 转换为JSON格式
Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
'description': description,
'price': price,
'imageUrl': imageUrl,
'category': category,
'publisherId': publisherId,
'publisherName': publisherName,
'publishTime': publishTime.toIso8601String(),
'condition': condition,
'location': location,
'isSold': isSold,
};
}
/// 创建副本并更新指定字段
SecondHandItem copyWith({
String? id,
String? title,
String? description,
double? price,
String? imageUrl,
String? category,
String? publisherId,
String? publisherName,
DateTime? publishTime,
String? condition,
String? location,
bool? isSold,
}) {
return SecondHandItem(
id: id ?? this.id,
title: title ?? this.title,
description: description ?? this.description,
price: price ?? this.price,
imageUrl: imageUrl ?? this.imageUrl,
category: category ?? this.category,
publisherId: publisherId ?? this.publisherId,
publisherName: publisherName ?? this.publisherName,
publishTime: publishTime ?? this.publishTime,
condition: condition ?? this.condition,
location: location ?? this.location,
isSold: isSold ?? this.isSold,
);
}
}
2. 服务层实现
接下来,我们实现二手物品服务类,用于管理物品数据和相关操作。
/// 二手物品服务类
/// 用于管理二手物品数据和相关操作
import '../models/second_hand_item_model.dart';
import '../utils/mock_data.dart';
class SecondHandItemService {
/// 获取所有二手物品列表
Future<List<SecondHandItem>> getItems() async {
// 模拟网络请求延迟
await Future.delayed(Duration(milliseconds: 500));
// 返回模拟数据
return MockData.secondHandItems;
}
/// 根据分类获取物品列表
Future<List<SecondHandItem>> getItemsByCategory(String category) async {
await Future.delayed(Duration(milliseconds: 300));
if (category == '全部') {
return MockData.secondHandItems;
}
return MockData.secondHandItems
.where((item) => item.category == category)
.toList();
}
/// 根据ID获取物品详情
Future<SecondHandItem?> getItemById(String id) async {
await Future.delayed(Duration(milliseconds: 200));
return MockData.secondHandItems
.firstWhere((item) => item.id == id, orElse: () => throw Exception('Item not found'));
}
/// 发布新物品
Future<SecondHandItem> publishItem(SecondHandItem item) async {
await Future.delayed(Duration(milliseconds: 500));
// 在实际应用中,这里会调用API将物品保存到服务器
// 这里我们只是将物品添加到模拟数据中
MockData.secondHandItems.insert(0, item);
return item;
}
/// 搜索物品
Future<List<SecondHandItem>> searchItems(String keyword) async {
await Future.delayed(Duration(milliseconds: 300));
if (keyword.isEmpty) {
return MockData.secondHandItems;
}
return MockData.secondHandItems
.where((item) =>
item.title.toLowerCase().contains(keyword.toLowerCase()) ||
item.description.toLowerCase().contains(keyword.toLowerCase()))
.toList();
}
/// 获取用户发布的物品
Future<List<SecondHandItem>> getUserItems(String userId) async {
await Future.delayed(Duration(milliseconds: 300));
return MockData.secondHandItems
.where((item) => item.publisherId == userId)
.toList();
}
/// 标记物品为已售出
Future<void> markAsSold(String itemId) async {
await Future.delayed(Duration(milliseconds: 200));
final itemIndex = MockData.secondHandItems
.indexWhere((item) => item.id == itemId);
if (itemIndex != -1) {
final updatedItem = MockData.secondHandItems[itemIndex].copyWith(isSold: true);
MockData.secondHandItems[itemIndex] = updatedItem;
}
}
}
3. 物品列表页面
物品列表页面是APP的核心页面之一,用于展示所有二手物品,并提供搜索和分类筛选功能。
/// 二手物品首页
/// 展示二手物品列表,包含搜索和分类筛选功能
import 'package:flutter/material.dart';
import '../services/second_hand_item_service.dart';
import '../models/second_hand_item_model.dart';
import '../utils/mock_data.dart';
import 'second_hand_detail_page.dart';
import 'second_hand_publish_page.dart';
class SecondHandHomePage extends StatefulWidget {
_SecondHandHomePageState createState() => _SecondHandHomePageState();
}
class _SecondHandHomePageState extends State<SecondHandHomePage> {
final SecondHandItemService _itemService = SecondHandItemService();
List<SecondHandItem> _items = [];
List<SecondHandItem> _filteredItems = [];
String _selectedCategory = '全部';
String _searchKeyword = '';
bool _isLoading = true;
bool _isRefreshing = false;
void initState() {
super.initState();
_loadItems();
}
/// 加载物品列表
Future<void> _loadItems() async {
setState(() {
_isLoading = true;
});
try {
final items = await _itemService.getItems();
setState(() {
_items = items;
_filteredItems = items;
_isLoading = false;
});
} catch (error) {
print('加载物品失败: $error');
setState(() {
_isLoading = false;
});
}
}
/// 刷新物品列表
Future<void> _refreshItems() async {
setState(() {
_isRefreshing = true;
});
await _loadItems();
setState(() {
_isRefreshing = false;
});
}
/// 筛选物品
void _filterItems() {
if (_selectedCategory == '全部' && _searchKeyword.isEmpty) {
setState(() {
_filteredItems = _items;
});
return;
}
List<SecondHandItem> filtered = _items;
// 按分类筛选
if (_selectedCategory != '全部') {
filtered = filtered.where((item) => item.category == _selectedCategory).toList();
}
// 按关键词搜索
if (_searchKeyword.isNotEmpty) {
filtered = filtered.where((item) =>
item.title.toLowerCase().contains(_searchKeyword.toLowerCase()) ||
item.description.toLowerCase().contains(_searchKeyword.toLowerCase())).toList();
}
setState(() {
_filteredItems = filtered;
});
}
/// 处理分类选择
void _handleCategorySelect(String category) {
setState(() {
_selectedCategory = category;
});
_filterItems();
}
/// 处理搜索提交
void _handleSearchSubmit(String keyword) {
setState(() {
_searchKeyword = keyword;
});
_filterItems();
}
/// 导航到物品详情页
void _navigateToDetail(String itemId) {
Navigator.pushNamed(
context,
'/second_hand_detail',
arguments: itemId,
);
}
/// 导航到发布物品页
void _navigateToPublish() {
Navigator.pushNamed(
context,
'/second_hand_publish',
).then((_) => _loadItems()); // 发布完成后刷新列表
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('二手物品交易'),
actions: [
IconButton(
icon: Icon(Icons.person),
onPressed: () {
// 导航到个人中心
Navigator.pushNamed(context, '/second_hand_profile');
},
),
],
),
body: Column(
children: [
// 搜索栏
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
decoration: InputDecoration(
hintText: '搜索物品...',
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
),
contentPadding: EdgeInsets.symmetric(vertical: 8),
),
onSubmitted: _handleSearchSubmit,
),
),
// 分类筛选栏
Container(
height: 50,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: MockData.secondHandCategories.length,
itemBuilder: (context, index) {
final category = MockData.secondHandCategories[index];
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: ChoiceChip(
label: Text(category),
selected: _selectedCategory == category,
onSelected: (_) => _handleCategorySelect(category),
selectedColor: Colors.blue,
labelStyle: TextStyle(
color: _selectedCategory == category ? Colors.white : Colors.black,
),
),
);
},
),
),
// 物品列表
Expanded(
child: _isLoading
? Center(child: CircularProgressIndicator())
: RefreshIndicator(
onRefresh: _refreshItems,
child: _filteredItems.isEmpty
? Center(child: Text('暂无物品'))
: ListView.builder(
itemCount: _filteredItems.length,
itemBuilder: (context, index) {
final item = _filteredItems[index];
return _buildItemCard(item);
},
),
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: _navigateToPublish,
child: Icon(Icons.add),
tooltip: '发布物品',
),
);
}
/// 构建物品卡片
Widget _buildItemCard(SecondHandItem item) {
return Card(
margin: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: InkWell(
onTap: () => _navigateToDetail(item.id),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 物品图片
Container(
width: 100,
height: 100,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12),
bottomLeft: Radius.circular(12),
),
image: DecorationImage(
image: AssetImage(item.imageUrl),
fit: BoxFit.cover,
),
),
),
// 物品信息
Expanded(
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题和价格
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
item.title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
SizedBox(width: 8),
Text(
'¥${item.price}',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.red,
),
),
],
),
// 描述
SizedBox(height: 4),
Text(
item.description,
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
// 其他信息
SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Text(
item.condition,
style: TextStyle(
fontSize: 12,
color: Colors.grey[500],
),
),
SizedBox(width: 12),
Text(
item.location,
style: TextStyle(
fontSize: 12,
color: Colors.grey[500],
),
),
],
),
Text(
'${item.publisherName}',
style: TextStyle(
fontSize: 12,
color: Colors.grey[500],
),
),
],
),
],
),
),
),
],
),
),
);
}
}
4. 物品详情页面
物品详情页面用于展示物品的详细信息,包括图片、描述、价格、发布者信息等,并提供联系卖家的功能。
/// 二手物品详情页
/// 展示二手物品的详细信息,包括图片、描述、价格等
import 'package:flutter/material.dart';
import '../services/second_hand_item_service.dart';
import '../models/second_hand_item_model.dart';
class SecondHandDetailPage extends StatefulWidget {
final String itemId;
const SecondHandDetailPage({Key? key, required this.itemId}) : super(key: key);
_SecondHandDetailPageState createState() => _SecondHandDetailPageState();
}
class _SecondHandDetailPageState extends State<SecondHandDetailPage> {
final SecondHandItemService _itemService = SecondHandItemService();
SecondHandItem? _item;
bool _isLoading = true;
bool _isError = false;
String _errorMessage = '';
void initState() {
super.initState();
_loadItemDetail();
}
/// 加载物品详情
Future<void> _loadItemDetail() async {
setState(() {
_isLoading = true;
_isError = false;
});
try {
final item = await _itemService.getItemById(widget.itemId);
setState(() {
_item = item;
_isLoading = false;
});
} catch (error) {
print('加载物品详情失败: $error');
setState(() {
_isError = true;
_errorMessage = '加载失败,请重试';
_isLoading = false;
});
}
}
/// 联系发布者
void _contactPublisher() {
if (_item == null) return;
// 在实际应用中,这里会跳转到聊天页面或显示联系方式
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('联系卖家'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('卖家: ${_item!.publisherName}'),
SizedBox(height: 8),
Text('物品: ${_item!.title}'),
SizedBox(height: 8),
Text('价格: ¥${_item!.price}'),
SizedBox(height: 16),
Text('联系方式将通过系统消息发送给您'),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('取消'),
),
TextButton(
onPressed: () {
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('联系信息已发送')),
);
},
child: Text('确定'),
),
],
);
},
);
}
/// 分享物品
void _shareItem() {
if (_item == null) return;
// 在实际应用中,这里会调用系统分享功能
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('分享功能已触发')),
);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('物品详情'),
actions: [
IconButton(
icon: Icon(Icons.share),
onPressed: _shareItem,
tooltip: '分享',
),
],
),
body: _isLoading
? Center(child: CircularProgressIndicator())
: _isError
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(_errorMessage),
SizedBox(height: 16),
ElevatedButton(
onPressed: _loadItemDetail,
child: Text('重试'),
),
],
),
)
: _item == null
? Center(child: Text('物品不存在'))
: _buildDetailContent(),
bottomNavigationBar: _item != null ? _buildBottomBar() : null,
);
}
/// 构建详情内容
Widget _buildDetailContent() {
if (_item == null) return Container();
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 物品图片
Container(
width: double.infinity,
height: 300,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(_item!.imageUrl),
fit: BoxFit.cover,
),
),
),
// 物品信息
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题和价格
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
_item!.title,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
Text(
'¥${_item!.price}',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.red,
),
),
],
),
// 物品状态和交易地点
SizedBox(height: 12),
Row(
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(12),
),
child: Text(
_item!.condition,
style: TextStyle(
fontSize: 14,
color: Colors.grey[700],
),
),
),
SizedBox(width: 16),
Icon(Icons.location_on, size: 16, color: Colors.grey[600]),
SizedBox(width: 4),
Text(
_item!.location,
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
],
),
// 发布时间
SizedBox(height: 8),
Text(
'发布时间: ${_formatDateTime(_item!.publishTime)}',
style: TextStyle(
fontSize: 12,
color: Colors.grey[500],
),
),
// 描述
SizedBox(height: 20),
Text(
'物品描述',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
Text(
_item!.description,
style: TextStyle(
fontSize: 14,
height: 1.5,
color: Colors.grey[800],
),
),
// 发布者信息
SizedBox(height: 24),
Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.grey[50],
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'发布者信息',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 12),
Row(
children: [
// 发布者头像(这里使用默认头像)
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey[300],
),
child: Center(
child: Text(
_item!.publisherName.substring(0, 1),
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.grey[600],
),
),
),
),
SizedBox(width: 16),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_item!.publisherName,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
SizedBox(height: 4),
Text(
'查看主页',
style: TextStyle(
fontSize: 14,
color: Colors.blue,
),
),
],
),
],
),
],
),
),
],
),
),
// 底部空间,确保内容不被底部导航栏遮挡
SizedBox(height: 80),
],
),
);
}
/// 构建底部导航栏
Widget _buildBottomBar() {
if (_item == null) return Container();
return Container(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
border: Border(top: BorderSide(color: Colors.grey[200]!)),
color: Colors.white,
),
child: Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: _contactPublisher,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
padding: EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text(
'联系卖家',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
);
}
/// 格式化日期时间
String _formatDateTime(DateTime dateTime) {
final now = DateTime.now();
final difference = now.difference(dateTime);
if (difference.inDays > 0) {
return '${difference.inDays}天前';
} else if (difference.inHours > 0) {
return '${difference.inHours}小时前';
} else if (difference.inMinutes > 0) {
return '${difference.inMinutes}分钟前';
} else {
return '刚刚';
}
}
}
5. 发布物品页面
发布物品页面用于用户发布新的二手物品,包括填写物品信息、上传图片等功能。
/// 发布二手物品页面
/// 用于用户发布新的二手物品
import 'package:flutter/material.dart';
import '../services/second_hand_item_service.dart';
import '../models/second_hand_item_model.dart';
import '../utils/mock_data.dart';
class SecondHandPublishPage extends StatefulWidget {
_SecondHandPublishPageState createState() => _SecondHandPublishPageState();
}
class _SecondHandPublishPageState extends State<SecondHandPublishPage> {
final SecondHandItemService _itemService = SecondHandItemService();
final _formKey = GlobalKey<FormState>();
// 表单字段
String _title = '';
String _description = '';
double _price = 0.0;
String _category = MockData.secondHandCategories[1]; // 默认选择第一个分类
String _condition = '九成新'; // 默认选择九成新
String _location = '';
String _imageUrl = 'images/R-C.jpg'; // 默认图片
bool _isSubmitting = false;
// 物品状态选项
final List<String> _conditionOptions = [
'全新',
'九成新',
'八成新',
'七成新',
'六成新',
'五成新及以下',
];
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('发布二手物品'),
),
body: SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 图片上传
Container(
width: double.infinity,
height: 200,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]!),
borderRadius: BorderRadius.circular(8),
image: DecorationImage(
image: AssetImage(_imageUrl),
fit: BoxFit.cover,
),
),
child: Center(
child: ElevatedButton(
onPressed: _selectImage,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black.withOpacity(0.5),
),
child: Text('更换图片'),
),
),
),
SizedBox(height: 20),
// 标题
TextFormField(
decoration: InputDecoration(
labelText: '物品标题',
hintText: '请输入物品标题',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入物品标题';
}
if (value.length > 50) {
return '标题长度不能超过50个字符';
}
return null;
},
onSaved: (value) => _title = value!,
),
SizedBox(height: 16),
// 价格
TextFormField(
decoration: InputDecoration(
labelText: '物品价格',
hintText: '请输入物品价格',
prefixText: '¥',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
keyboardType: TextInputType.numberWithOptions(decimal: true),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入物品价格';
}
final price = double.tryParse(value);
if (price == null || price <= 0) {
return '请输入有效的价格';
}
return null;
},
onSaved: (value) => _price = double.parse(value!),
),
SizedBox(height: 16),
// 分类
DropdownButtonFormField<String>(
decoration: InputDecoration(
labelText: '物品分类',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
value: _category,
items: MockData.secondHandCategories.skip(1).map((category) {
return DropdownMenuItem(
value: category,
child: Text(category),
);
}).toList(),
onChanged: (value) {
setState(() {
_category = value!;
});
},
validator: (value) {
if (value == null || value.isEmpty) {
return '请选择物品分类';
}
return null;
},
),
SizedBox(height: 16),
// 物品状态
DropdownButtonFormField<String>(
decoration: InputDecoration(
labelText: '物品状态',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
value: _condition,
items: _conditionOptions.map((condition) {
return DropdownMenuItem(
value: condition,
child: Text(condition),
);
}).toList(),
onChanged: (value) {
setState(() {
_condition = value!;
});
},
validator: (value) {
if (value == null || value.isEmpty) {
return '请选择物品状态';
}
return null;
},
),
SizedBox(height: 16),
// 交易地点
TextFormField(
decoration: InputDecoration(
labelText: '交易地点',
hintText: '请输入交易地点',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入交易地点';
}
return null;
},
onSaved: (value) => _location = value!,
),
SizedBox(height: 16),
// 描述
TextFormField(
decoration: InputDecoration(
labelText: '物品描述',
hintText: '请详细描述物品的成色、使用情况等信息',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
alignLabelWithHint: true,
),
maxLines: 4,
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入物品描述';
}
if (value.length < 10) {
return '描述长度不能少于10个字符';
}
if (value.length > 500) {
return '描述长度不能超过500个字符';
}
return null;
},
onSaved: (value) => _description = value!,
),
SizedBox(height: 32),
// 发布按钮
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _submitForm,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
padding: EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: _isSubmitting
? SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: Text(
'发布物品',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
),
SizedBox(height: 32),
],
),
),
),
);
}
/// 选择图片
void _selectImage() {
// 在实际应用中,这里会调用系统相册或相机
// 这里我们只是模拟更换图片
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('选择图片'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
GestureDetector(
onTap: () {
setState(() {
_imageUrl = 'images/R-C.jpg';
});
Navigator.pop(context);
},
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('images/R-C.jpg'),
fit: BoxFit.cover,
),
),
),
),
SizedBox(height: 16),
GestureDetector(
onTap: () {
setState(() {
_imageUrl = 'images/u=174051936,621369760&fm=253&gp=0.jpg';
});
Navigator.pop(context);
},
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('images/u=174051936,621369760&fm=253&gp=0.jpg'),
fit: BoxFit.cover,
),
),
),
),
SizedBox(height: 16),
GestureDetector(
onTap: () {
setState(() {
_imageUrl = 'images/1687770031227597.png';
});
Navigator.pop(context);
},
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('images/1687770031227597.png'),
fit: BoxFit.cover,
),
),
),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('取消'),
),
],
);
},
);
}
/// 提交表单
void _submitForm() {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
_publishItem();
}
}
/// 发布物品
Future<void> _publishItem() async {
setState(() {
_isSubmitting = true;
});
try {
final item = SecondHandItem(
id: DateTime.now().millisecondsSinceEpoch.toString(),
title: _title,
description: _description,
price: _price,
imageUrl: _imageUrl,
category: _category,
publisherId: 'current_user', // 实际应用中应该使用当前登录用户的ID
publisherName: '当前用户', // 实际应用中应该使用当前登录用户的名称
publishTime: DateTime.now(),
condition: _condition,
location: _location,
isSold: false,
);
await _itemService.publishItem(item);
// 发布成功,返回上一页
Navigator.pop(context, true);
} catch (error) {
print('发布物品失败: $error');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('发布失败,请重试')),
);
} finally {
setState(() {
_isSubmitting = false;
});
}
}
}
6. 个人中心页面
个人中心页面用于展示用户信息,管理用户发布的物品,以及提供其他功能入口。
/// 个人中心页面
/// 展示用户信息和相关操作
import 'package:flutter/material.dart';
import '../services/second_hand_item_service.dart';
import '../models/second_hand_item_model.dart';
import 'second_hand_detail_page.dart';
import 'second_hand_publish_page.dart';
class SecondHandProfilePage extends StatefulWidget {
_SecondHandProfilePageState createState() => _SecondHandProfilePageState();
}
class _SecondHandProfilePageState extends State<SecondHandProfilePage> {
final SecondHandItemService _itemService = SecondHandItemService();
List<SecondHandItem> _myItems = [];
List<SecondHandItem> _soldItems = [];
bool _isLoading = true;
void initState() {
super.initState();
_loadMyItems();
}
/// 加载用户发布的物品
Future<void> _loadMyItems() async {
setState(() {
_isLoading = true;
});
try {
// 模拟获取当前用户ID
final currentUserId = 'current_user';
final items = await _itemService.getUserItems(currentUserId);
// 分离已售出和未售出的物品
final myItems = items.where((item) => !item.isSold).toList();
final soldItems = items.where((item) => item.isSold).toList();
setState(() {
_myItems = myItems;
_soldItems = soldItems;
_isLoading = false;
});
} catch (error) {
print('加载用户物品失败: $error');
setState(() {
_isLoading = false;
});
}
}
/// 标记物品为已售出
Future<void> _markAsSold(String itemId) async {
try {
await _itemService.markAsSold(itemId);
await _loadMyItems();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('物品已标记为已售出')),
);
} catch (error) {
print('标记物品失败: $error');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('操作失败,请重试')),
);
}
}
/// 导航到物品详情页
void _navigateToDetail(String itemId) {
Navigator.pushNamed(
context,
'/second_hand_detail',
arguments: itemId,
);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('个人中心'),
),
body: _isLoading
? Center(child: CircularProgressIndicator())
: SingleChildScrollView(
child: Column(
children: [
// 用户信息卡片
_buildUserInfoCard(),
// 功能菜单
_buildFunctionMenu(),
// 我的发布
_buildMyItemsSection(),
// 已售出
_buildSoldItemsSection(),
],
),
),
);
}
/// 构建用户信息卡片
Widget _buildUserInfoCard() {
return Container(
padding: EdgeInsets.all(20),
color: Colors.blue,
child: Row(
children: [
// 用户头像
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: Center(
child: Text(
'用',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
),
),
SizedBox(width: 20),
// 用户信息
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'当前用户',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
SizedBox(height: 8),
Text(
'ID: current_user',
style: TextStyle(
fontSize: 14,
color: Colors.white.withOpacity(0.8),
),
),
SizedBox(height: 4),
Text(
'注册时间: 2024-01-01',
style: TextStyle(
fontSize: 14,
color: Colors.white.withOpacity(0.8),
),
),
],
),
],
),
);
}
/// 构建功能菜单
Widget _buildFunctionMenu() {
return Container(
margin: EdgeInsets.symmetric(vertical: 16),
padding: EdgeInsets.symmetric(horizontal: 16),
child: GridView.count(
crossAxisCount: 4,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
children: [
_buildMenuItem(Icons.add_circle, '发布物品', () {
Navigator.pushNamed(
context,
'/second_hand_publish',
).then((_) => _loadMyItems());
}),
_buildMenuItem(Icons.favorite, '我的收藏', () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('收藏功能开发中')),
);
}),
_buildMenuItem(Icons.chat, '我的消息', () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('消息功能开发中')),
);
}),
_buildMenuItem(Icons.settings, '设置', () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('设置功能开发中')),
);
}),
],
),
);
}
/// 构建菜单项
Widget _buildMenuItem(IconData icon, String title, VoidCallback onTap) {
return GestureDetector(
onTap: onTap,
child: Column(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey[100],
),
child: Icon(icon, size: 28, color: Colors.blue),
),
SizedBox(height: 8),
Text(
title,
style: TextStyle(fontSize: 12),
textAlign: TextAlign.center,
),
],
),
);
}
/// 构建我的发布部分
Widget _buildMyItemsSection() {
return Container(
margin: EdgeInsets.symmetric(vertical: 16),
padding: EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'我的发布',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
TextButton(
onPressed: () {
Navigator.pushNamed(
context,
'/second_hand_publish',
).then((_) => _loadMyItems());
},
child: Text('发布新物品'),
),
],
),
if (_myItems.isEmpty)
Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: Text('暂无发布物品'),
)
else
Column(
children: _myItems.map((item) => _buildItemCard(item, true)).toList(),
),
],
),
);
}
/// 构建已售出部分
Widget _buildSoldItemsSection() {
return Container(
margin: EdgeInsets.symmetric(vertical: 16),
padding: EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'已售出',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
if (_soldItems.isEmpty)
Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: Text('暂无已售出物品'),
)
else
Column(
children: _soldItems.map((item) => _buildItemCard(item, false)).toList(),
),
],
),
);
}
/// 构建物品卡片
Widget _buildItemCard(SecondHandItem item, bool showMarkAsSold) {
return Card(
margin: EdgeInsets.symmetric(vertical: 8),
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
child: InkWell(
onTap: () => _navigateToDetail(item.id),
child: Padding(
padding: EdgeInsets.all(12),
child: Row(
children: [
// 物品图片
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
image: DecorationImage(
image: AssetImage(item.imageUrl),
fit: BoxFit.cover,
),
),
),
SizedBox(width: 12),
// 物品信息
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 4),
Text(
'¥${item.price}',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.red,
),
),
SizedBox(height: 4),
Text(
item.condition,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
],
),
),
// 操作按钮
if (showMarkAsSold)
IconButton(
onPressed: () => _markAsSold(item.id),
icon: Icon(Icons.check_circle_outline),
tooltip: '标记为已售出',
),
],
),
),
),
);
}
}
7. 路由配置
最后,我们需要在main.dart文件中配置APP的路由,确保各个页面之间能够正确导航。
/// 应用入口文件
/// 配置应用路由和主题
import 'package:flutter/material.dart';
import 'pages/second_hand_home_page.dart';
import 'pages/second_hand_detail_page.dart';
import 'pages/second_hand_publish_page.dart';
import 'pages/second_hand_profile_page.dart';
void main() {
runApp(const MyApp());
}
/// 应用主类
class MyApp extends StatelessWidget {
/// 构造函数
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: '二手物品交易',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: SecondHandHomePage(),
routes: {
'/second_hand_detail': (context) => SecondHandDetailPage(
itemId: ModalRoute.of(context)?.settings.arguments as String,
),
'/second_hand_publish': (context) => SecondHandPublishPage(),
'/second_hand_profile': (context) => SecondHandProfilePage(),
},
debugShowCheckedModeBanner: false,
);
}
}
开发流程总结
开发过程中遇到的问题及解决方案
-
跨平台适配问题
- 问题:Flutter在不同平台上的渲染效果存在差异,特别是在鸿蒙OS上。
- 解决方案:使用Flutter的LayoutBuilder和MediaQuery组件,根据不同屏幕尺寸和平台进行适配。
-
图片加载优化
- 问题:大量图片加载导致APP性能下降。
- 解决方案:使用缓存策略,延迟加载不在可视区域内的图片,优化图片大小和格式。
-
状态管理复杂性
- 问题:随着功能的增加,状态管理变得越来越复杂。
- 解决方案:对于复杂应用,建议使用Provider、Bloc等状态管理库,提高代码可维护性。
-
网络请求处理
- 问题:网络请求失败时的错误处理和用户体验。
- 解决方案:实现统一的网络请求封装,添加加载状态、错误状态和重试机制。
性能优化措施
-
UI优化
- 使用const构造函数创建不可变widget。
- 避免在build方法中执行耗时操作。
- 使用ListView.builder等懒加载组件。
-
内存优化
- 及时释放不再使用的资源。
- 使用WeakReference管理大对象。
- 避免内存泄漏。
-
网络优化
- 实现请求缓存。
- 使用合适的网络请求库,如Dio。
- 优化API设计,减少不必要的网络请求。
测试和部署
-
测试
- 单元测试:测试各个功能模块的逻辑。
- 集成测试:测试多个模块之间的交互。
- UI测试:测试用户界面的渲染和交互。
- 性能测试:测试APP的性能表现。
-
部署
- 鸿蒙OS:生成HAP包,通过AppGallery Connect发布。
- Android:生成APK或AAB包,通过Google Play发布。
- iOS:生成IPA包,通过App Store发布。
未来展望
功能扩展计划
- 实时聊天功能:实现买家和卖家之间的实时沟通。
- 支付功能:集成第三方支付平台,实现线上交易。
- 评价系统:建立买卖双方的评价机制,提高交易可信度。
- 物流跟踪:对于需要邮寄的物品,提供物流跟踪功能。
- 推荐系统:根据用户的浏览和购买历史,推荐相关物品。
技术升级方向
- 使用Flutter 3.0+的新特性:如Impeller渲染引擎,提高APP性能。
- 采用更先进的状态管理方案:如Riverpod、Bloc等。
- 集成机器学习:实现更智能的物品推荐和搜索。
- 使用Flutter Web:扩展到Web平台,实现全平台覆盖。
- 优化鸿蒙OS特有功能:充分利用鸿蒙OS的分布式能力。
总结
通过本文的介绍,我们详细了解了如何使用Flutter框架开发一款跨平台的二手物品交易APP,并成功运行在鸿蒙OS上。从数据模型设计到页面实现,从功能开发到测试部署,我们完整地展示了整个开发流程。
Flutter作为一款优秀的跨平台开发框架,不仅可以大幅提高开发效率,还能保证APP在不同平台上的一致性和性能。而鸿蒙OS作为一款新兴的分布式操作系统,为开发者提供了更广阔的应用场景和创新空间。
二手物品交易APP的开发过程中,我们注重用户体验和功能完整性,同时也关注性能优化和代码质量。通过合理的架构设计和技术选型,我们成功构建了一款功能完善、性能良好的跨平台应用。
未来,随着Flutter和鸿蒙OS的不断发展,我们可以期待开发出更多优秀的跨平台应用,为用户带来更好的使用体验。同时,我们也将继续探索新的技术和方法,不断提升自己的开发能力和产品质量。
附录:项目结构
lib/
├── models/
│ └── second_hand_item_model.dart // 二手物品数据模型
├── services/
│ └── second_hand_item_service.dart // 二手物品服务
├── utils/
│ └── mock_data.dart // 模拟数据
├── pages/
│ ├── second_hand_home_page.dart // 物品列表页面
│ ├── second_hand_detail_page.dart // 物品详情页面
│ ├── second_hand_publish_page.dart // 发布物品页面
│ └── second_hand_profile_page.dart // 个人中心页面
└── main.dart // 应用入口和路由配置
📚 参考资料
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)