Flutter报错:ScrollController未关联滚动视图 求解决方法
Flutter中AnimatedBuilder结合ScrollController报错的解决方法
问题场景
我在Flutter项目中尝试结合AnimatedBuilder组件实现无限滚动分页功能,编写的代码如下:
import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; class Home extends StatefulWidget { Home({Key? key}) : super(key: key); @override State<Home> createState() => _HomeState(); } class _HomeState extends State<Home> { final ScrollController _scrollController = ScrollController(); // The item list where the data is stored. List<String> items = []; // Flags used to check the loading status. bool loading = false, allLoaded = false; // Function that loads data in the item list. mockFetch() async { // No more data to load. if (allLoaded) { return; } // Set the loading flag to true to prevent this function to be called several times. setState(() { loading = true; }); // Simulates the delay that might occurs during an API call. await Future.delayed(Duration(milliseconds: 500)); // If the item list is higher or equal to 60, an empty array is returned to simulate // the end of the data stream. If not, 20 more items are generated. List<String> newData = items.length >= 60 ? [] : List.generate(20, (index) => "List Item ${index + items.length}"); // Add the new data to the item list. if (newData.isNotEmpty) { items.addAll(newData); } // Reset the flags. setState(() { loading = false; // Returns false if the array is empty. allLoaded = newData.isEmpty; }); } @override void initState() { super.initState(); // Call the mockFetch() function when the state of MyHomePage is initialized for the first time. mockFetch(); // Set a listener. _scrollController.addListener(() { if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent && !loading) { mockFetch(); } }); } @override void dispose() { super.dispose(); _scrollController.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Colors.deepOrange, title: Text("Home Page"), ), body: LayoutBuilder(builder: (context, constraints) { if (items.isNotEmpty) { return Stack( // Dispay the data. children: [ ListView.separated( // Assign the scroll controller to the ListView. controller: _scrollController, itemBuilder: (context, index) { // Display the newly loaded items. if (index < items.length) { return ListTile( title: Text(items[index]), ); } // An extra item has been added to the index of the list meaning that // all the items have been loaded. else { // Inform the user that there is no more data to load. return Container( width: constraints.maxWidth, height: 50, child: Center( child: Text("Nothing more to load"), ), ); } }, // Add a separator between each item. separatorBuilder: (context, index) { return Divider(height: 1); }, // Add an extra item to the length of the list when all of the items are loaded. itemCount: items.length + (allLoaded ? 1 : 0)), if (loading)...[ // Display a progress indicator at the bottom of the list whenever some data is loading. Positioned( left: 0, bottom: 0, child: Container( width: constraints.maxWidth, height: 80, child: Center( child: CircularProgressIndicator(), ), ) ) ] ], ); } else { return Container( child: Center( child: CircularProgressIndicator(), ), ); } } ), bottomNavigationBar: AnimatedBuilder( animation: _scrollController, builder: (context, child) { return AnimatedContainer( duration: Duration(milliseconds: 300), height: _scrollController.position.userScrollDirection == ScrollDirection.reverse ? 0: 100, child: child, ); }, child: BottomNavigationBar( backgroundColor: Colors.amber[200], items: [ BottomNavigationBarItem( icon: Icon(Icons.home), label: 'Home', ), BottomNavigationBarItem( icon: Icon(Icons.child_friendly), label: 'Child', ), ], ), ), ); // Scaffold } }
报错信息
构建AnimatedBuilder(animation: ScrollController#713ce(no clients), dirty, state: _AnimatedState#02479)时抛出断言错误:ScrollController未关联任何滚动视图。'package:flutter/src/widgets/scroll_controller.dart':第107行第12位断言失败:'_positions.isNotEmpty'
解决方案
报错根源是页面初始化阶段,ListView还未完成构建,ScrollController还未关联到任何滚动视图(即_scrollController.hasClients为false),此时直接访问_scrollController.position会触发断言失败。
你提到的_scrollController.hasClients确实是核心解决点,只需要在访问ScrollController的position属性前先判断是否有客户端关联,修改AnimatedBuilder的builder逻辑即可:
修改后的AnimatedBuilder代码
bottomNavigationBar: AnimatedBuilder( animation: _scrollController, builder: (context, child) { // 先判断ScrollController是否已关联滚动视图 final scrollDirection = _scrollController.hasClients ? _scrollController.position.userScrollDirection : ScrollDirection.idle; return AnimatedContainer( duration: Duration(milliseconds: 300), height: scrollDirection == ScrollDirection.reverse ? 0 : 100, child: child, ); }, child: BottomNavigationBar( backgroundColor: Colors.amber[200], items: [ BottomNavigationBarItem( icon: Icon(Icons.home), label: 'Home', ), BottomNavigationBarItem( icon: Icon(Icons.child_friendly), label: 'Child', ), ], ), ),
关键说明
hasClients返回布尔值,代表当前ScrollController是否已绑定至少一个滚动视图- 初始状态下默认设置
ScrollDirection.idle,确保BottomNavigationBar默认显示(高度100),避免初始化时出现异常 - 这种方式直接在builder中做判断,是最简洁有效的解决思路
内容的提问来源于stack exchange,提问作者Duddy67
相关产品推荐
相关产品推荐

