Listview 按日期分组 Dart

问题描述 投票:0回答:6

我一直在尝试获取按日期排序的一些消息,但没有成功。我尝试过不同的软件包,例如 grouped_liststicky_headers

使用 sticky_headers 我设法在每个帖子上方都有一个标题,但这不是我想要的。我想按天对邮件进行排序。就像下图一样:

下面我有一个数据集,其中包含我从 API 获取的数据。我的目的是从 API 获取 40 条消息。这 40 条消息按时间排序,只有消息必须在应用程序中按天分组,如上图所示。

编辑: 在 flutter 上使用了 sticky_header 包之后。我现在已经成功获取标题了。只是没有设法在正确的标题下获取消息。

数据集:

[
  {
    "time": "2020-06-16T10:31:12.000Z",
    "message": "P2 BGM-01 HV buiten materieel (Gas lekkage) Franckstraat Arnhem 073631"
  },
  {
    "time": "2020-06-16T10:29:35.000Z",
    "message": "A1 Brahmslaan 3862TD Nijkerk 73278"
  },
  {
    "time": "2020-06-16T10:29:35.000Z",
    "message": "A2 NS Station Rheden Dr. Langemijerweg 6991EV Rheden 73286"
  },
  {
    "time": "2020-06-15T09:41:18.000Z",
    "message": "A2 VWS Utrechtseweg 6871DR Renkum 74636"
  },
  {
    "time": "2020-06-14T09:40:58.000Z",
    "message": "B2 5623EJ : Michelangelolaan Eindhoven Obj: ziekenhuizen 8610 Ca CATH route 522 PAAZ Rit: 66570"
  }
]
flutter listview dart
6个回答
26
投票

首先,分解代码以从数据集中获取按日期映射的分组。然后使用这张图来构建 UI。

使用 collection 包来使用 groupBy 函数。

import "package:collection/collection.dart";

void main() {
  final dataSet = [
    {
      "time": "2020-06-16T10:31:12.000Z",
      "message": "Message1",
    },
    {
      "time": "2020-06-16T10:29:35.000Z",
      "message": "Message2",
    },
    {
      "time": "2020-06-15T09:41:18.000Z",
      "message": "Message3",
    },
  ];

  var groupByDate = groupBy(dataSet, (obj) => obj['time'].substring(0, 10));
  groupByDate.forEach((date, list) {
    // Header
    print('${date}:');

    // Group
    list.forEach((listItem) {
      // List item
      print('${listItem["time"]}, ${listItem["message"]}');
    });
    // day section divider
    print('\n');
  });
}

输出:

2020-06-16:
2020-06-16T10:31:12.000Z, Message1
2020-06-16T10:29:35.000Z, Message2


2020-06-15:
2020-06-15T09:41:18.000Z, Message3

我希望这段代码能够帮助您开始构建您的 UI。 您可以使用 ListView 小部件来显示每个组中的列表项。

通过 DateTimeDateFormat 类来处理日期、时间值。


23
投票

您应该能够通过一些自定义的约会逻辑来实现这一点。如果不在这些列表项之间添加文本标题,请检查两个连续日期是否相同。

以下是上述方法的实现。

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

