You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何创建指定样式的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 03:10:16