Flutter+Cubit中ScrollController绑定多滚动视图报错问题
Flutter TabBar切换报错:ScrollController绑定多个滚动视图
问题现象
TabBar根据isLoggedIn状态展示不同页面集合:登录后显示4个页面,未登录显示3个页面。登录后执行注销操作返回登录页正常,但后续无法切换TabBar页面,抛出断言错误:
ScrollController attached to multiple scroll views.
'package:flutter/src/widgets/scroll_controller.dart':
Failed assertion: line 108 pos 12: '_positions.length == 1'
相关代码
Tabbar视图代码
class TabbarCubitView extends StatelessWidget { final bool isLoggedIn; const TabbarCubitView({Key? key, required this.isLoggedIn}) : super(key: key); @override Widget build(BuildContext context) { final tabbarCubit = context.watch<TabbarCubit>(); return Scaffold( extendBody: true, body: PageView( controller: tabbarCubit.controller, onPageChanged: (index) { tabbarCubit.changeTab(index); }, children: isLoggedIn ? const [ ChamadosView(), ArtigosView(), ChatView(), ConfiguracaoDeConta(), ] : const [ TelaLogin(), ArtigosView(), ChatView(), ]), bottomNavigationBar: BlocBuilder<TabbarCubit, int>( builder: (context, state) { final bottomNavBarItems = <BottomNavigationBarItem>[ const BottomNavigationBarItem( icon: Icon(Icons.paste), label: 'Chamados', ), const BottomNavigationBarItem( icon: Icon(Icons.help), label: 'Ajuda', ), const BottomNavigationBarItem( icon: Icon(Icons.chat), label: 'Chat', ), ]; if (isLoggedIn == true) { bottomNavBarItems.add( const BottomNavigationBarItem( icon: Icon(Icons.settings), label: 'Configurações', ), ); } return BottomNavigationBar( type: BottomNavigationBarType.fixed, currentIndex: state, onTap: (value) { context.read<TabbarCubit>().jumpToPage(value); }, elevation: 0, selectedItemColor: roxoPadrao, unselectedItemColor: Colors.grey, items: bottomNavBarItems, ); }, ), ); } }
Tabbar状态管理代码
class TabbarCubit extends Cubit<int> { final PageController controller; TabbarCubit(this.controller) : super(0); void jumpToPage(int index) { controller.animateToPage( index, duration: const Duration(milliseconds: 300), curve: Curves.linear, ); emit(index); } void changeTab(int newIndex) { emit(newIndex); } }
注销逻辑代码
logoffEmpresa(BuildContext context) async { try { var resposta = await CustomEmpresaContaHttp.logoffEmpresa(context); if (resposta != null) { SharedPreferences sharedPreferences = await SharedPreferences.getInstance(); await sharedPreferences.clear(); context.read<TabbarCubit>().jumpToPage(0); Navigator.pushReplacementNamed( context, Routes.tabbarCubitViewSemLogar); } else { FlushBarComponente.mostrar( context, 'erro ao sair da conta', Icons.check, verdePadrao); } } catch (exception) { FlushBarComponente.mostrar( context, 'Ocorreu um erro. Por favor, tente novamente.', Icons.warning_amber, vermelhoPadrao); } }
问题原因
注销时通过Navigator.pushReplacementNamed打开了新的TabbarCubitView实例,但原有的TabbarCubit(包含同一个PageController实例)并未被销毁,导致同一个PageController被同时绑定到旧的PageView和新的PageView上,触发Flutter的断言检查。
修复方案
方案1:为新视图创建独立的PageController
在路由注入TabbarCubit时,确保每次实例化都创建新的PageController,避免复用旧控制器:
// 路由注册或页面跳转时的BlocProvider包裹 BlocProvider( create: (context) => TabbarCubit(PageController()), child: TabbarCubitView(isLoggedIn: false), )
方案2:在注销时销毁旧控制器
修改注销逻辑,先销毁旧的PageController再跳转页面:
logoffEmpresa(BuildContext context) async { try { var resposta = await CustomEmpresaContaHttp.logoffEmpresa(context); if (resposta != null) { SharedPreferences sharedPreferences = await SharedPreferences.getInstance(); await sharedPreferences.clear(); // 销毁旧的PageController final tabbarCubit = context.read<TabbarCubit>(); tabbarCubit.controller.dispose(); Navigator.pushReplacementNamed( context, Routes.tabbarCubitViewSemLogar); } else { FlushBarComponente.mostrar( context, 'erro ao sair da conta', Icons.check, verdePadrao); } } catch (exception) { FlushBarComponente.mostrar( context, 'Ocorreu um erro. Por favor, tente novamente.', Icons.warning_amber, vermelhoPadrao); } }
方案3:让Cubit管理控制器生命周期
修改TabbarCubit,在内部初始化PageController并在销毁时清理,避免外部复用:
class TabbarCubit extends Cubit<int> { late final PageController controller; TabbarCubit() : super(0) { controller = PageController(); } void jumpToPage(int index) { controller.animateToPage( index, duration: const Duration(milliseconds: 300), curve: Curves.linear, ); emit(index); } void changeTab(int newIndex) { emit(newIndex); } @override Future<void> close() { controller.dispose(); // 销毁控制器 return super.close(); } }
额外优化:避免索引越界
注销后页面数量从4变为3,直接调用jumpToPage可能存在风险,改为直接更新状态即可:
// 替换注销逻辑中的jumpToPage context.read<TabbarCubit>().emit(0);
内容的提问来源于stack exchange,提问作者Natan
相关产品推荐
相关产品推荐

