Skip to content
项目

xuelongqy/flutter_easy_refresh

在 GitHub 查看

xuelongqy/flutter_easy_refresh has 4,073 GitHub stars and 653 forks. GitHot measured +1 in 7 days. Last checked 2026-11-11 05:08 UTC. Last push 2026-10-23.

这些数字如何统计

A flutter widget that provides pull-down refresh and pull-up load.

可以做什么

  • Supports all scrollable widgets
  • Scrolling physics scope, exactly matching scrollable widgets
  • Integrate multiple cool Header and Footer
  • Support custom styles to achieve various animation effects

实测星标增长

24小时
更新延迟
7天
+1
15天
正在收集历史
1个月
正在收集历史

各周期内的星标净变化,周期相互重叠,并非每日新增。缺失的测量保持空白。星标代表关注度,不代表软件质量。

README_CN.md

原文

flutter_easy_refresh

License Platform Flutter Pub

English | 中文

正如名字一样,EasyRefresh很容易就能在Flutter应用上实现下拉刷新以及上拉加载操作,它支持几乎所有的Flutter滚动组件。它的功能与Android的SmartRefreshLayout很相似,同样也吸取了很多三方库的优点。EasyRefresh中集成了多种风格的Header和Footer,但是它并没有局限性,你可以很轻松的自定义。使用Flutter强大的动画,甚至随便一个简单的控件也可以完成。EasyRefresh的目标是为Flutter打造一个强大,稳定,成熟的下拉刷新框架。

在线演示

APK下载

API文档

特点功能:

  • 支持所有的滚动组件
  • 滚动物理作用域,精确匹配滚动组件
  • 集成多个炫酷的 Header 和 Footer
  • 支持自定义样式,实现各种动画效果
  • 支持下拉刷新、上拉加载(可使用控制器触发和结束)
  • 支持指示器位置设定,结合监听器也放置在任何位置
  • 支持页面启动时刷新,并自定义视图
  • 支持安全区域,不再有遮挡
  • 自定义滚动参数,让列表具有不同的滚动反馈和惯性

环境要求(v4.0+)

EasyRefresh 4.x 要求 Flutter >= 3.47,并基于独立的 material_ui / cupertino_ui 包。

4.0 只面向已经完成迁移的应用(dart fix --apply --code=migrate_design_widgets)。 仍在使用 package:flutter/material.dart 的应用请继续使用 easy_refresh: ^3.5.1(v3 分支),完成迁移后再升级。

内置指示器从 material_ui 读取 Theme 与本地化。典型用法:

import 'package:material_ui/material_ui.dart';
import 'package:easy_refresh/easy_refresh.dart';

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
      ),
      localizationsDelegates: GlobalMaterialLocalizations.delegates,
      home: EasyRefresh(
        onRefresh: () async {},
        onLoad: () async {},
        child: ListView(),
      ),
    );
  }
}

配套包

分页能力现在拆分到了独立的 easy_paging Pub 包中。

import 'package:easy_paging/easy_paging.dart';
import 'package:easy_refresh/easy_refresh.dart';

示例实现见:example/lib/page/sample/paging_page.dart

AI 助手技能

全部八个包均提供使用技能。在应用中添加相关依赖后,运行以下命令并选择需要安装的技能:

dart run skills@ get
包 技能
easy_paging easy-paging-usage
easy_refresh easy-refresh-indicators, easy-refresh-nested-scroll, easy-refresh-usage
easy_refresh_bow easy-refresh-bow-usage
easy_refresh_bubbles easy-refresh-bubbles-usage
easy_refresh_halloween easy-refresh-halloween-usage
easy_refresh_skating easy-refresh-skating-usage
easy_refresh_space easy-refresh-space-usage
easy_refresh_squats easy-refresh-squats-usage

技能对应 easy_refresh/easy_paging 4.x 与样式包 2.x。 安装与更新方式见 Dart Package Skills。

简单用例

1.默认构造器

  • child作用域内,所有滚动组件会公用一个physics。如果有滚动嵌套,请使用EasyRefresh.builder或用ScrollConfiguration设置作用域
  EasyRefresh(
    onRefresh: () async {
      ....
    },
    onLoad: () async {
      ....
    },
    child: ListView(),
  )

