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

Flutter调用PageController页面切换方法时触发ScrollController绑定错误

解决Flutter中PageController跳转页面时的断言错误

问题描述

调用_pageController.jumpToPage或_pageController.animateToPage时触发断言错误:

Exception has occurred.
_AssertionError ('package:flutter/src/widgets/scroll_controller.dart': Failed assertion: line 157 pos 12: '_positions.isNotEmpty':
ScrollController not attached to any scroll views.)

点击折叠导航抽屉项时触发该错误,无法切换页面;注释跳转代码后错误消失但页面仍无法切换;用hasClients判断包裹跳转逻辑后,错误消失但页面依旧无法切换,推测PageController未关联任何客户端(即PageView)。


相关代码片段

触发错误的跳转方法

void navigateToPage(int index) {
    log(index.toString());
    _pageController.jumpToPage(  // 此处报错
      index,
      // duration: Duration(milliseconds: 300),
      // curve: Curves.linear,
    );
  }

Main.dart中的组件结构

Widget build(BuildContext context) {
    return PageControllerManager(
      child: Scaffold(
        appBar: AppBar(
          elevation: 8.0,
          backgroundColor: drawerBackgroundColor,
          title: const Text(
            "Photographers Assistant",
          ),
        ),
        body: Stack(
          children: [
            PageView.builder(
                controller: PageControllerProvider.of(context)?.pageController,
                onPageChanged:
                    PageControllerProvider.of(context)?.onPageChanged,
                physics: const NeverScrollableScrollPhysics(),
                itemCount: _pages.length,
                itemBuilder: (BuildContext context, int index) {
                  return _pages[index % _pages.length];
                }),
            const CollapsingNavigationDrawer()
          ],
        ),
      ),
    );

折叠导航抽屉的点击事件

onTap: () {
    navigationItems[counter].onTap();
    setState(() {
      currentSelectedIndex = counter;
    });
    PageControllerProvider.of(context)
        ?.navigateToPage(counter);

加入hasClients判断后的跳转方法(仍无法切换)

void navigateToPage(int index) {
    log(index.toString());
    if (_pageController.hasClients) {
      _pageController.animateToPage(
        index,
        duration: Duration(milliseconds: 300),
        curve: Curves.linear,
      );
    }
  }

问题根源

核心是PageController未与PageView成功绑定:

  1. PageView初始化时,通过PageControllerProvider.of(context)获取控制器的时机不对,此时上下文可能未完成初始化,导致PageView拿到的控制器为null或未关联的实例。
  2. 导航抽屉中获取的控制器实例,与PageView实际绑定的不是同一个,或者控制器从未被PageView注册为客户端。

解决方案

1. 修正PageControllerManager的实现

确保InheritedWidget能正确传递控制器实例:

class PageControllerManager extends InheritedWidget {
  final PageController pageController;
  final void Function(int) onPageChanged;
  final void Function(int) navigateToPage;

  const PageControllerManager({
    super.key,
    required super.child,
    required this.pageController,
    required this.onPageChanged,
    required this.navigateToPage,
  });

  static PageControllerManager? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<PageControllerManager>();
  }

  @override
  bool updateShouldNotify(PageControllerManager oldWidget) {
    return pageController != oldWidget.pageController ||
        onPageChanged != oldWidget.onPageChanged ||
        navigateToPage != oldWidget.navigateToPage;
  }
}

2. 在State中正确初始化控制器

避免在build方法中重复创建控制器,保证实例唯一:

class _MyHomePageState extends State<MyHomePage> {
  late PageController _pageController;
  final List<Widget> _pages = [/* 你的页面列表 */];

  @override
  void initState() {
    super.initState();
    _pageController = PageController(initialPage: 0); // 仅初始化一次
  }

  @override
  void dispose() {
    _pageController.dispose(); // 销毁控制器避免内存泄漏
    super.dispose();
  }

  void _onPageChanged(int index) {
    // 页面切换后的逻辑
  }

  void _navigateToPage(int index) {
    if (_pageController.hasClients) {
      _pageController.animateToPage(
        index,
        duration: const Duration(milliseconds: 300),
        curve: Curves.linear,
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return PageControllerManager(
      pageController: _pageController,
      onPageChanged: _onPageChanged,
      navigateToPage: _navigateToPage,
      child: Scaffold(
        // ... 原有AppBar代码
        body: Stack(
          children: [
            PageView.builder(
              controller: _pageController, // 直接传递State中的控制器实例
              onPageChanged: _onPageChanged,
              physics: const NeverScrollableScrollPhysics(),
              itemCount: _pages.length,
              itemBuilder: (BuildContext context, int index) {
                return _pages[index % _pages.length];
              }),
            const CollapsingNavigationDrawer()
          ],
        ),
      ),
    );
  }
}

3. 在导航抽屉中正确获取控制器

// CollapsingNavigationDrawer的点击事件中
onTap: () {
  navigationItems[counter].onTap();
  setState(() {
    currentSelectedIndex = counter;
  });
  final manager = PageControllerManager.of(context);
  manager?.navigateToPage(counter);
}

关键注意点

  • 不要在build方法中创建PageController,否则会因build重复调用导致控制器实例多次创建,绑定失效。
  • 控制器需在initState中初始化,dispose中销毁,避免内存泄漏。
  • PageView直接使用State中的控制器实例,而非通过InheritedWidget获取,保证绑定的唯一性和正确性。

内容的提问来源于stack exchange,提问作者Evan R.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 13:34:53