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

Flutter仿Instagram通知页:添加24小时分组标题功能求助

解决方案

要实现按24小时阈值区分“Today”和“Older Notifications”的分组标题,核心是通过时间差判断分组,而非单纯的日期匹配,同时要处理分组切换时的标题显示逻辑(原代码仅在第一个item显示标题的逻辑无法覆盖所有分组场景)。

关键修改点

  1. 新增方法判断通知是否在24小时内
  2. 在ListView.builder中对比当前与前一个通知的分组,仅当分组变化时显示对应标题
  3. 替换原有日期判断逻辑为24小时时间差判断

修改后的完整代码

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:masalabox_flutter/app/modules/user_profile/controller/ctr_profile.dart';
import 'package:masalabox_flutter/app/themes_styles/colors.dart';
import 'package:masalabox_flutter/app/themes_styles/text_styles.dart';
import 'package:masalabox_flutter/app/utilities/utils_class.dart';

class UINotification extends GetView<CtrProfile> {
  const UINotification({super.key});

  // 判断通知是否在24小时内
  bool isWithin24Hours(DateTime notificationDate) {
    final now = DateTime.now();
    return now.difference(notificationDate).inHours <= 24;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: AppColors.secondaryBackgroundColor,
      appBar: AppBar(
        elevation: 2,
        backgroundColor: Colors.transparent,
        title: Text("Notifications", style: Styles.clickableText),
        leading: GestureDetector(
          onTap: () => Get.back(),
          child: const Icon(
            Icons.arrow_back,
            color: Colors.white,
          ),
        ),
        centerTitle: false,
      ),
      body: Obx(
        () => controller.isLoading.value
            ? Center(
                child: Image.asset(
                  'assets/gifs/loader.gif',
                  height: 100,
                  width: 100,
                ),
              )
            : controller.notification.isNotEmpty
                ? Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const SizedBox(height: 10),
                      Expanded(
                        child: Align(
                          alignment: Alignment.topCenter,
                          child: ListView.builder(
                            shrinkWrap: true,
                            reverse: true, // 保持最新通知在顶部的倒序逻辑
                            itemCount: controller.notification.length,
                            itemBuilder: (context, index) {
                              final notification = controller.notification[index];
                              DateTime notificationDate = DateTime.fromMillisecondsSinceEpoch(
                                  notification.ts!.toInt() * 1000);
                              bool isTodayGroup = isWithin24Hours(notificationDate);

                              // 判断是否需要显示分组标题:当前是分组第一个,或与前一个分组不同
                              bool showHeader = false;
                              if (index == 0) {
                                showHeader = true;
                              } else {
                                final prevNotification = controller.notification[index-1];
                                DateTime prevDate = DateTime.fromMillisecondsSinceEpoch(
                                    prevNotification.ts!.toInt() * 1000);
                                bool prevIsTodayGroup = isWithin24Hours(prevDate);
                                if (isTodayGroup != prevIsTodayGroup) {
                                  showHeader = true;
                                }
                              }

                              // 组装返回内容:需要显示标题则包含标题,否则仅显示通知项
                              List<Widget> children = [];
                              if (showHeader) {
                                children.add(
                                  Padding(
                                    padding: const EdgeInsets.only(top: 17, left: 20),
                                    child: Text(
                                      isTodayGroup ? 'Today' : 'Older Notifications',
                                      style: Styles.lineTextMedium.copyWith(fontWeight: FontWeight.w600),
                                    ),
                                  ),
                                );
                                children.add(const SizedBox(height: 10));
                              }
                              children.add(
                                notificationItem(
                                  index,
                                  controller.profileImageUrl.value,
                                  (notification.p?.type ?? "") == "USER"
                                      ? (notification.p?.param ?? "")
                                      : " ",
                                ),
                              );

                              return Column(
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: children,
                              );
                            },
                          ),
                        ),
                      ),
                    ],
                  )
                : Center(
                    child: Text(
                      'No New Notification',
                      style: Styles.valueText,
                    ),
                  ),
      ),
    );
  }

  notificationItem(
      dynamic notificationIndex, String profileImageUrl, String userHandle) {
    String hash = Utils.getHash(userHandle);
    final notification = controller.notification[notificationIndex];

    DateTime dt = DateTime.fromMillisecondsSinceEpoch(
        notification.ts!.toInt() * 1000);

    return Container(
      margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.start,
        children: [
          CachedNetworkImage(
            width: 46,
            height: 46,
            imageUrl: profileImageUrl +
                'users/' +
                userHandle +
                '_' +
                hash +
                '_profile.jpg',
            imageBuilder: (context, imageProvider) => Container(
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                image: DecorationImage(
                  image: imageProvider,
                  fit: BoxFit.scaleDown,
                ),
              ),
            ),
            errorWidget: (context, url, error) => Container(
              width: 40,
              height: 40,
              decoration: const BoxDecoration(
                shape: BoxShape.circle,
                image: DecorationImage(
                  image: AssetImage('assets/images/pro_icon.png'),
                  fit: BoxFit.scaleDown,
                ),
              ),
            ),
          ),
          const SizedBox(width: 10),
          Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              GestureDetector(
                onTap: () {},
                child: SizedBox(
                  width: Get.width * 0.7,
                  child: RichText(
                    text: TextSpan(children: [
                      TextSpan(
                          text: notification.t,
                          style: Styles.clickableTextMedium),
                    ]),
                  ),
                ),
              ),
              const SizedBox(height: 3),
              Text(
                "${controller.getNotificationTime(notification.ts!).toString()}  ago",
                style: Styles.searchTextGrey,
              )
            ],
          ),
        ],
      ),
    );
  }
}

代码说明

  • isWithin24Hours方法:通过计算当前时间与通知时间的差值,精准判断是否在24小时范围内
  • 分组标题逻辑:确保在分组切换(从Today到Older或反之)时自动显示对应标题,覆盖所有场景
  • 移除原有的按自然日判断逻辑,完全按照24小时阈值实现需求

内容的提问来源于stack exchange,提问作者Swatantra Singh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 15:23:14