2.builder构造器

  EasyRefresh.builder(
    onRefresh: () async {
      ....
      return IndicatorResult.success; 
    },
    onLoad: () async {
      ....
    },
    childBuilder: (context, physics) {
      return ListView(
        physics: physics,
      );
    },
  )

3.指示器定位

  EasyRefresh(
    header: Header(
      position: IndicatorPosition.locator,
    ),
    footer: Footer(
      position: IndicatorPosition.locator,
    ),
    onRefresh: () async {
      ....
    },
    onLoad: () async {
      ....
      return IndicatorResult.noMore;
    },
    child: CustomScrollView(
      slivers: [
        SliverAppBar(),
        const HeaderLocator.sliver(),
        ...
        const FooterLocator.sliver(),
        ],
      ),
  )

4.控制器使用

  EasyRefreshController _controller = EasyRefreshController(
    controlFinishRefresh: true,
    controlFinishLoad: true,
  );
  ....
  EasyRefresh(
    controller: _controller,
    onRefresh: () async {
      ....
      _controller.finishRefresh();
      _controller.resetFooter();
    },
    onLoad: () async {
      ....
      _controller.finishLoad(IndicatorResult.noMore);
    },
    ....
  );
  ....
  _controller.callRefresh();
  _controller.callLoad();

5.使用指定的 Header 和 Footer

  EasyRefresh(
    header: MaterialHeader(),
    footer: MaterialFooter(),
    child: ListView(),
    ....
  )
  // 全局设置
  EasyRefresh.defaultHeaderBuilder = () => ClassicHeader();
  EasyRefresh.defaultFooterBuilder = () => ClassicFooter();

6. NestedScrollView

EasyRefresh.nested 是官方 NestedScrollView 的一等封装:内部创建该组件、加上 NestedScrollView 安全的 physics,并在设置了 onRefresh 时自动在 outer slivers 最前面插入 HeaderLocator。body 不用再传 physics。Header 必须是 clamping(否则会提升为 clamping + locator)。不支持把 bouncing 下拉刷新塞进 Nested body。上拉请用 ClassicFooter 默认的无限加载(clamping: false);clamping: true 且 infiniteOffset: null 时 NestedScrollView 进不了 overscroll。

官方 NestedScrollView 支持二楼 Header:用 SecondaryBuilderHeader 包装 clamping Header,配置 secondaryTriggerOffset、secondaryDimension,通过 EasyRefresh.nested 接入;builder 接入程序化打开时显式设置 isNested: true。参考嵌套二楼示例处理 overlap 和安全区。此支持不扩展到 ExtendedNestedScrollView。

二楼打开期间,Header 接管纵向拖动;回收达到 secondaryCloseTriggerOffset(默认 70 逻辑像素)后释放会关闭,较小拖动则回弹保持打开,点击不会关闭。使用 openHeaderSecondary() / closeHeaderSecondary() 控制按钮或返回动作;关闭后可继续正常刷新。

这一层的 Footer 绑的是当前可见 inner(当前 TabBarView)。需要按 Tab 自己分流 onLoad。要独立 noMore / Footer 状态时用写法 B。

不要用 EasyRefresh.nested 去包 ExtendedNestedScrollView(或其它自定义 nested)——它只能创建 Flutter 自带的 NestedScrollView。这种情况用 EasyRefresh.builder(isNested: true),把 childBuilder 给的 physics 赋给该 nested 的 physics。

isNested: true 表示使用 NestedScrollView 安全的 physics,不是在普通 bouncing 路径上再堆 if。

写法 A — 页级刷新(推荐)

一个 EasyRefresh 包住 NestedScrollView,一个 onRefresh。示例:example/lib/page/sample/easy_refresh_nested_page.dart。ExtendedNested 用 builder(tab_bar_view_page.dart)。

EasyRefresh.nested(
  header: MaterialHeader(
    clamping: true,
    position: IndicatorPosition.locator,
  ),
  onRefresh: () async {
    ....
  },
  onLoad: () async {
    ....
  },
  headerSliverBuilder: (context, innerBoxIsScrolled) {
    return [
      const SliverAppBar(pinned: true, expandedHeight: 120),
    ];
  },
  body: ListView(),
);

