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

如何为Flutter交易页面的ListView添加月份日期头部(如[2024年3月])

实现Flutter交易页面按月份分组显示

步骤1:对交易列表按年月分组并排序

在ValueListenableBuilder的builder函数内,先对交易列表按日期倒序排序,再按年份-月份维度分组:

// 按日期倒序排列,确保最新交易优先展示
final sortedTransactions = List.from(newList)
  ..sort((a, b) => b.date.compareTo(a.date));

// 按"yyyy年MM月"格式分组,将同月份交易归为一组
final groupedTransactions = <String, List<TransactionModel>>{};
for (final transaction in sortedTransactions) {
  final monthYear = DateFormat('yyyy年MM月').format(transaction.date);
  groupedTransactions.putIfAbsent(monthYear, () => []);
  groupedTransactions[monthYear]!.add(transaction);
}

// 构建混合显示列表:先添加月份头部,再追加对应交易项
final List<dynamic> displayList = [];
groupedTransactions.forEach((month, transactions) {
  displayList.add(month);
  displayList.addAll(transactions);
});

步骤2:修改ListView适配分组结构

调整ListView.separated的配置,根据混合列表的元素类型渲染头部或交易卡片:

return ListView.separated(
  padding: EdgeInsets.all(10),
  itemBuilder: (ctx, index) {
    final item = displayList[index];
    // 渲染月份头部
    if (item is String) {
      return Container(
        padding: EdgeInsets.symmetric(vertical: 8, horizontal: 16),
        color: Colors.grey[100],
        child: Text(
          item,
          style: TextStyle(
            fontSize: 16,
            fontWeight: FontWeight.bold,
            color: Colors.grey[700],
          ),
        ),
      );
    } 
    // 渲染交易卡片(复用原有Slidable逻辑)
    else if (item is TransactionModel) {
      final _list = item;
      return Slidable(
        key: Key(_list.id!),
        startActionPane: ActionPane(
          motion: DrawerMotion(),
          children: [
            SlidableAction(
              onPressed: (ctx) {
                TransactionDB.interface.deleteTransaction(_list.id!);
              },
              icon: Icons.delete,
              backgroundColor: Colors.red,
              label: '删除',
            ),
          ],
        ),
        child: Card(
          child: ListTile(
            leading: CircleAvatar(
              backgroundColor: _list.type == CategoryType.income
                  ? Colors.green
                  : Colors.red,
              child: Text(
                parsedDate(_list.date),
                textAlign: TextAlign.center,
              ),
              radius: 30,
            ),
            title: Text('Rs ${_list.amount}'),
            subtitle: Text(_list.categoryModel.name),
          ),
        ),
      );
    }
    return Container();
  },
  separatorBuilder: (ctx, index) => SizedBox(height: 10),
  itemCount: displayList.length,
);

完整修改后的代码

class ScreenTransaction extends StatelessWidget {
  const ScreenTransaction({super.key});

  @override
  Widget build(BuildContext context) {
    TransactionDB.interface.refreshTransaction();
    CategoryDB.instance.refreshUI();
    return ValueListenableBuilder(
      valueListenable: TransactionDB.interface.transactionListNotifier,
      builder: (BuildContext ctx, List<TransactionModel> newList, _) {
        // 1. 按日期倒序排序
        final sortedTransactions = List.from(newList)
          ..sort((a, b) => b.date.compareTo(a.date));

        // 2. 按年月分组
        final groupedTransactions = <String, List<TransactionModel>>{};
        for (final transaction in sortedTransactions) {
          final monthYear = DateFormat('yyyy年MM月').format(transaction.date);
          groupedTransactions.putIfAbsent(monthYear, () => []);
          groupedTransactions[monthYear]!.add(transaction);
        }

        // 3. 构建混合显示列表
        final List<dynamic> displayList = [];
        groupedTransactions.forEach((month, transactions) {
          displayList.add(month);
          displayList.addAll(transactions);
        });

        return ListView.separated(
          padding: EdgeInsets.all(10),
          itemBuilder: (ctx, index) {
            final item = displayList[index];
            if (item is String) {
              return Container(
                padding: EdgeInsets.symmetric(vertical: 8, horizontal: 16),
                color: Colors.grey[100],
                child: Text(
                  item,
                  style: TextStyle(
                    fontSize: 16,
                    fontWeight: FontWeight.bold,
                    color: Colors.grey[700],
                  ),
                ),
              );
            } else if (item is TransactionModel) {
              final _list = item;
              return Slidable(
                key: Key(_list.id!),
                startActionPane: ActionPane(
                  motion: DrawerMotion(),
                  children: [
                    SlidableAction(
                      onPressed: (ctx) {
                        TransactionDB.interface.deleteTransaction(_list.id!);
                      },
                      icon: Icons.delete,
                      backgroundColor: Colors.red,
                      label: '删除',
                    ),
                  ],
                ),
                child: Card(
                  child: ListTile(
                    leading: CircleAvatar(
                      backgroundColor: _list.type == CategoryType.income
                          ? Colors.green
                          : Colors.red,
                      child: Text(
                        parsedDate(_list.date),
                        textAlign: TextAlign.center,
                      ),
                      radius: 30,
                    ),
                    title: Text('Rs ${_list.amount}'),
                    subtitle: Text(_list.categoryModel.name),
                  ),
                ),
              );
            }
            return Container();
          },
          separatorBuilder: (ctx, index) => SizedBox(height: 10),
          itemCount: displayList.length,
        );
      },
    );
  }

  String parsedDate(DateTime date) {
    final _date = DateFormat.MMMd().format(date);
    final _splittedDate = _date.split(' ');
    return '${_splittedDate.last}\n ${_splittedDate.first}';
  }
}

关键说明

  • 分组逻辑:通过DateFormat('yyyy年MM月')生成统一的月份标识,作为分组的唯一key
  • 混合列表:将月份头部和对应交易项依次加入列表,保证ListView按「头部+交易」的顺序渲染
  • 排序处理:先对交易列表倒序排列,确保最新的月份展示在页面顶部

内容的提问来源于stack exchange,提问作者Midhun S

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 17:55:07