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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 07:37:34