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成功绑定:
- PageView初始化时,通过
PageControllerProvider.of(context)获取控制器的时机不对,此时上下文可能未完成初始化,导致PageView拿到的控制器为null或未关联的实例。 - 导航抽屉中获取的控制器实例,与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.
相关产品推荐
相关产品推荐

