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

Flutter编程切换Tab及监听事件:切换至最后Tab时报错

问题

点击FloatingActionButton实现编程式切换Tab,正常情况下可正确切换并输出选中索引;但切换到最后一个Tab后再次点击按钮会触发越界错误。

错误信息

RangeError (index): 无效值:不在0..4的闭合范围内:5

代码

class MyHome extends StatefulWidget {
  const MyHome({super.key});

  @override
  State<MyHome> createState() => _MyHomeState();
}

class _MyHomeState extends State<MyHome> with SingleTickerProviderStateMixin {
  late TabController _controller;
  int _selectedIndex = 0;

  @override
  void initState() {
    super.initState();
    _controller = TabController(length: 5, vsync: this);

    // 添加监听器
    _controller.addListener(() {
      setState(() {
        _selectedIndex = _controller.index;
      });
      print("Selected Index: " + _controller.index.toString());
    });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 5,
      child: Scaffold(
        floatingActionButton: FloatingActionButton(onPressed: () {
          _controller.animateTo(_selectedIndex += 1);
        }),
        body: NestedScrollView(
            headerSliverBuilder: (context, innerBoxIsScrolled) {
              return [
                SliverAppBar(
                  title: const Text('WELCOME'),
                  floating: true,
                  pinned: true,
                  bottom: TabBar(controller: _controller, tabs: const [
                    Tab(child: Text('Flight')),
                    Tab(child: Text('Train')),
                    Tab(child: Text('Car')),
                    Tab(child: Text('Cycle')),
                    Tab(child: Text('Boat')),
                  ]),
                )
              ];
            },
            body: TabBarView(controller: _controller, children: const [
              Icon(Icons.flight, size: 350),
              Icon(Icons.directions_transit, size: 350),
              Icon(Icons.directions_car, size: 350),
              Icon(Icons.directions_bike, size: 350),
              Icon(Icons.directions_boat, size: 350),
            ])),
      ),
    );
  }
}

问题原因

TabController的length设为5,有效索引范围是0到4(索引从0开始计数)。当选中最后一个Tab(索引4)时,点击按钮执行_selectedIndex += 1,此时_selectedIndex变为5,调用_controller.animateTo(5)会超出有效索引范围,触发越界错误。

解决方案

方案1:限制索引不超过最大值

点击按钮时判断下一个索引是否在有效范围内,超出则停止切换(或循环回到第一个Tab):

floatingActionButton: FloatingActionButton(onPressed: () {
  final nextIndex = _selectedIndex + 1;
  if (nextIndex < _controller.length) {
    _controller.animateTo(nextIndex);
  } else {
    // 可选:点击最后一个Tab后回到第一个
    // _controller.animateTo(0);
  }
}),

方案2:直接使用TabController的当前索引(更简洁)

不需要维护额外的_selectedIndex变量,直接通过_controller.index计算下一个索引:

floatingActionButton: FloatingActionButton(onPressed: () {
  final nextIndex = (_controller.index + 1) % _controller.length;
  _controller.animateTo(nextIndex);
}),

注:% _controller.length实现循环切换(最后一个Tab后回到第一个),如果不需要循环,改成判断即可。

额外优化:移除冗余的DefaultTabController

代码中同时使用了自定义TabController和DefaultTabController,后者完全冗余,可直接删除避免混淆:

@override
Widget build(BuildContext context) {
  return Scaffold(
    floatingActionButton: FloatingActionButton(onPressed: () {
      final nextIndex = (_controller.index + 1) % _controller.length;
      _controller.animateTo(nextIndex);
    }),
    body: NestedScrollView(
        headerSliverBuilder: (context, innerBoxIsScrolled) {
          return [
            SliverAppBar(
              title: const Text('WELCOME'),
              floating: true,
              pinned: true,
              bottom: TabBar(controller: _controller, tabs: const [
                Tab(child: Text('Flight')),
                Tab(child: Text('Train')),
                Tab(child: Text('Car')),
                Tab(child: Text('Cycle')),
                Tab(child: Text('Boat')),
              ]),
            )
          ];
        },
        body: TabBarView(controller: _controller, children: const [
          Icon(Icons.flight, size: 350),
          Icon(Icons.directions_transit, size: 350),
          Icon(Icons.directions_car, size: 350),
          Icon(Icons.directions_bike, size: 350),
          Icon(Icons.directions_boat, size: 350),
        ])),
  );
}

内容的提问来源于stack exchange,提问作者Hana.HD

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 18:45:35