Flutter for OpenHarmony 猫咪管家App实战 - 支出列表实现
摘要 本文介绍了一个养猫支出管理页面的实现方案。页面包含总支出统计卡片、支出记录列表、滑动删除功能和快速添加按钮。采用Flutter框架开发,使用Provider进行状态管理,实现以下核心功能: 顶部渐变背景的总支出统计卡片,显示累计金额 支出记录列表展示每笔消费的类别、日期和金额 支持右滑删除单条记录 无数据时显示友好提示 底部悬浮按钮快速跳转至添加页面 界面设计注重用户体验,通过颜色区分不同消

养猫的开销需要有个地方记录和查看。今天我们来实现支出列表页面,展示所有支出记录,支持滑动删除,还有一个醒目的总支出统计。
功能规划
支出列表页面需要实现:
- 顶部显示总支出金额
- 列表展示所有支出记录
- 支持滑动删除记录
- 空状态友好提示
- 快速添加新支出
把这些功能组合起来,就是一个实用的支出管理页面。
依赖引入
首先导入需要的包:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:intl/intl.dart';
import '../../providers/cat_provider.dart';
import '../../models/expense_record.dart';
import 'add_expense_screen.dart';
Provider管理支出数据,数据变化时自动刷新UI。
intl用于日期格式化显示。
无状态组件
支出列表不需要维护额外状态:
class ExpenseListScreen extends StatelessWidget {
const ExpenseListScreen({super.key});
数据由Provider管理,页面本身不需要状态。
StatelessWidget更简洁高效。
页面结构
build方法构建整体布局:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('支出记录')),
body: Consumer<CatProvider>(
builder: (context, provider, child) {
final records = provider.expenseRecords;
final totalExpense = provider.getTotalExpenses();
Consumer监听CatProvider的变化。
获取支出记录列表和总支出金额。
页面内容布局
Column组织顶部统计和列表:
return Column(
children: [
_buildTotalCard(totalExpense),
Expanded(
child: records.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.receipt_long, size: 80.sp, color: Colors.grey[300]),
SizedBox(height: 16.h),
Text('暂无支出记录', style: TextStyle(color: Colors.grey[600])),
],
),
)
Column让统计卡片和列表垂直排列。
Expanded让列表占据剩余空间。
有数据时显示列表:
: ListView.builder(
padding: EdgeInsets.all(16.w),
itemCount: records.length,
itemBuilder: (context, index) => _buildExpenseCard(context, records[index], provider),
),
),
],
);
},
),
ListView.builder按需构建列表项。
传入provider用于删除操作。
悬浮添加按钮
页面底部的FAB:
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(
builder: (_) => const AddExpenseScreen(),
)),
backgroundColor: Colors.orange,
child: const Icon(Icons.add),
),
);
}
点击跳转到添加支出页面。
橙色与App主题一致。
总支出卡片
顶部的统计卡片:
Widget _buildTotalCard(double total) {
return Container(
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.orange, Colors.orange.shade300],
),
),
child: Row(
children: [
Icon(Icons.account_balance_wallet, color: Colors.white, size: 40.sp),
SizedBox(width: 16.w),
渐变背景让卡片更有层次感。
钱包图标直观表达支出含义。
金额显示:
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('总支出', style: TextStyle(color: Colors.white70, fontSize: 14.sp)),
Text(
'¥${total.toStringAsFixed(2)}',
style: TextStyle(color: Colors.white, fontSize: 28.sp, fontWeight: FontWeight.bold),
),
],
),
],
),
);
}
大字号显示金额,一目了然。
保留两位小数更精确。
支出卡片组件
构建单条支出记录:
Widget _buildExpenseCard(BuildContext context, ExpenseRecord record, CatProvider provider) {
return Dismissible(
key: Key(record.id),
direction: DismissDirection.endToStart,
background: Container(
color: Colors.red,
alignment: Alignment.centerRight,
padding: EdgeInsets.only(right: 16.w),
child: const Icon(Icons.delete, color: Colors.white),
),
onDismissed: (_) => provider.deleteExpenseRecord(record.id),
Dismissible实现滑动删除功能。
从右向左滑动显示红色删除背景。
卡片内容:
child: Card(
margin: EdgeInsets.only(bottom: 8.h),
child: ListTile(
leading: CircleAvatar(
backgroundColor: _getCategoryColor(record.category).withOpacity(0.1),
child: Icon(_getCategoryIcon(record.category), color: _getCategoryColor(record.category)),
),
title: Text(record.title),
subtitle: Text('${record.categoryString} · ${DateFormat('MM-dd').format(record.date)}'),
CircleAvatar显示类别对应的图标。
副标题显示类别和日期。
金额显示:
trailing: Text(
'¥${record.amount.toStringAsFixed(2)}',
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold, color: Colors.orange),
),
),
),
);
}
金额用橙色粗体突出显示。
保留两位小数。
类别颜色映射
不同类别用不同颜色:
Color _getCategoryColor(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;
case ExpenseCategory.other: return Colors.grey;
}
}
食品用绿色,医疗用红色。
颜色区分让用户快速识别类别。
类别图标映射
不同类别用不同图标:
IconData _getCategoryIcon(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_bag;
case ExpenseCategory.other: return Icons.more_horiz;
}
}
}
图标让类别更直观。
Material Icons提供了丰富的选择。
Dismissible详解
滑动删除组件的使用:
Dismissible(
key: Key(record.id),
direction: DismissDirection.endToStart,
background: Container(...),
onDismissed: (_) => provider.deleteExpenseRecord(record.id),
child: Card(...),
)
key必须唯一,用于标识每个项。
direction设置滑动方向。
背景设置:
background: Container(
color: Colors.red,
alignment: Alignment.centerRight,
padding: EdgeInsets.only(right: 16.w),
child: const Icon(Icons.delete, color: Colors.white),
)
红色背景表示删除操作。
图标靠右对齐,与滑动方向一致。
LinearGradient渐变
创建渐变背景:
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.orange, Colors.orange.shade300],
),
)
LinearGradient创建线性渐变。
从深橙色到浅橙色的过渡。
金额格式化
保留两位小数:
total.toStringAsFixed(2)
输出类似"123.45"的格式。
金额显示更规范。
Consumer使用
监听Provider数据变化:
Consumer<CatProvider>(
builder: (context, provider, child) {
final records = provider.expenseRecords;
final totalExpense = provider.getTotalExpenses();
// 使用数据构建UI
},
)
builder在数据变化时重新执行。
删除记录后列表自动刷新。
空状态设计
没有记录时的显示:
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.receipt_long, size: 80.sp, color: Colors.grey[300]),
SizedBox(height: 16.h),
Text('暂无支出记录', style: TextStyle(color: Colors.grey[600])),
],
),
)
账单图标暗示支出主题。
灰色调表示空状态。
Column和Expanded
布局组合使用:
Column(
children: [
_buildTotalCard(totalExpense), // 固定高度
Expanded(
child: ListView(...), // 占据剩余空间
),
],
)
统计卡片高度固定。
Expanded让列表填充剩余空间。
ListTile布局
列表项的标准结构:
ListTile(
leading: CircleAvatar(...),
title: Text(record.title),
subtitle: Text(...),
trailing: Text(...),
)
四个位置放置不同内容。
自动处理间距和对齐。
日期格式化
简短的日期格式:
DateFormat('MM-dd').format(record.date)
只显示月日,节省空间。
年份通常是当年,不需要显示。
小结
支出列表页面涉及的知识点:
- Dismissible滑动删除
- LinearGradient渐变背景
- Consumer监听数据变化
- 颜色和图标映射
这些技巧在其他列表页面也能复用。
欢迎加入OpenHarmony跨平台开发社区,一起交流Flutter开发经验:
https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)