如何创建指定样式的ListViewBuilder实现按日统计收入的App
按日分组展示收入的ListViewBuilder实现方案
1. 数据预处理(分组、排序、计算汇总)
先把原始的时间戳+金额数据按日期分组,计算每日总收入,同时按日期倒序排列(最新日期在前)。假设你的原始数据模型如下:
class IncomeRecord { final int timestamp; // 毫秒级时间戳 final double amount; final String? remark; // 可选收入备注 IncomeRecord({required this.timestamp, required this.amount, this.remark}); }
处理数据的核心代码:
// 将时间戳转换为统一格式的日期字符串 String _getDateKey(int timestamp) { DateTime date = DateTime.fromMillisecondsSinceEpoch(timestamp); return "${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}"; } // 分组并计算每日汇总,返回排序后的结果 Map<String, Map<String, dynamic>> groupAndSortIncome(List<IncomeRecord> records) { Map<String, Map<String, dynamic>> groupedData = {}; for (var record in records) { String dateKey = _getDateKey(record.timestamp); if (!groupedData.containsKey(dateKey)) { groupedData[dateKey] = { 'totalAmount': 0.0, 'dailyRecords': <IncomeRecord>[], }; } groupedData[dateKey]!['totalAmount'] += record.amount; groupedData[dateKey]!['dailyRecords'].add(record); } // 按日期倒序排序分组键 List<String> sortedDates = groupedData.keys.toList()..sort((a, b) => b.compareTo(a)); return Map.fromEntries(sortedDates.map((date) => MapEntry(date, groupedData[date]!))); }
2. 构建分组ListViewBuilder
根据处理后的数据,用ListView.builder实现分组展示,头部显示日期和当日总收入,下方展开(或直接显示)每笔收入明细:
Widget buildIncomeListView(List<IncomeRecord> rawRecords) { final groupedData = groupAndSortIncome(rawRecords); final sortedDateKeys = groupedData.keys.toList(); return ListView.builder( padding: const EdgeInsets.symmetric(vertical: 8), itemCount: sortedDateKeys.length, itemBuilder: (context, index) { String currentDate = sortedDateKeys[index]; var dailyData = groupedData[currentDate]!; double total = dailyData['totalAmount']; List<IncomeRecord> dailyRecords = dailyData['dailyRecords']; // 用ExpansionTile实现可折叠的分组样式 return ExpansionTile( title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( currentDate, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), ), Text( "¥${total.toStringAsFixed(2)}", style: const TextStyle(color: Colors.green, fontSize: 16, fontWeight: FontWeight.bold), ), ], ), children: dailyRecords.map((record) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(record.remark ?? "无备注"), Text("¥${record.amount.toStringAsFixed(2)}", style: const TextStyle(color: Colors.black87)), ], ), ); }).toList(), ); }, ); }
3. 非折叠版样式适配(直接展示所有明细)
如果设计图不需要折叠功能,可改用普通容器嵌套实现:
Widget buildNonCollapsibleIncomeList(List<IncomeRecord> rawRecords) { final groupedData = groupAndSortIncome(rawRecords); final sortedDateKeys = groupedData.keys.toList(); return ListView.builder( padding: const EdgeInsets.symmetric(vertical: 8), itemCount: sortedDateKeys.length, itemBuilder: (context, index) { String currentDate = sortedDateKeys[index]; var dailyData = groupedData[currentDate]!; double total = dailyData['totalAmount']; List<IncomeRecord> dailyRecords = dailyData['dailyRecords']; return Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 日期+汇总行 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(currentDate, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), Text("¥${total.toStringAsFixed(2)}", style: const TextStyle(color: Colors.green, fontSize: 16, fontWeight: FontWeight.bold)), ], ), const SizedBox(height: 8), // 明细列表 Column( children: dailyRecords.map((record) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(record.remark ?? "无备注", style: const TextStyle(fontSize: 14)), Text("¥${record.amount.toStringAsFixed(2)}", style: const TextStyle(fontSize: 14, color: Colors.black54)), ], ), ); }).toList(), ), const Divider(height: 16), ], ), ); }, ); }
关键注意点
- 时间戳转换:统一使用本地时间或UTC时间,避免日期分组错误
- 排序调整:若需按日期正序展示,将排序逻辑改为
a.compareTo(b) - 样式自定义:可根据设计图调整字体、颜色、间距、边框等细节,比如给日期栏添加背景色
内容的提问来源于stack exchange,提问作者Yıldırım Küloğlu
相关产品推荐
相关产品推荐

