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

Flutter Animated List报错:RangeError (index): 无效值:有效范围为空:0

解决Flutter AnimatedList的RangeError索引越界问题

错误根源

  • 你给AnimatedList设置了initialItemCount: serverList.length,但animatedTiles初始为空,且是通过Future.delayed延迟逐个添加元素的。AnimatedList会立即根据initialItemCount尝试渲染所有item,此时animatedTiles的长度远小于serverList.length,调用animatedTiles[index]就会触发索引越界错误。
  • 额外问题:dispose方法里的_controller未定义,会导致另一个运行时错误。

修复方案

方案1:动态从零构建AnimatedList(推荐,保留逐个添加的动画效果)

去掉initialItemCount(默认值为0),完全通过insertItem动态添加元素,确保animatedTiles的长度和AnimatedList的item数量同步。

修改后的核心代码:

class _ServerListViewState extends State<ServerListView> {
  final List<ServerList> serverList = List.from(servers);
  List<Widget> animatedTiles = [];
  final GlobalKey<AnimatedListState> _listkey = GlobalKey<AnimatedListState>();
  final Tween<Offset> _offset = 
      Tween(begin: const Offset(1, 0), end: const Offset(0, 0));

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      addServer();
    });
  }

  void addServer() {
    Future loading = Future(() {});

    for (var server in serverList) {
      loading = loading.then((_) {
        return Future.delayed(const Duration(milliseconds: 100), () {
          final index = animatedTiles.length;
          animatedTiles.add(
            ServerCard(serverItem: server, onTap: () {}),
          );
          _listkey.currentState!.insertItem(index);
        });
      });
    }
  }

  @override
  void dispose() {
    super.dispose();
    // 删除未定义的_controller.dispose()调用
  }

  @override
  Widget build(BuildContext context) {
    return ViewModelBuilder<ServerListViewModel>.reactive(
      builder: (context, model, child) => Scaffold(
        // 其他代码保持不变
        body: serverList.isEmpty
            ? const _EmptyServer()
            : Column(
                children: <Widget>[
                  verticalSpaceMini,
                  Expanded(
                    child: AnimatedList(
                      shrinkWrap: true,
                      padding: kEdgeInsetsAllMedium,
                      key: _listkey,
                      // 移除initialItemCount配置,默认从0开始
                      itemBuilder: (context, index, animation) {
                        return SlideTransition(
                          position: animation.drive(_offset),
                          child: Slidable(
                              endActionPane: ActionPane(
                                  motion: const StretchMotion(),
                                  children: [
                                    SlidableAction(
                                      onPressed: ((context) {}),
                                      icon: Icons.delete,
                                      backgroundColor: AppColor.kErrorColor,
                                      label: "Delete",
                                    ),
                                  ]),
                              child: animatedTiles[index]),
                        );
                      },
                    ),
                  ),
                ],
              ),
        // 其他代码保持不变
      ),
      viewModelBuilder: () => ServerListViewModel(),
    );
  }
}

方案2:初始化时填充animatedTiles(适用于不需要逐个添加动画的场景)

如果不需要延迟逐个添加的动画效果,可以在initState里直接填充animatedTiles,确保initialItemCount和animatedTiles长度一致:

@override
void initState() {
  super.initState();
  // 直接初始化所有卡片,无需延迟添加
  animatedTiles = serverList.map((server) => ServerCard(serverItem: server, onTap: () {})).toList();
}

// 删除addServer方法,因为不需要动态添加逻辑

此时AnimatedList的initialItemCount: serverList.length可以保留,itemBuilder中调用animatedTiles[index]不会出现索引越界。

额外注意点

  • 后续实现删除功能时,需同步从animatedTiles移除对应元素,并调用_listkey.currentState!.removeItem()方法,保持数据与UI状态一致。
  • 搜索功能中的ServerCard参数缺失,需补全对应属性避免UI渲染异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 02:05:22