// ExtendedNestedScrollView 或自定义 nested:
EasyRefresh.builder(
  isNested: true,
  header: MaterialHeader(
    clamping: true,
    position: IndicatorPosition.locator,
  ),
  onRefresh: () async {
    ....
  },
  childBuilder: (context, physics) {
    return ExtendedNestedScrollView(
      physics: physics,
      headerSliverBuilder: (context, innerBoxIsScrolled) {
        return [
          const HeaderLocator.sliver(clearExtent: false),
          ....
        ];
      },
      body: ListView(
        physics: physics,
      ),
    );
  },
);

写法 B — 页级 Header + 每 Tab Footer

外层 EasyRefresh.nested(或 builder(isNested: true))只负责刷新(onLoad: null)。每个 Tab 再套 EasyRefresh:onRefresh: null + NotRefreshHeader()。

EasyRefresh.nested(
  header: MaterialHeader(
    clamping: true,
    position: IndicatorPosition.locator,
  ),
  onRefresh: () async {
    ....
  },
  headerSliverBuilder: (context, innerBoxIsScrolled) {
    return [
      const SliverAppBar(pinned: true, expandedHeight: 120),
    ];
  },
  body: TabBarView(
    children: [
      EasyRefresh(
        header: const NotRefreshHeader(),
        onLoad: () async {
          ....
        },
        child: ListView(),
      ),
    ],
  ),
);

写法 C — 每个 Tab 自己的 Header(NestedScrollView 示例)

Header 放在每个 Tab 的 inner slivers(TabBar 下面)时,外层不能再设 onRefresh,否则会变成页级 Header。但 NestedScrollView 的 outer 仍然需要 nested physics,否则往上推会先收 AppBar 再刷新。

外层用没有 onRefresh / onLoad 的 EasyRefresh.builder(isNested: true),只把 physics 传给 nested。每个 Tab 再套真正的 EasyRefresh。这两层 physics 不是同一份:外层冻 AppBar,里层驱动该 Tab 的指示器。

示例:example/lib/page/sample/nested_scroll_view.dart。

EasyRefresh.builder(
  isNested: true,
  childBuilder: (context, physics) {
    return NestedScrollView(
      physics: physics,
      headerSliverBuilder: (context, innerBoxIsScrolled) {
        return [
          const SliverAppBar(pinned: true, expandedHeight: 120),
        ];
      },
      body: TabBarView(
        children: [
          EasyRefresh.builder(
            isNested: true,
            header: MaterialHeader(
              clamping: true,
              position: IndicatorPosition.locator,
            ),
            onRefresh: () async {
              ....
            },
            onLoad: () async {
              ....
            },
            childBuilder: (context, innerPhysics) {
              return CustomScrollView(
                physics: innerPhysics,
                slivers: [
                  const HeaderLocator.sliver(clearExtent: false),
                  ....
                  const FooterLocator.sliver(clearExtent: false),
                ],
              );
            },
          ),
        ],
      ),
    );
  },
);

样式搜集

包名 Pub
easy_refresh_bubbles Pub
easy_refresh_bow Pub
easy_refresh_halloween Pub
easy_refresh_skating Pub
easy_refresh_space Pub
easy_refresh_squats Pub

欢迎贡献

一个人的维护是孤独的。如果你有好的建议和改动,欢迎贡献你的代码。如果你有非常酷的样式,能够分享给大家那就更酷了。

修改包内 skill 后,在仓库根目录运行:

flutter pub get
dart run tool/check_skills.dart

CI 使用同一入口,直接提取当前 Markdown 的 Dart 代码块,静态分析全部 16 个 示例,并复用 example 的依赖执行 11 项 widget 检查。新增或删除 Dart 代码块时, 同步更新 tool/check_skills.dart 的映射及 行为测试。生成文件位于 example/.dart_tool/, 成功后删除,失败时保留以便排查。六套 Rive 样式示例参与静态分析,原生动画显示 仍需在目标平台验证。Skill 仅维护英文版本。

感谢所有的贡献者!

QQ讨论群 - 554981921

进群须知

这个群不仅仅是解决EasyRefresh的问题,任何Flutter相关的问题都可以进行讨论。正如它的名字一样,有问必答,只要群主有时间,都会帮大家一起解决问题。

感谢

SmartRefreshLayout
flutter_spinkit

开源协议

 
MIT License

Copyright (c) 2018 xuelongqy

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

 

探索相关项目