Flutter加载数据后CircularProgressIndicator不消失及滚动加载更多问题排查
问题分析与修复方案
我帮你梳理下核心问题,再给出完整的修复方案:
你当前的代码存在几个关键问题:
- FutureBuilder误用:直接把
service.getFilms()放在FutureBuilder的future参数里,每次setState触发build时都会重新发起API请求,不仅浪费资源,还会导致状态混乱、旧数据被覆盖。 - 无全局数据列表:每次只加载当前page的数据,没有把新数据合并到已有列表,滚动加载时旧数据会丢失。
- isLoading状态未生效:定义了
isLoading但既没在加载时切换状态,也没在UI里用它控制指示器显示,而且滚动到底部时没判断是否正在加载,会导致重复触发加载。 - 滚动监听逻辑错误:滚动到底部时实例化
ShowsListDesign()完全无效,根本不会更新当前页面状态。
下面是修复后的完整代码,关键修改我都做了标注:
int pageNumber = 1; String filterName = ''; class ShowsListDesign extends StatefulWidget { @override _ShowsListDesignState createState() => _ShowsListDesignState(); } class _ShowsListDesignState extends State<ShowsListDesign> { ScrollController controller = ScrollController(); ServicesClass service = ServicesClass(); bool isLoading = false; // 新增:维护全局数据列表,存储所有加载的影片数据 List<dynamic> films = []; @override void initState() { super.initState(); // 务必调用super.initState() controller.addListener(listenScrolling); // 初始化加载第一页数据 _loadFilms(); } // 新增:封装统一的加载逻辑 Future<void> _loadFilms() async { if (isLoading) return; // 正在加载时直接返回,防止重复请求 setState(() { isLoading = true; // 标记为加载中 }); try { final newFilms = await service.getFilms('posts/$pageNumber/$filterName'); setState(() { films.addAll(newFilms); // 把新数据合并到全局列表 pageNumber++; // 加载成功后页码+1 }); } catch (e) { // 这里可以添加错误提示,比如Toast或SnackBar print('加载失败: $e'); } finally { setState(() { isLoading = false; // 无论成功失败,都标记加载完成 }); } } @override Widget build(BuildContext context) { // 初始加载状态:无数据且正在加载 if (films.isEmpty && isLoading) { return const Center(child: CircularProgressIndicator()); } // 空状态:无数据且加载完成(可选) if (films.isEmpty && !isLoading) { return const Center(child: Text('暂无影片数据')); } return Stack( alignment: Alignment.bottomCenter, children: [ GridView.builder( // itemCount包含现有数据+加载指示器占位(如果正在加载) itemCount: films.length + (isLoading ? 1 : 0), gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 250, crossAxisSpacing: 24, mainAxisSpacing: 24, childAspectRatio: (3 / 5), ), controller: controller, itemBuilder: (context, index) { // 如果是最后一个item且正在加载,显示底部加载指示器 if (index == films.length) { return const Padding( padding: EdgeInsets.symmetric(vertical: 16), child: CircularProgressIndicator(), ); } final film = films[index]; return FilmsCard( image: film['thumbnailUrl'], title: film['title'], year: film['year'], ); }, ), FloatingActionButton( onPressed: scrollUp, elevation: 24, backgroundColor: PRIMARY, child: const Text( 'Scroll Up', textAlign: TextAlign.center, style: TextStyle( fontSize: 12, ), ), ), ], ); } void scrollUp() { const double start = 0; controller.animateTo( start, duration: const Duration(seconds: 1, milliseconds: 50), curve: Curves.easeIn, ); } void listenScrolling() { // 判断滚动到底部,且当前没有在加载 if (controller.position.pixels >= controller.position.maxScrollExtent && !isLoading) { _loadFilms(); // 触发加载更多 } } @override void dispose() { controller.dispose(); // 销毁控制器,避免内存泄漏 super.dispose(); } }
关键修改说明:
- 全局数据列表:新增
films列表存储所有加载的数据,避免滚动加载时丢失旧数据。 - 统一加载方法:
_loadFilms()封装了状态切换、数据合并、错误处理,确保isLoading状态正确更新。 - 移除FutureBuilder:改用手动初始化+加载更多,避免每次build重复发起API请求。
- 滚动监听优化:滚动到底部时先检查
isLoading状态,防止重复触发加载。 - 底部加载指示器:在GridView最后一个位置显示加载指示器,替代初始全屏加载,用户体验更流畅。
- 资源清理:在
dispose()中销毁ScrollController,避免内存泄漏。
这样修改后,加载数据时的指示器会正确显示和隐藏,滚动到底部也能正常触发加载更多,加载完成后指示器自动消失。
内容的提问来源于stack exchange,提问作者AbDalla Ahmed
相关产品推荐
相关产品推荐