void main() {
  final dateString = '2020-06-16T10:31:12.000Z';
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
 
  List<Map> list = [
    {
      "time": "2020-06-16T10:31:12.000Z",
      "message":
          "P2 BGM-01 HV buiten materieel (Gas lekkage) Franckstraat Arnhem 073631"
    },
    {
      "time": "2020-06-16T10:29:35.000Z",
      "message": "A1 Brahmslaan 3862TD Nijkerk 73278"
    },
    {
      "time": "2020-06-16T10:29:35.000Z",
      "message": "A2 NS Station Rheden Dr. Langemijerweg 6991EV Rheden 73286"
    },
    {
      "time": "2020-06-15T09:41:18.000Z",
      "message": "A2 VWS Utrechtseweg 6871DR Renkum 74636"
    },
    {
      "time": "2020-06-14T09:40:58.000Z",
      "message":
          "B2 5623EJ : Michelangelolaan Eindhoven Obj: ziekenhuizen 8610 Ca CATH route 522 PAAZ Rit: 66570"
    }
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: ListView.builder(
            itemCount: list.length,
            itemBuilder: (_, index) {
              bool isSameDate = true;
              final String dateString = list[index]['time'];
              final DateTime date = DateTime.parse(dateString);
              final item = list[index];
              if (index == 0) {
                isSameDate = false;
              } else {
                final String prevDateString = list[index - 1]['time'];
                final DateTime prevDate = DateTime.parse(prevDateString);
                isSameDate = date.isSameDate(prevDate);
              }
              if (index == 0 || !(isSameDate)) {
                return Column(children: [
                  Text(date.formatDate()),
                  ListTile(title: Text('item $index'))
                ]);
              } else {
                return ListTile(title: Text('item $index'));
              }
            }),
      ),
    );
  }
}

const String dateFormatter = 'MMMM dd, y';

extension DateHelper on DateTime {
  
   String formatDate() {
     final formatter = DateFormat(dateFormatter);
      return formatter.format(this);
  }
  bool isSameDate(DateTime other) {
    return this.year == other.year &&
        this.month == other.month &&
        this.day == other.day;
  }

  int getDifferenceInDaysWithNow() {
    final now = DateTime.now();
    return now.difference(this).inDays;
  }
}

输出


9
投票

有一些名为 sticky_grouped_listgrouped_list 的 dart.pub 软件包(在您的问题中提到)。他们通过公共属性围绕组元素做了很好的工作。我已经尝试过 sticky_grouped_list 而且我没有在您的特定场景中使用它,一旦列表可以按动态类型对元素进行分组,它似乎适用于它。

除了外观之外,您的代码与该示例之间的主要功能差异是它们使用了 DateTime 元素而不是 String 作为对列表进行分组的更好方式。粘性组列表视图很有趣,因为当向下滚动时,当前日期会粘在屏幕顶部。

instead of using a ListView create a GroupedListView Widget:

  GroupedListView<dynamic, String>(
    elements: _elements,
    groupBy: (element) => element['group'],
    groupSeparatorBuilder: (String groupByValue) => Text(groupByValue),
    itemBuilder: (context, dynamic element) => Text(element['name']),
    order: GroupedListOrder.ASC,
  ),

使用 StickyGroupedListView,您还可以按自定义对象进行分组:

StickyGroupedListView<CustomObject, DateTime>(
  elements: pedidos,
  order: StickyGroupedListOrder.ASC,
  groupBy: (CustomObject element) => DateTime(
      element.dateGroup.year,
      element.dateGroup.month,
      element.dateGroup.day),
  groupSeparatorBuilder: (CustomObject element) =>
      renderDataCabecalho(element),
  itemBuilder: (context, element) {
    return _buildPedidoItemView(element, context);
  },
),

如果它不适用于您的需求,您可能可以了解他们对元素进行分组的方法。


2
投票

如果API支持排序,按照正确的顺序获取结果将是最简单、性能最高的解决方案。


1
投票
I think you should filter data in the model to create groups follow the date.
class ListDate {
  int id;
  String name;  
  String date;
  List<DateItem> results;
}
class DateItem {
  String id;
  String date;
  String name;
}
After parse JSON to model, you can run listView by ListDate().results

0
投票

您可以按日期对对象进行分组(例如,考虑到您的示例,是一天的开始),然后显示您想要的任何内容(没有库)。

示例:

1 - 我们有一些课程,其日期分组依据:

class WalletAccountRecord {
  String id;
  String name;
  DateTime date;
....

2 - 我们有包含此类项目的列表(在我们的例子中来自 api,但任何构建方式都可以用于方法理解)

List<WalletAccountRecord> records = response.records;

3 - 然后我们按日期(按一天的开始)对此列表进行分组

Map<Jiffy, List<WalletAccountRecord>> recordsByDays = {};
for (var record in records) {
  Jiffy date = Jiffy.parseFromDateTime(record.date).startOf(Unit.day);
  recordsByDays.update(date, (List<WalletAccountRecord> value) => [record, ...value], ifAbsent: () => [record]);
}
_logger.d('Records by Dates: ${recordsByDays.length} days -> ${recordsByDays.keys}');

4 - 现在,考虑到所有数据都分组到 Map 中,我们可以按照想要的方式显示它。我们的 UI 的完整示例:

 class WalletRecords extends StatelessWidget {
  final AnalyticsService _analyticsService = Get.find();
  final WalletRecordsStateController _walletRecordsStateController = Get.find();
  final Logger _logger = LoggerHelper.logger();

  @override
  Widget build(BuildContext context) {
    return Obx(() => _recordsItems());
  }

  Widget _recordsItems() {
    RecordsSearchResultRx recordsRx = _walletRecordsStateController.recordsGet();
    //TODO Revision flow. if no wallet (when??) then just show 'no records yet'
    //TODO no wallet/loading handle

    List<WalletAccountRecord> records = recordsRx.records;

    Map<Jiffy, List<WalletAccountRecord>> recordsByDays = {};
    for (var record in records) {
      Jiffy date = Jiffy.parseFromDateTime(record.date).startOf(Unit.day);
      recordsByDays.update(date, (List<WalletAccountRecord> value) => [record, ...value], ifAbsent: () => [record]);
    }
    _logger.d('Records by Dates: ${recordsByDays.length} days -> ${recordsByDays.keys}');

    return _recordsByDates(recordsByDays);
  }

  Widget _recordsByDates(Map<Jiffy, List<WalletAccountRecord>> recordsByDays) {
    return ListView.builder(
      itemCount: recordsByDays.length,
      shrinkWrap: true,
      itemBuilder: (context, int index) {
        var date = recordsByDays.keys.elementAt(index);
        return _recordsByDate(date, recordsByDays[date] ?? []);
      },
    );
  }

  Widget _recordsByDate(Jiffy date, List<WalletAccountRecord> recordsByDay) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 3),
      child: Container(
        decoration: BoxDecoration(
            color: UiConstants.colorPrimaryDark.withOpacity(0.3),
            border: Border.all(color: Colors.grey),
            borderRadius: const BorderRadius.all(Radius.circular(15))),
        child: Column(children: [
          Padding(
            padding: const EdgeInsets.only(left: 20),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Expanded(
                    child: Text(date.format(pattern: 'd.MM.yy'),
                        style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600))),
                Wrap(children: [
                  IconButton(icon: const Icon(LineIcons.plus), onPressed: () => {}),
                  IconButton(icon: const Icon(LineIcons.pen), onPressed: () => {}),
                ]),
              ],
            ),
          ),
          const Divider(height: 5, indent: 10, endIndent: 10),
          _recordItems(recordsByDay),
        ]),
      ),
    );
  }

  Widget _recordItems(List<WalletAccountRecord> records) {
    return ListView.separated(
      itemCount: records.length,
      shrinkWrap: true,
      physics: const NeverScrollableScrollPhysics(),
      separatorBuilder: (BuildContext context, int index) => const Divider(height: 1, indent: 10, endIndent: 10),
      itemBuilder: (context, index) {
        return _recordOverview(records[index]);
      },
    );
  }

  Widget _recordOverview(WalletAccountRecord record) {
    return ListTile(
      dense: true,
      horizontalTitleGap: 0,
      title: Text(record.category!, style: const TextStyle(fontWeight: FontWeight.bold)),
      subtitle: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(record.account.accountName),
          Text(record.operationType.name),
          Text(record.note ?? ''),
        ],
      ),
      contentPadding: const EdgeInsets.only(left: 3),
      trailing: Text('${record.amount.amount} ${record.amount.currencyCode}'),
      //todo dynamic based on account color per account
      leading: Container(color: Colors.blue, width: 5, padding: EdgeInsets.zero, margin: EdgeInsets.zero),
    );
  }
}

© www.soinside.com 2019 - 2024. All rights reserved.