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

Flutter中ListView.Builder自动滚动到当前月份组件的报错求助

问题:横向ListView.builder自动滚动到当前月份组件失败,出现多GlobalKey及ScrollController使用错误

场景与初始错误

我有一个横向ListView.builder,每个子组件显示月份名称,需求是进入页面时自动滚动到当前月份的组件。目前已实现当前月份预选中,但自动滚动功能失效,触发如下错误:

Multiple widgets used the same GlobalKey.

相关代码

final selectedItem = GlobalKey();

List<Map<String, dynamic>> months = [
  {'month': 'Jan', 'onclick': false},
  {'month': 'Feb', 'onclick': false},
  {'month': 'Mar', 'onclick': false},
  {'month': 'Apr', 'onclick': false},
  {'month': 'May', 'onclick': false},
  {'month': 'Jun', 'onclick': false},
  {'month': 'Jul', 'onclick': false},
  {'month': 'Aug', 'onclick': false},
  {'month': 'Sep', 'onclick': false},
  {'month': 'Oct', 'onclick': false},
  {'month': 'Nov', 'onclick': false},
  {'month': 'Dec', 'onclick': false},
];

@override
void initState() {
  var now = new DateTime.now();
  current_mon = now.month.toString();

  print('CURRENT MONTH: ${monthFormat.format(DateTime.parse(now.toString()))}');

  for (int i = 0; i < months.length; i++) {
    if (months[i]['month'] ==
        monthFormat.format(DateTime.parse(now.toString()))) {
      setState(() {
        months[i]['onclick'] = true;
        scrollToItem();
      });
    }
  }

  Map data = {
    "month": '',
    "all": 1,
    "year": myYears.toString(),
  };

  claimz_list.postClaimzHistoryList(context, data);
  super.initState();
}

Future<void> scrollToItem() async {
  final selectedContext = selectedItem.currentContext;

  await Scrollable.ensureVisible(context,
      duration: const Duration(seconds: 1));
}

Widget build(BuildContext context) {
 return ListView.builder(
                      scrollDirection: Axis.horizontal,
                      itemBuilder: (context, index) => InkWell(
                        onTap: () {
                          current_mon = months[index]['month'];

                          for (int i = 0; i < months.length; i++) {
                            if (months[i]['onclick'] == true) {
                              setState(() {
                                months[i]['onclick'] = false;
                              });
                            }
                          }

                          setState(() {
                            months[index]['onclick'] = true;
                          });

                          Map data = {
                            "month": current_mon.toString(),
                            "all": 1,
                            "year": myYears.toString(),
                          };
                          if (kDebugMode) {
                            print(data);
                          }
                          claimz_list.postClaimzHistoryList(context, data);
                        },
                        child: Padding(
                          padding: EdgeInsets.only(
                              right: SizeVariables.getWidth(context) * 0.04),
                          child: CircleAvatar(
                            key: selectedItem,
                            backgroundColor:
                                const Color.fromARGB(174, 51, 51, 51),
                            radius: 40,
                            child: Center(
                              child: FittedBox(
                                fit: BoxFit.contain,
                                child: Text(
                                  months[index]['month'],
                                  style: months[index]['onclick'] == false
                                      ? Theme.of(context).textTheme.bodySmall
                                      : Theme.of(context)
                                          .textTheme
                                          .bodySmall!
                                          .copyWith(color: Colors.amber),
                                ),
                              ),
                            ),
                          ),
                        ),
                      ),
                      itemCount: months.length,
                    );
                  }
                }

尝试ScrollController后的新错误

改用ScrollController时,触发新错误:

The argument type 'Future' can't be assigned to the parameter type 'ScrollController?'

尝试的代码片段

ScrollController scrollController = ScrollController();

ListView.builder(
                      scrollDirection: Axis.horizontal,
                      controller: scrollController.animateTo(offset, duration: duration, curve: curve),   // 错误行
                      itemBuilder: (context, index) => InkWell(
                        onTap: () {
                          current_mon = months[index]['month'];

                          for (int i = 0; i < months.length; i++) {
                            if (months[i]['onclick'] == true) {
                              setState(() {
                                months[i]['onclick'] = false;
                              });
                            }
                          }

                          setState(() {
                            months[index]['onclick'] = true;
                          });

                          Map data = {
                            "month": current_mon.toString(),
                            "all": 1,
                            "year": myYears.toString(),
                          };
                          if (kDebugMode) {
                            print(data);
                          }
                          claimz_list.postClaimzHistoryList(context, data);
                        },
                        child: Padding(
                          padding: EdgeInsets.only(
                              right: SizeVariables.getWidth(context) * 0.04),
                          child: CircleAvatar(
                            backgroundColor:
                                const Color.fromARGB(174, 51, 51, 51),
                            radius: 40,
                            child: Center(
                              child: FittedBox(
                                fit: BoxFit.contain,
                                child: Text(
                                  months[index]['month'],
                                  style: months[index]['onclick'] == false
                                      ? Theme.of(context).textTheme.bodySmall
                                      : Theme.of(context)
                                          .textTheme
                                          .bodySmall!
                                          .copyWith(color: Colors.amber),
                                ),
                              ),
                            ),
                          ),
                        ),
                      ),
                      itemCount: months.length,
                    ),

解决方案

1. 修复GlobalKey重复问题

你将同一个GlobalKey赋值给了所有CircleAvatar,违反了Flutter中组件key必须唯一的规则,直接删除该GlobalKey,改用ScrollController实现滚动即可。

2. 正确使用ScrollController实现自动滚动

步骤1:初始化并管理ScrollController

late ScrollController scrollController;

@override
void initState() {
  scrollController = ScrollController();
  super.initState();
}

@override
void dispose() {
  scrollController.dispose(); // 销毁控制器防止内存泄漏
  super.dispose();
}

步骤2:计算偏移量并执行滚动

在initState中找到当前月份的索引,计算对应滚动偏移量,延迟执行滚动确保ListView已构建完成:

@override
void initState() {
  scrollController = ScrollController();
  var now = DateTime.now();
  String currentMonthStr = monthFormat.format(now);
  int currentMonthIndex = months.indexWhere((item) => item['month'] == currentMonthStr);
  
  // 标记当前月份为选中状态
  if (currentMonthIndex != -1) {
    setState(() {
      months[currentMonthIndex]['onclick'] = true;
    });
    
    // 延迟执行滚动
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (mounted) {
        // 计算单个列表项总宽度:头像直径 + 右侧间距
        double itemWidth = 40 * 2 + SizeVariables.getWidth(context) * 0.04;
        double targetOffset = currentMonthIndex * itemWidth;
        scrollController.animateTo(
          targetOffset,
          duration: const Duration(seconds: 1),
          curve: Curves.easeInOut,
        );
      }
    });
  }
  
  // 原有数据请求逻辑
  Map data = {
    "month": '',
    "all": 1,
    "year": myYears.toString(),
  };
  claimz_list.postClaimzHistoryList(context, data);
  
  super.initState();
}

步骤3:给ListView绑定ScrollController

ListView.builder(
  scrollDirection: Axis.horizontal,
  controller: scrollController, // 传入控制器实例而非调用animateTo的返回值
  itemBuilder: (context, index) => InkWell(
    // 原有点击逻辑
  ),
  itemCount: months.length,
)

3. 简化选中状态切换逻辑

onTap: () {
  setState(() {
    // 重置所有月份选中状态
    for (var month in months) {
      month['onclick'] = false;
    }
    // 设置当前点击月份为选中
    months[index]['onclick'] = true;
    current_mon = months[index]['month'];
  });
  
  // 数据请求逻辑
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 15:50:39