You need to enable JavaScript to run this app. Bloc模式下使用infinite_scroll_pagination实现无限滚动的方法
优惠活动
大模型
产品
解决方案
定价
更多

Bloc模式下使用infinite_scroll_pagination实现无限滚动的方法

场景说明

我目前正在将代码迁移为BLoC模式,此前使用flutter_pagewise ^1.2.3结合Future<>实现无限滚动,但不清楚如何在BLoC中使用该库,也不确定二者是否兼容。
因此我尝试切换为infinite_scroll_pagination: ^2.3.0,其官方文档说明支持BLoC,但我看不懂文档中针对BLoC的示例代码,希望得到一个二者搭配使用的简单示例,我当前使用的flutter_bloc版本为^6.1.3。

原始BLoC代码
class TimeslotViewBloc extends Bloc<TimeslotViewEvent, TimeslotViewState> {
  final GetTimeslotView gettimeslotView;

  TimeslotViewBloc({this.gettimeslotView}) : super(TimeslotViewInitialState());

  @override
  Stream<TimeslotViewState> mapEventToState(
    TimeslotViewEvent event,
  ) async* {
    if (event is GetTimeslotViewEvent) {
      yield TimeslotViewLoadingState();
      final failureOrSuccess = await gettimeslotView(Params(
        id: event.id,
        date: event.date,
        offset: event.offset,
        limit: event.limit,
      ));
      yield* _eitherLoadedOrErrorState(failureOrSuccess);
    }
  }

  Stream<TimeslotViewState> _eitherLoadedOrErrorState(
    Either<Failure, List<TimeslotViewEntity>> failureOrTrivia,
  ) async* {
    yield failureOrTrivia.fold(
      (failure) => TimeslotViewErrorState(
          message: _mapFailureToMessage(failure), failure: failure),
      (result) => TimeslotViewLoadedState(result),
    );
  }
}

// BLoC事件定义
abstract class TimeslotViewEvent extends Equatable {
  const TimeslotViewEvent();
  @override
  List<Object> get props => [];
}

class GetTimeslotViewEvent extends TimeslotViewEvent {
  final String id;
  final String date;
  final int offset;
  final int limit;

  GetTimeslotViewEvent({
    required this.id,
    required this.date,
    required this.offset,
    required this.limit
  });

  @override
  List<Object> get props => [id, date, offset, limit];
}

// BLoC状态定义
abstract class TimeslotViewState extends Equatable {
  const TimeslotViewState();
  @override
  List<Object> get props => [];
}

class TimeslotViewInitialState extends TimeslotViewState {}

class TimeslotViewLoadingState extends TimeslotViewState {}

class TimeslotViewLoadedState extends TimeslotViewState {
  final List<TimeslotViewEntity> records;

  TimeslotViewLoadedState(this.records);
  @override
  List<Object> get props => [records];
}

class TimeslotViewErrorState extends TimeslotViewState {
  final String message;
  final Failure failure;

  TimeslotViewErrorState({required this.message, required this.failure});
  @override
  List<Object> get props => [message, failure];
}
可正常运行的UI层实现代码
class TimeslotPage extends StatefulWidget {
  const TimeslotPage({super.key});

  @override
  State<TimeslotPage> createState() => _TimeslotPageState();
}

class _TimeslotPageState extends State<TimeslotPage> {
  // 分页大小按实际接口规则调整
  static const int PAGE_SIZE = 10;
  final PagingController<int, TimeslotViewEntity> _pagingController = PagingController(firstPageKey: 0);
  late TimeslotViewBloc _timeLotBloc;
  int totalRecordCount = 0;

  @override
  void initState() {
    super.initState();
    _timeLotBloc = TimeslotViewBloc(gettimeslotView: GetTimeslotView());
    // 监听分页控制器的翻页请求,触发BLoC拉取数据
    _pagingController.addPageRequestListener((pageKey) {
      _timeLotBloc.add(GetTimeslotViewEvent(
        id: "替换为实际业务id",
        date: "替换为实际日期参数",
        offset: pageKey,
        limit: PAGE_SIZE,
      ));
    });
  }

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => _timeLotBloc,
      child: BlocListener<TimeslotViewBloc, TimeslotViewState>(
        listener: (context, state) {
          if (state is TimeslotViewLoadedState) {
            // 仅统计累计条数,无需在本地维护全量列表
            totalRecordCount += state.records.length; 
            final nextPageKey = totalRecordCount;

            final isLastPage = state.records.length < PAGE_SIZE;
            if (isLastPage) {
              _pagingController.appendLastPage(state.records);
            } else {
              _pagingController.appendPage(state.records, nextPageKey);
            }
          }
          if (state is TimeslotViewErrorState) {
            _pagingController.error = state.failure;
          }
        },
        // 不要把PagedListView放在BlocBuilder中,避免不必要的全局重绘
        child: PagedListView<int, TimeslotViewEntity>(
          pagingController: _pagingController,
          builderDelegate: PagedChildBuilderDelegate<TimeslotViewEntity>(
            itemBuilder: (context, time, index) => TimeslotViewEntityListItem(
              character: time,
            ),
          ),
        ),
      ),
    );
  }

  @override
  void dispose() {
    _pagingController.dispose();
    _timeLotBloc.close();
    super.dispose();
  }
}
核心实现逻辑
  • 分页相关状态全部由infinite_scroll_pagination的PagingController统一管理,无需在BLoC中维护分页偏移量、全量数据列表,大幅降低BLoC的复杂度
  • 仅使用BlocListener监听BLoC的状态变更,触发分页控制器的状态更新,不需要用BlocBuilder包裹列表组件,避免列表全局重绘
  • 分页控制器的addPageRequestListener会在用户滚动到待加载区域时自动触发,直接携带下一页的偏移量调用BLoC拉取数据即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.17 22:54:04