下拉刷新
简介
一个提供给Flutter滚动组件下拉刷新和上拉加载的组件。支持Android和iOS。如果你是中国用户,请点击这里(中文文档)
通知
本项目来自 peng8350/flutter_pulltorefresh 的fork,因为原作者可能不再维护此库。因此,我fork了这个项目并整合了原作者的提交。我对本项目不负责,也不能保证未来的更新。但如果有人提交请求到项目,我会相应地进行审查。
已整合的原项目中的 Issues 或 Commits
修复:ScrollContext.storageContext 时 element 可能已经 unmounted #510
修复不满一页且 footer=nomore 显示不全的bug #521
功能
- 上拉加载和下拉刷新
- 几乎适用于所有滚动组件,例如 GridView, ListView…
- 提供默认指示器和属性的全局设置
- 提供一些最常见的指示器
- 支持Android和iOS默认的ScrollPhysics,可控制overscroll距离,自定义弹簧动画、阻尼、速度。
- 支持水平和垂直刷新,也支持反向ScrollView(四向)。
- 提供更多刷新样式:Behind, Follow, UnFollow, Front,提供更多加载更多样式
- 支持二级刷新,实现类似淘宝二级、微信二级的效果
- 启用链接指示器,放置在其他地方,例如微信朋友圈刷新效果
用法
在 pubspec.yaml 中添加此行
dependencies:
pull_to_refresh:
git:
url: https://github.com/cube1in/pull_to_refresh.git
导入包
import 'package:pull_to_refresh/pull_to_refresh.dart';
简单示例,*在此必须指出,ListView 必须是 SmartRefresher 的子项,并且不能将其分离。有关详细原因,请参见 此处*
List<String> items = ["1", "2", "3", "4", "5", "6", "7", "8"];
RefreshController _refreshController =
RefreshController(initialRefresh: false);
void _onRefresh() async{
// monitor network fetch
await Future.delayed(Duration(milliseconds: 1000));
// if failed,use refreshFailed()
_refreshController.refreshCompleted();
}
void _onLoading() async{
// monitor network fetch
await Future.delayed(Duration(milliseconds: 1000));
// if failed,use loadFailed(),if no data return,use LoadNodata()
items.add((items.length+1).toString());
if(mounted)
setState(() {
});
_refreshController.loadComplete();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SmartRefresher(
enablePullDown: true,
enablePullUp: true,
header: WaterDropHeader(),
footer: CustomFooter(
builder: (BuildContext context,LoadStatus mode){
Widget body ;
if(mode==LoadStatus.idle){
body = Text("pull up load");
}
else if(mode==LoadStatus.loading){
body = CupertinoActivityIndicator();
}
else if(mode == LoadStatus.failed){
body = Text("Load Failed!Click retry!");
}
else if(mode == LoadStatus.canLoading){
body = Text("release to load more");
}
else{
body = Text("No more Data");
}
return Container(
height: 55.0,
child: Center(child:body),
);
},
),
controller: _refreshController,
onRefresh: _onRefresh,
onLoading: _onLoading,
child: ListView.builder(
itemBuilder: (c, i) => Card(child: Center(child: Text(items[i]))),
itemExtent: 100.0,
itemCount: items.length,
),
),
);
}
// from 1.5.0, it is not necessary to add this line
//@override
// void dispose() {
// TODO: implement dispose
// _refreshController.dispose();
// super.dispose();
// }
全局配置 RefreshConfiguration,它配置了子树下的所有 Smart Refresher 表示,通常存储在 MaterialApp 的根部,其用法类似于 ScrollConfiguration。此外,如果你的某个 SmartRefresher 的行为与世界其他地方不同,你可以使用 RefreshConfiguration.copyAncestor() 来复制其祖先 RefreshConfiguration 的属性并替换非空的属性。
// Smart Refresher under the global configuration subtree, here are a few particularly important attributes
RefreshConfiguration(
headerBuilder: () => WaterDropHeader(), // Configure the default header indicator. If you have the same header indicator for each page, you need to set this
footerBuilder: () => ClassicFooter(), // Configure default bottom indicator
headerTriggerDistance: 80.0, // header trigger refresh trigger distance
springDescription:SpringDescription(stiffness: 170, damping: 16, mass: 1.9), // custom spring back animate,the props meaning see the flutter api
maxOverScrollExtent :100, //The maximum dragging range of the head. Set this property if a rush out of the view area occurs
maxUnderScrollExtent:0, // Maximum dragging range at the bottom
enableScrollWhenRefreshCompleted: true, //This property is incompatible with PageView and TabBarView. If you need TabBarView to slide left and right, you need to set it to true.
enableLoadingWhenFailed : true, //In the case of load failure, users can still trigger more loads by gesture pull-up.
hideFooterWhenNotFull: false, // Disable pull-up to load more functionality when Viewport is less than one screen
enableBallisticLoad: true, // trigger load more by BallisticScrollActivity
child: MaterialApp(
........
)
);
1.5.6 添加了新功能:本地化,你可以在 MaterialApp 或 CupertinoApp 中添加以下代码
MaterialApp(
localizationsDelegates: [
// this line is important
RefreshLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalMaterialLocalizations.delegate
],
supportedLocales: [
const Locale('en'),
const Locale('zh'),
],
localeResolutionCallback:
(Locale locale, Iterable<Locale> supportedLocales) {
//print("change language");
return locale;
},
)
截图
示例
| 风格 | 基础 | 头部在其他地方 | 反向 + 水平 |
|---|---|---|---|
![]() |
![]() |
![]() |
| 风格 | 二级 | 与其他小部件一起使用 | 聊天 |
|---|---|---|---|
![]() |
![]() |
![]() |
| 风格 | 简单的自定义头部(使用 SpinKit) | 可拖动滚动表单 + 加载更多 | Gif 指示器 |
|---|---|---|---|
![]() |
![]() |
![]() |
指示器
各种指示器
| 刷新样式 | 上拉加载样式 | ||
|---|---|---|---|
RefreshStyle.Follow ![]() |
RefreshStyle.UnFollow ![]() |
LoadStyle.ShowAlways ![]() |
LoadStyle.HideAlways ![]() |
RefreshStyle.Behind ![]() |
RefreshStyle.Front ![]() |
LoadStyle.ShowWhenLoading ![]() |
| 风格 | 经典指示器 | 水滴头部 | Material经典头部 |
|---|---|---|---|
![]() |
![]() |
![]() |
| 风格 | 水滴Material头部 | 闪烁指示器 | 贝塞尔曲线+圆形 |
|---|---|---|---|
![]() |
![]() |
![]() |
关于 SmartRefresher 的子项说明
从 1.4.3 开始,child 属性从 ScrollView 改为 Widget,但这并不意味着所有 Widget 都被同样处理。SmartRefresher 的内部实现机制不像 NestedScrollView。这里有两种主要的处理机制:第一类是继承自 ScrollView 的组件。目前只有三种类型:ListView、GridView、CustomScrollView。第二类是未继承自 ScrollView 的组件,通常放置空视图、不可滚动视图(NoScrollable 转换成 Scrollable)、PageView,您无需自行通过 LayoutBuilder 估算高度。
对于第一种机制,从系统中“非法”地取出 slivers。第二种是将子项直接放入 `SliverToBoxAdapter` 等类中。通过前后拼接头部和尾部形成 slivers,然后将 slivers 放入 Smart Refresher 的 CustomScrollView 中,您可以将 Smart Refresher 理解为 CustomScrollView,因为内部是通过返回 CustomScrollView 来实现的。因此,子节点与 ScrollView 之间存在很大差异。
现在,假设您有一个需求:需要在 ScrollView 外部添加背景、滚动条或类似的东西。这里演示了错误的实践和正确的做法。
//error
SmartRefresher(
child: ScrollBar(
child: ListView(
....
)
)
)
// right
ScrollBar(
child: SmartRefresher(
child: ListView(
....
)
)
)
演示另一个错误的做法,将 ScrollView 放在另一个 widget 中
//error
SmartRefresher(
child:MainView()
)
class MainView extends StatelessWidget{
Widget build(){
return ListView(
....
);
}
}
上述错误导致了 scrollable 嵌套另一个 scrollable,无论您如何滑动,都无法看到头部和尾部。同样,您可能需要使用 NotificationListener、ScrollConfiguration… 等组件,请记住,不要将它们存储在 ScrollView(您想要添加刷新部分)和 Smart Refresher 之外。
将 SmartRefresher 放入 Column 中
如果您想将 SmartRefresher 放入 Column 中,您需要将其包装在 Expanded() Widget 中,例如:
Column(
children: [
Text('hello world',
Expanded(child: SmartRefresher(...))
],
)
更多
现有问题
- 关于 NestedScrollView,当您快速向下滚动然后快速向上滑动时,它会返回。主要原因是 NestedScrollView 在 bouncingScrollPhysics 下没有考虑跨边界弹性的问题。相关的 Flutter issue:34316、33367、29264。这个问题只能等待 Flutter 来修复。
- SmartRefresher 没有将刷新注入到子树的 ScrollView 中,也就是说,如果您在子项中放置 AnimatedList 或 RecordableListView 是不可能的。我尝试了多种方法来解决这个问题,但都失败了。由于实现原理,我必须将其附加到 slivers 的头部和尾部。实际上,问题不是我的组件问题,例如 AnimatedList,除非我将 AnimatedList 转换为 SliverAnimatedList,否则它不能与 AnimatedList 和 GridView 一起使用。目前,我有一个临时的解决方案,但它有点麻烦,需要重写其中的代码,然后移到 ScrollView 之外。添加 SmartRefresher,请参阅我的两个示例 示例 1 和 示例 2
感谢
许可
MIT License
Copyright (c) 2018 Jpeng
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.





















