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

Flutter中如何将带交互函数的ListView.builder改为StickyGroupList

解决方案:将ListView.builder替换为StickyGroupList并处理交互逻辑

要实现按日期分组的交易列表,同时保留长按选中、点击取消选中以及删除功能,核心是放弃依赖全局索引跟踪选中项,改用交易对象本身判断选中状态(分组后原全局索引会失效)。以下是修改后的完整代码:

步骤1:导入必要依赖

确保pubspec.yaml中添加以下包:

dependencies:
  flutter:
    sdk: flutter
  sticky_grouped_list: ^1.4.0 # 可自行调整为最新版本
  intl: ^0.19.0
  collection: ^1.18.0
  get: ^4.6.5 # 保留你原项目使用的GetX依赖

步骤2:完整修改代码

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:collection/collection.dart';
import 'package:sticky_grouped_list/sticky_grouped_list.dart';

// 以下为示例模型,需与你项目中的实际定义对齐
class TransactionModel {
  final double amount;
  final DateTime date;
  final String paymentmode;
  final Category category;
  final bool isexpense;

  TransactionModel({
    required this.amount,
    required this.date,
    required this.paymentmode,
    required this.category,
    required this.isexpense,
  });
}

class Category {
  final String title;
  final String iconurl;

  Category({required this.title, required this.iconurl});
}

class TransactionProvider extends GetxController {
  List<TransactionModel> showRecentTransactions = [];
  List<TransactionModel> allTransactions = [];

  void delete_transaction(TransactionModel transaction) {
    showRecentTransactions.remove(transaction);
    allTransactions.remove(transaction);
    update();
  }
}

class ShowAllTransactionScreen extends StatefulWidget {
  const ShowAllTransactionScreen({Key? key}) : super(key: key);
  
  @override
  State<ShowAllTransactionScreen> createState() => _ShowAllTransactionScreenState();
}

class _ShowAllTransactionScreenState extends State<ShowAllTransactionScreen> {
  bool islongpressed = false;
  TransactionModel? selectedtransaction; // 直接用交易对象跟踪选中项,替代原selectedindex
  bool showit = true;

  final provider = Get.put(TransactionProvider());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('交易记录'),
        actions: [
          if (islongpressed)
            TextButton(
              onPressed: () {
                provider.delete_transaction(selectedtransaction!);
                setState(() {
                  islongpressed = false;
                  selectedtransaction = null;
                });
              },
              child: const Text('删除', style: TextStyle(color: Colors.white)),
            )
        ],
      ),
      body: showTransactions(),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Navigator.of(context).pushReplacement(
            MaterialPageRoute(builder: (context) => const CreateSpend()),
          );
        },
        child: const Icon(Icons.add),
      ),
    );
  }

  Widget showTransactions() {
    return GetBuilder<TransactionProvider>(
      builder: (_) {
        if (provider.allTransactions.isEmpty) {
          return const Center(child: Text('暂无交易记录'));
        }

        return StickyGroupedListView<TransactionModel, String>(
          // 传入所有交易数据
          elements: provider.showRecentTransactions,
          // 按年月日分组,作为分组标识
          groupBy: (transaction) => DateFormat('yyyy-MM-dd').format(transaction.date),
          // 分组排序:最新日期优先显示
          groupComparator: (date1, date2) => date2.compareTo(date1),
          // 组内排序:同一日期下最新交易在前
          itemComparator: (item1, item2) => item2.date.compareTo(item1.date),
          // 自定义分组头部样式
          groupSeparatorBuilder: (groupDate) => Container(
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
            color: Colors.grey[200],
            child: Text(
              DateFormat('yyyy年MM月dd日').format(DateFormat('yyyy-MM-dd').parse(groupDate)),
              style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
            ),
          ),
          // 构建列表项
          itemBuilder: (context, transaction) {
            final isSelected = selectedtransaction == transaction;
            return TransactionCard(
              ontap: () {
                setState(() {
                  islongpressed = false;
                  selectedtransaction = null;
                });
              },
              onlongpressed: () {
                setState(() {
                  islongpressed = true;
                  selectedtransaction = transaction;
                });
              },
              amount: transaction.amount.toStringAsFixed(2),
              datetime: DateFormat('HH:mm').format(transaction.date), // 优化为显示时分
              paymentby: transaction.paymentmode,
              category: transaction.category.title,
              categoryicon: transaction.category.iconurl,
              isexpense: transaction.isexpense,
              isselected: isSelected,
            );
          },
        );
      },
    );
  }
}

// 以下为示例组件,需与你项目中的实际定义对齐
class TransactionCard extends StatelessWidget {
  final VoidCallback ontap;
  final VoidCallback onlongpressed;
  final String amount;
  final String datetime;
  final String paymentby;
  final String category;
  final String categoryicon;
  final bool isexpense;
  final bool isselected;

  const TransactionCard({
    super.key,
    required this.ontap,
    required this.onlongpressed,
    required this.amount,
    required this.datetime,
    required this.paymentby,
    required this.category,
    required this.categoryicon,
    required this.isexpense,
    required this.isselected,
  });

  @override
  Widget build(BuildContext context) {
    return ListTile(
      onTap: ontap,
      onLongPress: onlongpressed,
      tileColor: isselected ? Colors.blue[100] : null,
      title: Text(category),
      subtitle: Text('$datetime · $paymentby'),
      trailing: Text(isexpense ? '-$amount' : '+$amount', style: TextStyle(color: isexpense ? Colors.red : Colors.green)),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(appBar: AppBar(title: const Text('添加交易')));
  }
}

核心修改说明

  1. 替换索引跟踪逻辑:删除原selectedindex变量,改用selectedtransaction直接存储选中的交易对象,彻底避免分组后索引混乱的问题。
  2. 分组配置与样式:通过groupBy指定分组规则,groupComparator控制分组排序,groupSeparatorBuilder自定义分组头部的显示样式。
  3. 交互逻辑适配:点击和长按操作直接针对当前交易对象,选中状态通过selectedtransaction == transaction判断,逻辑更清晰可靠。
  4. 用户体验优化:将日期和时间格式化为更友好的样式,提升列表可读性。

内容的提问来源于stack exchange,提问作者Irfan Ganatra

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 03:21:05