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

Flutter:如何按日期对API数据进行ListView分组展示

实现日期分组的列表展示效果

1. 定义数据模型

先为API返回的数据定义对应的实体类:

class Item {
  final DateTime date;
  final String title;
  final String subtitle;

  Item({required this.date, required this.title, required this.subtitle});
}

2. 对API数据做分组处理

从API拿到数据后,按「本月」和「历史年月」维度完成分组:

// 模拟API返回的原始数据
List<Item> apiData = [
  Item(date: DateTime.now(), title: "今日事项1", subtitle: "详情1"),
  Item(date: DateTime.now().subtract(Duration(days: 10)), title: "上旬事项", subtitle: "详情2"),
  Item(date: DateTime(2022,12,15), title: "2022年12月事项", subtitle: "详情3"),
  Item(date: DateTime(2021,11,20), title: "2021年11月事项", subtitle: "详情4"),
];

// 分组逻辑实现
Map<String, List<Item>> groupItems() {
  Map<String, List<Item>> groups = {};
  DateTime now = DateTime.now();
  DateTime currentMonth = DateTime(now.year, now.month);

  // 筛选本月数据
  List<Item> currentMonthItems = apiData.where((item) {
    DateTime itemMonth = DateTime(item.date.year, item.date.month);
    return itemMonth.isAtSameMomentAs(currentMonth);
  }).toList();
  if (currentMonthItems.isNotEmpty) {
    groups["本月"] = currentMonthItems;
  }

  // 处理历史月份数据
  for (var item in apiData) {
    DateTime itemMonth = DateTime(item.date.year, item.date.month);
    if (itemMonth.isBefore(currentMonth)) {
      String groupKey = "${item.date.year}年${item.date.month}月";
      groups.putIfAbsent(groupKey, () => []);
      groups[groupKey]!.add(item);
    }
  }

  // 对分组排序:本月在前,历史月份按时间倒序排列
  List<String> sortedKeys = groups.keys.toList()
    ..remove("本月")
    ..sort((a, b) {
      DateTime aDate = DateTime.parse(a.replaceAll("年", "-").replaceAll("月", ""));
      DateTime bDate = DateTime.parse(b.replaceAll("年", "-").replaceAll("月", ""));
      return bDate.compareTo(aDate);
    });
  if (groups.containsKey("本月")) {
    sortedKeys.insert(0, "本月");
  }

  // 构建有序分组Map
  Map<String, List<Item>> sortedGroups = {};
  for (var key in sortedKeys) {
    sortedGroups[key] = groups[key]!;
  }

  return sortedGroups;
}

3. 构建分组列表UI

将分组后的数据渲染为「标题+ListTile」的结构:

class GroupedDateList extends StatelessWidget {
  final Map<String, List<Item>> groupedItems;

  const GroupedDateList({super.key, required this.groupedItems});

  @override
  Widget build(BuildContext context) {
    List<Widget> listWidgets = [];
    groupedItems.forEach((key, items) {
      // 添加分组标题
      listWidgets.add(Container(
        padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
        color: Colors.grey[200],
        child: Text(
          key,
          style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
        ),
      ));
      // 添加分组下的ListTile
      for (var item in items) {
        listWidgets.add(ListTile(
          title: Text(item.title),
          subtitle: Text(item.subtitle),
          onTap: () {
            // 点击事件逻辑
          },
        ));
      }
    });

    return ListView(children: listWidgets);
  }
}

4. 页面中使用组件

在页面里调用分组方法并渲染最终UI:

@override
Widget build(BuildContext context) {
  Map<String, List<Item>> groups = groupItems();
  return Scaffold(
    appBar: AppBar(title: Text("日期分组列表")),
    body: GroupedDateList(groupedItems: groups),
  );
}

便捷方案:使用第三方包

如果不想手动写分组逻辑,可借助grouped_list包简化开发:

  1. 在pubspec.yaml添加依赖:
dependencies:
  grouped_list: ^4.2.0
  1. 直接使用组件:
GroupedListView<Item, String>(
  elements: apiData,
  groupBy: (item) {
    DateTime now = DateTime.now();
    DateTime currentMonth = DateTime(now.year, now.month);
    DateTime itemMonth = DateTime(item.date.year, item.date.month);
    return itemMonth.isAtSameMomentAs(currentMonth) ? "本月" : "${item.date.year}年${item.date.month}月";
  },
  groupComparator: (value1, value2) {
    if (value1 == "本月") return -1;
    if (value2 == "本月") return 1;
    DateTime a = DateTime.parse(value1.replaceAll("年", "-").replaceAll("月", ""));
    DateTime b = DateTime.parse(value2.replaceAll("年", "-").replaceAll("月", ""));
    return b.compareTo(a);
  },
  itemComparator: (item1, item2) => item2.date.compareTo(item1.date),
  groupSeparatorBuilder: (String value) => Container(
    padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
    color: Colors.grey[200],
    child: Text(value, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
  ),
  itemBuilder: (context, Item item) => ListTile(
    title: Text(item.title),
    subtitle: Text(item.subtitle),
  ),
)

内容的提问来源于stack exchange,提问作者Konstantinos Rotsidis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 18:15:35