在 Flutter 里实现「下拉刷新 + 上拉加载更多」最常用、最省心的组合就是
RefreshIndicator(官方下拉)+ ScrollController(监听上拉)。下面给出完整可直接运行的最小示例,并补充 3 种常见需求变体,复制即可用。


一、最小可运行示例(ListView.separated 版)

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Refresh&LoadMore',
      theme: ThemeData(useMaterial3: true),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});
  
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final ScrollController _ctrl = ScrollController();
  final List<int> _data = List.generate(15, (i) => i);
  bool _isLoading = false;   // 上拉加载状态
  bool _noMore  = false;     // 数据到底标记

  
  void initState() {
    super.initState();
    _ctrl.addListener(_onScroll);
  }

  
  void dispose() {
    _ctrl.dispose();
    super.dispose();
  }

  /* 核心:上拉检测 */
  void _onScroll() {
    if (_ctrl.position.pixels >= _ctrl.position.maxScrollExtent - 100 &&
        !_isLoading &&
        !_noMore) {
      _loadMore();
    }
  }

  /* 下拉刷新 */
  Future<void> _onRefresh() async {
    await Future.delayed(const Duration(seconds: 1)); // 模拟网络
    _noMore = false;
    _data.clear();
    _data.addAll(List.generate(15, (i) => i));
    if (mounted) setState(() {});
  }

  /* 上拉加载 */
  Future<void> _loadMore() async {
    _isLoading = true;
    if (mounted) setState(() {});
    await Future.delayed(const Duration(seconds: 1));
    final append = List.generate(10, (i) => _data.length + i);
    _data.addAll(append);
    _isLoading = false;
    if (_data.length > 60) _noMore = true;   // 模拟到底
    if (mounted) setState(() {});
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('下拉刷新 + 上拉加载')),
      body: RefreshIndicator(
        onRefresh: _onRefresh,
        child: ListView.separated(
          controller: _ctrl,
          padding: const EdgeInsets.all(12),
          itemCount: _data.length + 1, // +1 给底部 loading / 到底提示
          separatorBuilder: (_, __) => const Divider(height: 16),
          itemBuilder: (context, index) {
            if (index == _data.length) {
              return _buildFooter();   // 底部组件
            }
            return ListTile(
              tileColor: Colors.blueGrey.shade50,
              title: Text('Item ${_data[index]}'),
            );
          },
        ),
      ),
    );
  }

  /* 底部:加载中 / 无更多 / 空容器 */
  Widget _buildFooter() {
    if (_noMore) {
      return const Padding(
        padding: EdgeInsets.all(16),
        child: Center(child: Text('—— 到底了 ——')),
      );
    }
    if (_isLoading) {
      return const Padding(
        padding: EdgeInsets.all(16),
        child: Center(child: CircularProgressIndicator(strokeWidth: 2)),
      );
    }
    return const SizedBox.shrink();
  }
}

运行效果:
下拉出现圆形进度 → 数据重置;滑到底部自动触发加载 → 底部出现小圆圈;加载到 60 条后提示“到底了”。


二、3 种常见变体

  1. 使用 flutter_easyrefresh 第三方库(自带 Material/Cupertino 风格、无需自己算滚动)
dependencies:
  flutter_easyrefresh: ^4.0.0
EasyRefresh(
  onRefresh: () async => ...,
  onLoad: () async => ...,
  child: ListView(...),
)
  1. 使用 CustomScrollView + Slivers 混合同步刷新
CustomScrollView(
  slivers: [
    CupertinoSliverRefreshControl(onRefresh: ...),
    SliverList(delegate: SliverChildBuilderDelegate(...)),
    if (_isLoading)
      const SliverToBoxAdapter(
        child: Center(child: CircularProgressIndicator())),
  ],
)
  1. 分页请求 + Repository 层
    _onRefresh / _loadMore 里的延时换成真正的 Dio/http 请求,分页参数 page=1/page++,异常捕获、空页判断统一放在仓库层,UI 只关心状态。

三、踩坑提醒

  1. 一定要在 dispose()removeListenerdispose()ScrollController,否则热重载会内存泄漏。
  2. 加载更多触发阈值不要写死 maxScrollExtent,留 100~150 px 缓冲体验更好。
  3. 当数据不足一屏时,ListView 不会滚动,也就不会触发 onScroll,此时可在首次加载完成后补足一页(比如后台直接返回 20 条),或主动显示“加载更多”按钮。
  4. 如果页面同时存在 TabBarView + AutomaticKeepAliveClientMixin,需要把 ScrollController 放到 PageStorageKey 里保证切换 Tab 后位置不丢失。

把上面模板直接复制进项目即可跑通,再根据业务替换网络请求、分页逻辑、空视图、错误视图即可。祝编码愉快!

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