1. 项目背景与需求分析
养猫人士常常面临一个实际问题:如何有效记录和管理宠物日常开销?从猫粮、医疗到玩具、美容,各项支出分散且容易遗忘。传统记账应用往往缺乏针对宠物场景的优化设计,这正是"猫咪管家"App要解决的核心痛点。
支出列表作为财务管理模块的核心界面,需要实现以下关键功能:
- 实时统计并突出显示总支出金额
- 按时间倒序展示所有支出记录
- 支持滑动删除误操作或已取消的消费项
- 提供直观的类别图标和颜色区分
- 确保无记录时的空状态友好提示
- 快速跳转至新增记录页面
技术选型上,Flutter框架的跨平台特性完美适配OpenHarmony生态,一套代码可同时覆盖移动端和物联网设备。Dart语言的响应式编程模型与Provider状态管理的组合,能够高效处理动态数据变化。
2. 环境准备与工程配置
2.1 Flutter开发环境搭建
首先确保已安装Flutter SDK 3.0+版本,通过以下命令验证环境:
bash复制flutter doctor
针对OpenHarmony平台需要额外配置:
- 在
pubspec.yaml中添加openharmony兼容性声明:
yaml复制environment:
sdk: ">=2.17.0 <3.0.0"
flutter: ">=3.0.0"
- 安装必要的依赖包:
yaml复制dependencies:
flutter_screenutil: ^5.0.0 # 屏幕适配
provider: ^6.0.0 # 状态管理
intl: ^0.17.0 # 国际化与日期格式化
2.2 项目结构设计
采用分层架构组织代码:
code复制lib/
├── models/
│ └── expense_record.dart # 数据模型
├── providers/
│ └── cat_provider.dart # 状态管理
├── screens/
│ ├── expense_list_screen.dart # 本案例主文件
│ └── add_expense_screen.dart # 添加页面
└── widgets/
└── expense_card.dart # 可复用的卡片组件
3. 核心功能实现详解
3.1 数据模型定义
在expense_record.dart中建立支出记录的数据结构:
dart复制enum ExpenseCategory {
food, // 猫粮零食
medical, // 医疗检查
grooming, // 美容洗澡
toys, // 玩具用品
supplies, // 日常耗材
other // 其他支出
}
class ExpenseRecord {
final String id;
final String title;
final double amount;
final DateTime date;
final ExpenseCategory category;
// 构造方法与toJson/fromJson等序列化逻辑
}
3.2 状态管理实现
cat_provider.dart中使用ChangeNotifier管理数据:
dart复制class CatProvider with ChangeNotifier {
final List<ExpenseRecord> _expenseRecords = [];
List<ExpenseRecord> get expenseRecords => _expenseRecords;
double getTotalExpenses() {
return _expenseRecords.fold(0, (sum, record) => sum + record.amount);
}
void addExpenseRecord(ExpenseRecord record) {
_expenseRecords.insert(0, record); // 新记录置顶
notifyListeners();
}
void deleteExpenseRecord(String id) {
_expenseRecords.removeWhere((record) => record.id == id);
notifyListeners();
}
}
3.3 列表页面构建
主界面采用StatelessWidget结合Consumer实现响应式更新:
dart复制class ExpenseListScreen extends StatelessWidget {
const ExpenseListScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('猫咪支出记录')),
body: Consumer<CatProvider>(
builder: (context, provider, _) {
final records = provider.expenseRecords;
final total = provider.getTotalExpenses();
return Column(
children: [
_buildSummaryCard(total),
_buildRecordsList(records, provider),
],
);
},
),
floatingActionButton: _buildAddButton(context),
);
}
}
4. 关键UI组件实现
4.1 顶部统计卡片
使用渐变背景增强视觉层次:
dart复制Widget _buildSummaryCard(double total) {
return Container(
margin: EdgeInsets.all(16.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFFFFA726), Color(0xFFFB8C00)],
),
),
child: Padding(
padding: EdgeInsets.all(20.w),
child: Row(
children: [
Icon(Icons.pets, size: 36.w, color: Colors.white),
SizedBox(width: 16.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('累计支出',
style: TextStyle(color: Colors.white70, fontSize: 14.w)),
Text('¥${total.toStringAsFixed(2)}',
style: TextStyle(
color: Colors.white,
fontSize: 28.w,
fontWeight: FontWeight.bold,
)),
],
),
],
),
),
);
}
4.2 滑动删除列表项
通过Dismissible组件实现右滑删除:
dart复制Widget _buildRecordItem(BuildContext context, ExpenseRecord record, CatProvider provider) {
return Dismissible(
key: Key(record.id),
direction: DismissDirection.endToStart,
background: Container(
alignment: Alignment.centerRight,
padding: EdgeInsets.only(right: 24.w),
color: Colors.red[400],
child: Icon(Icons.delete_forever, color: Colors.white, size: 28.w),
),
confirmDismiss: (_) => _showDeleteDialog(context, record),
onDismissed: (_) => provider.deleteExpenseRecord(record.id),
child: ExpenseCard(record: record),
);
}
Future<bool> _showDeleteDialog(BuildContext context, ExpenseRecord record) async {
return await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('确认删除'),
content: Text('确定要删除"${record.title}"这条记录吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('删除', style: TextStyle(color: Colors.red)),
),
],
),
) ?? false;
}
5. 性能优化与调试技巧
5.1 列表渲染优化
对于可能很长的支出列表,采用这些优化策略:
- 确保ListView.builder的itemExtent设定固定高度
- 使用const构造函数创建静态组件
- 复杂卡片内容通过RepaintBoundary隔离重绘
dart复制ListView.builder(
itemCount: records.length,
itemExtent: 80.h, // 固定高度提升性能
itemBuilder: (context, index) => _buildRecordItem(context, records[index], provider),
)
5.2 空状态处理
当没有支出记录时显示友好提示:
dart复制Widget _buildEmptyState() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.receipt_long, size: 80.w, color: Colors.grey[300]),
SizedBox(height: 16.h),
Text('还没有任何支出记录',
style: TextStyle(color: Colors.grey, fontSize: 16.w)),
SizedBox(height: 8.h),
Text('点击右下角+号添加第一条记录',
style: TextStyle(color: Colors.grey[500], fontSize: 14.w)),
],
),
);
}
6. 样式与交互增强
6.1 类别视觉区分
通过颜色和图标强化类别识别:
dart复制class CategoryStyle {
static Color getColor(ExpenseCategory category) {
switch (category) {
case ExpenseCategory.food: return Colors.green;
case ExpenseCategory.medical: return Colors.red;
case ExpenseCategory.grooming: return Colors.pink;
case ExpenseCategory.toys: return Colors.purple;
case ExpenseCategory.supplies: return Colors.blue;
default: return Colors.grey;
}
}
static IconData getIcon(ExpenseCategory category) {
switch (category) {
case ExpenseCategory.food: return Icons.restaurant;
case ExpenseCategory.medical: return Icons.medical_services;
case ExpenseCategory.grooming: return Icons.content_cut;
case ExpenseCategory.toys: return Icons.toys;
case ExpenseCategory.supplies: return Icons.shopping_cart;
default: return Icons.more_horiz;
}
}
}
6.2 交互动画优化
为删除操作添加动画效果:
dart复制onDismissed: (_) {
provider.deleteExpenseRecord(record.id);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('已删除"${record.title}"记录'),
action: SnackBarAction(
label: '撤销',
onPressed: () => provider.addExpenseRecord(record),
),
),
);
},
7. 测试与调试要点
7.1 关键测试场景
- 添加多条记录验证列表滚动性能
- 测试滑动删除的边界情况(最后一条记录)
- 验证总金额计算的准确性(含小数)
- 横竖屏切换时的布局适配
- 深色模式下的颜色对比度
7.2 常见问题解决
问题1:滑动删除灵敏度太高
解决方案:调整dismissThresholds参数
dart复制Dismissible(
dismissThresholds: const {
DismissDirection.endToStart: 0.4,
},
// ...
)
问题2:列表项重建导致状态丢失
解决方案:确保key的唯一性和稳定性
dart复制Key(record.id + record.updatedAt.millisecondsSinceEpoch.toString())
8. 项目扩展方向
- 多猫管理:扩展Provider支持多个猫咪档案
- 数据可视化:引入折线图展示支出趋势
- 预算功能:添加各类别预算设置与超支提醒
- 数据导出:支持CSV格式导出到本地
- 云端同步:集成OpenHarmony分布式能力跨设备同步
实际开发中发现,使用Flutter的Isolate处理大量数据计算(如年度统计)能显著提升界面流畅度。对于OpenHarmony设备,需要特别注意平台通道的异步调用方式与Android/iOS的差异。
