Flutter for OpenHarmony 手办收藏 App 实战:购买记录实现
本文介绍了在Flutter for OpenHarmony项目中实现手办购买记录页面的方法。该功能帮助收藏者系统记录购买信息,包括日期、店铺、价格等关键数据,便于消费统计和资产管理。文章详细讲解了页面结构设计、数据过滤与空状态处理、列表渲染优化等核心实现步骤,重点介绍了使用ListView.builder提升性能、Card组件美化布局、日期格式化等技术要点。通过合理的UI设计和数据处理,该页面能清

对于手办收藏者来说,记录每一次购买的详细信息是非常重要的。购买日期、购买店铺、实际支付价格等数据不仅是收藏档案的一部分,更是日后评估投资回报、分析消费习惯的重要依据。购买记录功能帮助用户完整记录每个手办的购买历程,让收藏管理更加专业和系统化。本文将从列表展示、日期格式化、空状态处理等角度,详细讲解如何在 Flutter for OpenHarmony 项目中实现一个实用的购买记录页面。
一、购买记录的核心价值
在手办收藏场景中,购买记录不仅是简单的流水账,更承载着以下核心功能:
- 消费统计:了解自己在手办上的总投入,按月、按年统计消费趋势
- 店铺分析:记录在哪些店铺购买,评估店铺的可靠性和价格优势
- 价格对比:对比购买价和当前市场价,计算增值或贬值幅度
- 税务凭证:完整的购买记录可作为个人资产管理的一部分
这些需求决定了购买记录页面需要信息完整、时间清晰、查询便捷。
二、页面结构设计
1. 依赖导入
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:intl/intl.dart';
import '../../providers/figure_provider.dart';
关键依赖说明:
intl:国际化库,用于格式化日期显示,支持多语言环境provider:状态管理,从 FigureProvider 获取手办数据flutter_screenutil:屏幕适配,确保在不同 OpenHarmony 设备上显示一致
2. 无状态组件定义
class PurchaseHistoryPage extends StatelessWidget {
const PurchaseHistoryPage({super.key});
采用 StatelessWidget 是因为购买记录数据由 Provider 提供,页面本身只负责展示。这种设计保持组件纯粹,符合 Flutter 的最佳实践。
三、数据过滤与空状态处理
1. Scaffold 基础框架
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('购买记录')),
顶部导航栏显示"购买记录"标题,让用户明确当前所在页面。在完整版本中,可以在右侧添加"筛选"按钮,支持按时间范围、店铺、价格区间过滤记录。
2. Consumer 数据监听
body: Consumer<FigureProvider>(
builder: (context, provider, child) {
final purchases = provider.ownedFigures.where((f) => f.purchaseDate != null).toList();
数据过滤逻辑:
provider.ownedFigures获取所有已拥有的手办列表.where((f) => f.purchaseDate != null)过滤出有购买日期的手办.toList()将过滤结果转换为列表
这样做的原因是:
- 不是所有手办都有购买记录(如赠送、交换获得的手办)
- 只显示有明确购买日期的手办,确保数据完整性
- 避免显示空日期导致页面混乱
3. 空状态处理
if (purchases.isEmpty) {
return const Center(child: Text('暂无购买记录'));
}
空状态设计:
- 当没有购买记录时,显示"暂无购买记录"提示
- 使用
Center组件让文本居中显示,符合用户视觉习惯 - 避免显示空白页面导致用户困惑
在完整版本中,可以添加更友好的空状态设计:
if (purchases.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.receipt_long, size: 80, color: Colors.grey),
SizedBox(height: 16.h),
Text('暂无购买记录', style: TextStyle(fontSize: 18.sp, color: Colors.grey)),
SizedBox(height: 8.h),
Text('添加手办时记录购买信息', style: TextStyle(fontSize: 14.sp, color: Colors.grey)),
],
),
);
}
这样用户能更清楚地了解如何添加购买记录。
四、列表渲染核心逻辑
1. ListView.builder 高效渲染
return ListView.builder(
padding: EdgeInsets.all(16.w),
itemCount: purchases.length,
为什么选择 builder 模式?
- 性能优化:只渲染可见区域的列表项,当购买记录达到几十条甚至上百条时,性能优势明显
- 动态扩展:购买记录数量不固定,builder 能灵活适应数据变化
- 内存友好:避免一次性创建大量 Widget 导致内存峰值
padding 设置:
EdgeInsets.all(16.w)为整个列表添加 16 像素的内边距- 这样第一个和最后一个列表项不会紧贴屏幕边缘,视觉上更舒适
五、购买记录卡片实现
1. Card 卡片布局
itemBuilder: (context, index) {
final figure = purchases[index];
return Card(
margin: EdgeInsets.only(bottom: 12.h),
Card 组件的优势:
- 自带阴影和圆角,让每条购买记录独立清晰
margin: EdgeInsets.only(bottom: 12.h)只设置底部间距,避免顶部和底部都有间距导致浪费空间- 符合 Material Design 规范,用户体验一致
2. Padding 内边距
child: Padding(
padding: EdgeInsets.all(12.w),
在 Card 内部再添加一层 Padding,确保内容不会紧贴卡片边缘。12 像素的内边距经过实测,既能保证内容清晰,又不会显得过于空旷。
3. Row 横向布局
child: Row(
children: [
const Icon(Icons.receipt_long, size: 40),
左侧图标设计:
- 使用
Icons.receipt_long收据图标,符合购买记录的主题 size: 40设置图标大小为 40 像素,与右侧文本高度匹配- 可以根据购买渠道使用不同图标:
- 线上购买:
Icons.shopping_cart - 线下购买:
Icons.store - 预订:
Icons.event_available
- 线上购买:
SizedBox(width: 12.w),
在图标和文本之间添加 12 像素的间距,避免内容过于紧凑。
4. Expanded 自适应宽度
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
Expanded 的作用:
- 让 Column 占据 Row 中除图标外的所有剩余空间
crossAxisAlignment: CrossAxisAlignment.start让文本左对齐,符合阅读习惯- 这样即使手办名称很长,也不会溢出屏幕
5. 手办名称显示
children: [
Text(figure.name, style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16.sp)),
文本样式设计:
fontWeight: FontWeight.bold加粗显示,突出手办名称fontSize: 16.sp使用响应式字体大小,确保在不同设备上可读性一致- 实际项目中显示真实名称,如"《Re:Zero 雷姆》1/7 比例手办"
SizedBox(height: 4.h),
在每行文本之间添加 4 像素的垂直间距,避免文本过于紧密。
6. 购买日期显示
Text('购买日期: ${figure.purchaseDate != null ? DateFormat('yyyy-MM-dd').format(figure.purchaseDate!) : '未知'}'),
日期格式化逻辑:
- 使用三元运算符判断
purchaseDate是否为空 - 如果不为空,使用
DateFormat('yyyy-MM-dd').format()格式化日期 - 格式为"年-月-日",如"2024-01-26",符合中文用户习惯
- 如果为空,显示"未知",避免显示 null 导致错误
DateFormat 的优势:
- 支持多种日期格式,如
yyyy年MM月dd日、MM/dd/yyyy等 - 支持国际化,可以根据系统语言自动调整格式
- 处理时区问题,确保日期显示准确
在实际项目中,可以根据需求调整格式:
// 显示相对时间
final daysSincePurchase = DateTime.now().difference(figure.purchaseDate!).inDays;
Text('购买日期: ${daysSincePurchase}天前'),
// 显示中文格式
Text('购买日期: ${DateFormat('yyyy年MM月dd日', 'zh_CN').format(figure.purchaseDate!)}'),
7. 购买店铺显示
Text('购买店铺: ${figure.purchaseStore ?? '未记录'}'),
店铺信息处理:
- 使用
??空值合并运算符,如果purchaseStore为空,显示"未记录" - 这种处理方式比三元运算符更简洁
- 店铺信息对于评估购买渠道的可靠性很重要
在实际项目中,可以将店铺名称设置为可点击链接:
GestureDetector(
onTap: () {
// 跳转到店铺详情页或打开店铺网址
Get.to(() => StoreDetailPage(storeName: figure.purchaseStore));
},
child: Text(
'购买店铺: ${figure.purchaseStore ?? '未记录'}',
style: TextStyle(
color: Colors.blue,
decoration: TextDecoration.underline,
),
),
)
8. 价格显示
Text('价格: ¥${figure.price}', style: const TextStyle(color: Colors.red)),
价格样式设计:
- 使用红色显示价格,符合中文用户对价格的视觉认知
- 前缀
¥明确标识货币单位为人民币 - 使用
const优化性能,因为TextStyle(color: Colors.red)是不变的
在实际项目中,可以对比购买价和当前市场价:
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('购买价: ¥${figure.purchasePrice}', style: TextStyle(color: Colors.grey)),
Text('当前价: ¥${figure.currentPrice}', style: TextStyle(color: Colors.red)),
Text(
'${figure.currentPrice > figure.purchasePrice ? '增值' : '贬值'} ¥${(figure.currentPrice - figure.purchasePrice).abs()}',
style: TextStyle(
color: figure.currentPrice > figure.purchasePrice ? Colors.green : Colors.red,
),
),
],
)
这样用户能直观看到投资回报情况。
六、数据模型扩展
为了支持购买记录功能,需要在 Figure 模型中添加相关字段:
class Figure {
final String id;
final String name;
final double price;
final DateTime? purchaseDate;
final String? purchaseStore;
final double? purchasePrice; // 实际购买价格
final String? purchaseChannel; // 购买渠道:线上、线下、预订
final String? purchaseNotes; // 购买备注
final String? invoiceNumber; // 发票号
Figure({
required this.id,
required this.name,
required this.price,
this.purchaseDate,
this.purchaseStore,
this.purchasePrice,
this.purchaseChannel,
this.purchaseNotes,
this.invoiceNumber,
});
}
这样可以记录更完整的购买信息。
七、高级功能扩展
1. 时间筛选
添加按钮切换显示不同时间段的购买记录:
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FilterChip(
label: Text('本月'),
selected: _period == Period.month,
onSelected: (selected) => setState(() => _period = Period.month),
),
FilterChip(
label: Text('本年'),
selected: _period == Period.year,
onSelected: (selected) => setState(() => _period = Period.year),
),
FilterChip(
label: Text('全部'),
selected: _period == Period.all,
onSelected: (selected) => setState(() => _period = Period.all),
),
],
)
2. 消费统计
添加统计卡片显示总消费:
Card(
child: Padding(
padding: EdgeInsets.all(16.w),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Column(
children: [
Text('总消费', style: TextStyle(fontSize: 14.sp, color: Colors.grey)),
Text('¥${provider.totalPurchaseAmount}', style: TextStyle(fontSize: 20.sp, fontWeight: FontWeight.bold)),
],
),
Column(
children: [
Text('平均单价', style: TextStyle(fontSize: 14.sp, color: Colors.grey)),
Text('¥${provider.averagePurchasePrice}', style: TextStyle(fontSize: 20.sp, fontWeight: FontWeight.bold)),
],
),
],
),
),
)
3. 导出功能
支持导出购买记录为 CSV 或 Excel:
Future<void> exportPurchaseHistory() async {
final csv = const ListToCsvConverter().convert([
['手办名称', '购买日期', '购买店铺', '价格'],
...purchases.map((f) => [
f.name,
DateFormat('yyyy-MM-dd').format(f.purchaseDate!),
f.purchaseStore ?? '未记录',
f.price.toString(),
]),
]);
final file = File('${await getApplicationDocumentsDirectory()}/purchase_history.csv');
await file.writeAsString(csv);
await Share.shareFiles([file.path], text: '购买记录');
}
八、OpenHarmony 适配要点
1. 日期格式化国际化
确保日期格式适配不同语言环境:
import 'package:intl/date_symbol_data_local.dart';
void main() async {
await initializeDateFormatting('zh_CN', null);
runApp(MyApp());
}
// 使用时指定语言
DateFormat('yyyy年MM月dd日', 'zh_CN').format(date)
2. 数据持久化
使用 sqflite 存储购买记录:
Future<void> savePurchaseRecord(Figure figure) async {
final db = await database;
await db.insert('purchase_history', {
'figure_id': figure.id,
'purchase_date': figure.purchaseDate?.toIso8601String(),
'purchase_store': figure.purchaseStore,
'price': figure.price,
});
}
3. 性能优化
当购买记录数量很大时,使用分页加载:
ScrollController _scrollController = ScrollController();
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
provider.loadMorePurchases();
}
});
}
九、实战经验总结
购买记录页面的实现要点:
- 数据完整性:记录购买日期、店铺、价格等关键信息
- 空状态处理:友好提示用户如何添加购买记录
- 日期格式化:使用 intl 库确保日期显示准确
- 扩展性:预留筛选、统计、导出等高级功能的接口
通过本文的实战讲解,你已经掌握了在 Flutter for OpenHarmony 项目中构建购买记录功能的核心技巧。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐
所有评论(0)