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
相关产品推荐
相关产品推荐

