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

如何基于flutter_bloc实现无重复刷新的加载更多功能

基于flutter_bloc实现无重复刷新的加载更多功能

你的问题核心在于Bloc没有维护全局的已加载数据列表,每次加载更多只传递新页数据,同时UI层的状态处理逻辑也有问题,导致重复刷新。下面是具体的修复方案:

问题分析

  1. Bloc中仅在每次请求时返回当前页数据,没有保存历史数据,导致UI需要自己累加,容易出现重复添加
  2. LoadMoreState的使用逻辑错误,UI中判断state is LoadMoreState来显示加载指示器,但状态是互斥的,发射PostLoadedState后就会覆盖之前的LoadMoreState
  3. ScrollController的listener在build方法中每次都会添加,会导致重复触发加载事件
  4. StatelessWidget中的posts成员会因为build重复执行而重复累加数据

修复步骤

1. 修改PostBloc,维护全局数据列表

在Bloc内部保存已加载的所有帖子,每次加载更多时追加新数据,并在状态中传递完整列表,同时添加加载状态标记避免重复请求:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:scroool/post_events.dart';
import 'package:scroool/post_repo.dart';
import 'package:scroool/post_states.dart';

class PostsBloc extends Bloc<PostEvents, PostsStates> {
  final PostRepo repo;
  int page = 1;
  final ScrollController controller = ScrollController();
  // 维护全局已加载的帖子列表
  List<dynamic> _allPosts = [];
  // 标记是否正在加载更多,避免重复触发
  bool _isLoadingMore = false;

  PostsBloc(this.repo) : super(PostInitState()) {
    on<FetchPosts>((event, emit) async {
      emit(PostLoadingState());
      try {
        final posts = await repo.fetchPosts(page);
        _allPosts = posts;
        emit(PostLoadedState(posts: _allPosts, isLoadingMore: false));
      } catch (e) {
        // 可以添加错误状态处理
        emit(PostErrorState(message: e.toString()));
      }
    });

    on<LoadMore>((event, emit) async {
      // 避免重复加载
      if (_isLoadingMore || controller.position.pixels != controller.position.maxScrollExtent) return;
      
      _isLoadingMore = true;
      // 发射加载中的状态,让UI显示底部加载指示器
      emit(PostLoadedState(posts: _allPosts, isLoadingMore: true));
      
      try {
        page++;
        final newPosts = await repo.fetchPosts(page);
        _allPosts.addAll(newPosts);
        _isLoadingMore = false;
        emit(PostLoadedState(posts: _allPosts, isLoadingMore: false));
      } catch (e) {
        _isLoadingMore = false;
        emit(PostLoadedState(posts: _allPosts, isLoadingMore: false));
        // 可以添加错误提示
        print('加载更多失败: $e');
      }
    });

    // 初始化listener,避免重复添加
    controller.addListener(() {
      add(LoadMore());
    });
  }

  @override
  Future<void> close() {
    controller.dispose();
    return super.close();
  }
}

2. 修改PostStates,添加加载状态标记

需要给PostLoadedState添加isLoadingMore字段,用来标记是否正在加载更多:

// post_states.dart示例
abstract class PostsStates {}

class PostInitState extends PostsStates {}

class PostLoadingState extends PostsStates {}

class PostLoadedState extends PostsStates {
  final List<dynamic> posts;
  final bool isLoadingMore;

  PostLoadedState({required this.posts, required this.isLoadingMore});
}

class PostErrorState extends PostsStates {
  final String message;

  PostErrorState({required this.message});
}

3. 修改UI层,正确处理状态

把StatelessWidget改成StatefulWidget,避免在build中重复累加数据,同时正确显示加载指示器:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:scroool/post_bloc.dart';
import 'package:scroool/post_events.dart';
import 'package:scroool/post_states.dart';

class Scroool extends StatefulWidget {
  const Scroool({super.key});

  @override
  State<Scroool> createState() => _ScrooolState();
}

class _ScrooolState extends State<Scroool> {
  @override
  void initState() {
    super.initState();
    // 初始化加载第一页数据
    context.read<PostsBloc>().add(FetchPosts());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: BlocConsumer<PostsBloc, PostsStates>(
        listener: (context, state) {},
        builder: (context, state) {
          if (state is PostLoadingState) {
            return const Center(child: CircularProgressIndicator());
          } else if (state is PostLoadedState) {
            final posts = state.posts;
            return ListView.builder(
              controller: context.read<PostsBloc>().controller,
              // 如果正在加载更多,itemCount加1用来显示底部加载指示器
              itemCount: state.isLoadingMore ? posts.length + 1 : posts.length,
              itemBuilder: (context, index) {
                if (index < posts.length) {
                  final post = posts[index];
                  return Card(
                    child: ListTile(
                      leading: CircleAvatar(child: Text("${index + 1}")),
                      title: Text(post['author'].toString()),
                      subtitle: Text(post['title']['rendered']),
                    ),
                  );
                } else {
                  // 显示底部加载指示器
                  return const Padding(
                    padding: EdgeInsets.symmetric(vertical: 16),
                    child: Center(child: CircularProgressIndicator()),
                  );
                }
              },
            );
          } else if (state is PostErrorState) {
            return Center(child: Text(state.message));
          } else {
            return const SizedBox();
          }
        },
      ),
    );
  }
}

关键优化点

  • Bloc内部维护完整数据列表,UI直接从状态获取全部数据,避免重复累加
  • 添加_isLoadingMore标记,防止滚动到底部时重复触发加载请求
  • 将ScrollController的listener移到Bloc的初始化中,避免在build中重复添加
  • 在PostLoadedState中添加isLoadingMore字段,统一管理加载状态,UI根据该字段显示底部加载指示器

内容的提问来源于stack exchange,提问作者Osama Mohammed

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 12:15:25