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

Flutter加载数据后CircularProgressIndicator不消失及滚动加载更多问题排查

问题分析与修复方案

我帮你梳理下核心问题,再给出完整的修复方案:

你当前的代码存在几个关键问题:

  1. FutureBuilder误用:直接把service.getFilms()放在FutureBuilder的future参数里,每次setState触发build时都会重新发起API请求,不仅浪费资源,还会导致状态混乱、旧数据被覆盖。
  2. 无全局数据列表:每次只加载当前page的数据,没有把新数据合并到已有列表,滚动加载时旧数据会丢失。
  3. isLoading状态未生效:定义了isLoading但既没在加载时切换状态,也没在UI里用它控制指示器显示,而且滚动到底部时没判断是否正在加载,会导致重复触发加载。
  4. 滚动监听逻辑错误:滚动到底部时实例化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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 09:27:46