精选文章

Flutter 列表刷新

2021-03-29 · 组件

列表刷新是最常见的业务场景,问题也最多:重复请求、分页乱序、空态难处理。下面以 easy_refresh 为例,给出一套“能直接用”的完整方案。

0. 依赖

dependencies:
  easy_refresh: ^3.4.0

1. 定义分页状态模型

class PageState<T> {
  final List<T> items;
  final bool loading;
  final bool hasMore;
  final String? error;

  const PageState({
    this.items = const [],
    this.loading = false,
    this.hasMore = true,
    this.error,
  });

  PageState<T> copyWith({
    List<T>? items,
    bool? loading,
    bool? hasMore,
    String? error,
  }) {
    return PageState<T>(
      items: items ?? this.items,
      loading: loading ?? this.loading,
      hasMore: hasMore ?? this.hasMore,
      error: error,
    );
  }
}

2. 控制器与数据加载

class ListController<T> extends ChangeNotifier {
  final Future<List<T>> Function(int page) loader;
  final EasyRefreshController refreshController = EasyRefreshController();

  PageState<T> state = const PageState();
  int _page = 1;

  ListController({required this.loader});

  Future<void> refresh() async {
    _page = 1;
    state = state.copyWith(loading: true, error: null);
    notifyListeners();
    try {
      final data = await loader(_page);
      state = state.copyWith(items: data, hasMore: data.isNotEmpty, loading: false);
      refreshController.finishRefresh();
    } catch (e) {
      state = state.copyWith(loading: false, error: e.toString());
      refreshController.finishRefresh(IndicatorResult.fail);
    }
    notifyListeners();
  }

  Future<void> loadMore() async {
    if (!state.hasMore) return;
    try {
      final data = await loader(_page + 1);
      _page += 1;
      state = state.copyWith(items: [...state.items, ...data], hasMore: data.isNotEmpty);
      refreshController.finishLoad(data.isEmpty ? IndicatorResult.noMore : IndicatorResult.success);
    } catch (e) {
      refreshController.finishLoad(IndicatorResult.fail);
    }
    notifyListeners();
  }
}

3. UI 使用方式

ChangeNotifierProvider(
  create: (_) => ListController(loader: fetchPage)..refresh(),
  child: Consumer<ListController>(builder: (context, ctrl, _) {
    if (ctrl.state.error != null) return ErrorView(onRetry: ctrl.refresh);
    if (ctrl.state.items.isEmpty) return const EmptyView();
    return EasyRefresh(
      controller: ctrl.refreshController,
      onRefresh: ctrl.refresh,
      onLoad: ctrl.loadMore,
      child: ListView.builder(
        itemCount: ctrl.state.items.length,
        itemBuilder: (_, i) => Text(ctrl.state.items[i].toString()),
      ),
    );
  }),
)

4. 只刷新 / 只加载

// 只刷新
EasyRefresh(onRefresh: ctrl.refresh, child: ListView(...))

// 只加载
EasyRefresh(onLoad: ctrl.loadMore, child: ListView(...))

5. 主动触发刷新

ctrl.refreshController.callRefresh();

6. 结束状态

refreshController.finishRefresh(IndicatorResult.success);
refreshController.finishLoad(IndicatorResult.noMore);

7. 常见坑点

  • 重复触发:刷新与加载不要同时改同一套分页参数
  • hasMore 失控:空列表必须立即 noMore
  • 错误态不清理:错误后要允许手动重试

8. 实践清单

  • 分页状态集中管理
  • 刷新/加载分离
  • 空态/错误态明确
  • 控制器统一收敛

JJ

作者简介

专注于内容创作、产品策略与设计实践。欢迎交流合作。

上一篇 下一篇