Flutter下拉刷新功能插件pull_to_refresh_new的使用

发布于 1周前 作者 nodeper 来自 Flutter

Flutter 下拉刷新功能插件 pull_to_refresh_new 的使用

简介

pull_to_refresh_new 是一个为 Flutter 滚动组件提供下拉刷新和上拉加载更多功能的插件。它支持 Android、iOS 和 Web 平台。如果你是中文用户,可以点击这里查看 中文文档

特性

  • 支持 Android、iOS 和 Web 的刷新指示器组件
  • 支持上拉加载更多和下拉刷新
  • 几乎适用于所有滚动组件,如 GridViewListView
  • 提供全局设置默认指示器和属性
  • 提供一些常用的指示器
  • 支持 Android 和 iOS 默认的滚动物理效果,可以控制过度滚动距离,自定义弹簧动画、阻尼、速度
  • 支持水平和垂直刷新,支持反向滚动视图(四个方向)
  • 提供多种刷新样式:Behind、Follow、UnFollow、Front,提供多种加载更多样式
  • 支持两级刷新,实现类似淘宝两级刷新、微信两级刷新
  • 支持将指示器放置在其他位置,实现类似微信朋友圈的刷新效果

使用方法

添加依赖

pubspec.yaml 文件中添加以下依赖:

dependencies:
  pull_to_refresh_new: ^2.0.5

导入包

在 Dart 文件中导入 pull_to_refresh_new 包:

import 'package:pull_to_refresh_new/pull_to_refresh_new.dart';

简单示例

以下是一个简单的示例,展示了如何使用 SmartRefresher 实现下拉刷新和上拉加载更多功能:

import 'package:flutter/material.dart';
import 'package:pull_to_refresh_new/pull_to_refresh_new.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<String> items = ["1", "2", "3", "4", "5", "6", "7", "8"];
  RefreshController _refreshController = RefreshController(initialRefresh: false);

  void _onRefresh() async {
    // 监控网络请求
    await Future.delayed(Duration(milliseconds: 1000));
    // 如果失败,使用 refreshFailed()
    _refreshController.refreshCompleted();
  }

  void _onLoading() async {
    // 监控网络请求
    await Future.delayed(Duration(milliseconds: 1000));
    // 如果失败,使用 loadFailed(),如果没有数据返回,使用 LoadNodata()
    items.add((items.length + 1).toString());
    if (mounted) {
      setState(() {});
    }
    _refreshController.loadComplete();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Pull to Refresh Example'),
      ),
      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: (context, index) => Card(child: Center(child: Text(items[index]))),
          itemExtent: 100.0,
          itemCount: items.length,
        ),
      ),
    );
  }

  @override
  void dispose() {
    _refreshController.dispose();
    super.dispose();
  }
}

全局配置

RefreshConfiguration 可以配置所有子树下的 SmartRefresher 表现,通常存储在 MaterialApp 的根部,用法类似于 ScrollConfiguration。如果其中一个 SmartRefresher 需要与其他不同,可以使用 RefreshConfiguration.copyAncestor() 复制祖先的属性并替换非空属性。

RefreshConfiguration(
  headerBuilder: () => WaterDropHeader(), // 配置默认头部指示器
  footerBuilder: () => ClassicFooter(), // 配置默认底部指示器
  headerTriggerDistance: 80.0, // 头部触发刷新的距离
  springDescription: SpringDescription(stiffness: 170, damping: 16, mass: 1.9), // 自定义回弹动画
  maxOverScrollExtent: 100, // 头部最大拖拽范围
  maxUnderScrollExtent: 0, // 底部最大拖拽范围
  enableScrollWhenRefreshCompleted: true, // 刷新完成后是否允许滚动
  enableLoadingWhenFailed: true, // 加载失败时是否允许手势上拉加载更多
  hideFooterWhenNotFull: false, // 视图小于一屏时是否禁用上拉加载更多
  enableBallisticLoad: true, // 是否通过 BallisticScrollActivity 触发加载更多
  child: MaterialApp(
    // ...
  ),
);

国际化

从 1.5.6 版本开始,增加了国际化支持,可以在 MaterialAppCupertinoApp 中添加以下代码:

MaterialApp(
  localizationsDelegates: [
    RefreshLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalMaterialLocalizations.delegate,
  ],
  supportedLocales: [
    const Locale('en'),
    const Locale('zh'),
  ],
  localeResolutionCallback: (Locale locale, Iterable<Locale> supportedLocales) {
    return locale;
  },
  // ...
);

屏幕截图

示例

风格 基础 头部在其他位置 反向 + 水平
图片 basic header in other place reverse + horizontal
风格 两级刷新 与其他组件结合 聊天
图片 twoLevel use with other widgets chat
风格 简单自定义头部 可拖动底部加载更多 GIF 指示器
图片 simple custom header draggable bottom sheet + load more GIF Indicator

指示器

刷新样式 加载更多样式
Follow Follow ShowAlways ShowAlways
UnFollow UnFollow HideAlways HideAlways
Behind Behind ShowWhenLoading ShowWhenLoading
Front Front
风格 ClassicIndicator WaterDropHeader MaterialClassicHeader
图片 ClassicIndicator WaterDropHeader MaterialClassicHeader
风格 WaterDropMaterialHeader Shimmer Indicator Bezier+Circle
图片 WaterDropMaterialHeader Shimmer Indicator Bezier+Circle

关于 SmartRefresher 的子组件说明

自 1.4.3 版本起,child 属性从 ScrollView 变为 Widget,但这并不意味着所有组件都处理相同。SmartRefresher 的内部实现机制与 NestedScrollView 不同。

主要有两种处理机制:

  1. 继承自 ScrollView 的组件,目前有三种类型:ListViewGridViewCustomScrollView
  2. 不继承自 ScrollView 的组件,一般用于放置空视图、不可滚动视图(将不可滚动转换为可滚动)、PageView 等,不需要自己通过 LayoutBuilder 估算高度。

对于第一种机制,slivers 会被“非法”地从系统中取出。第二种机制则是将子组件直接放入 SliverToBoxAdapter 类中,通过拼接头部和尾部形成 slivers,然后将 slivers 放入 SmartRefresher 中的 CustomScrollView。因此,SmartRefresher 可以理解为 CustomScrollView,因为其内部返回的是 CustomScrollView。所以,child 节点和 ScrollView 有很大的区别。

例如,你可能需要在 ScrollView 外部添加背景、滚动条等,以下是错误和正确的做法:

// 错误
SmartRefresher(
  child: ScrollBar(
    child: ListView(
      // ...
    ),
  ),
)

// 正确
ScrollBar(
  child: SmartRefresher(
    child: ListView(
      // ...
    ),
  ),
)

另一个错误的做法是将 ScrollView 放在另一个组件中:

// 错误
SmartRefresher(
  child: MainView(),
)

class MainView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView(
      // ...
    );
  }
}

上述错误会导致嵌套滚动,导致你无论怎么滑动都看不到头部和尾部。同样,如果你需要与 NotificationListenerScrollConfiguration 等组件配合使用,记得不要将它们放在 ScrollView(你想要添加刷新部分)和 SmartRefresher 之外。

更多资源

存在的问题

  • 关于 NestedScrollView,当你快速下滑再上滑时,会回到原来的位置。主要原因是 NestedScrollView 没有考虑 bouncingScrollPhysics 下的跨边界弹性问题。相关 Flutter 问题:34316, 33367, 29264。这个问题只能等待 Flutter 修复。
  • SmartRefresher 无法将刷新注入到子树下的 ScrollView 中,即如果你将 AnimatedListRecordableListView 放在 child 中是不可能的。我尝试了多种解决方法但都失败了。由于实现原理,我不得不将它们附加到 slivers 的头部和尾部。实际上,这个问题并不是我的组件问题,如 AnimatedList 无法与 AnimatedListGridView 一起使用,除非将 AnimatedList 转换为 SliverAnimatedList。目前,我有一个临时解决方案,但需要重写内部代码并在外部 ScrollView 中添加 SmartRefresher,参见我的两个示例 示例 1示例 2

致谢

感谢 SmartRefreshLayout 的启发。

许可证

MIT License

Copyright © 2023 suoshuaihong

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.


更多关于Flutter下拉刷新功能插件pull_to_refresh_new的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter下拉刷新功能插件pull_to_refresh_new的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter中使用pull_to_refresh_new插件来实现下拉刷新功能的代码示例。这个插件提供了丰富的刷新效果和配置选项,可以满足不同的需求。

首先,你需要在pubspec.yaml文件中添加该插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  pull_to_refresh_new: ^0.2.0  # 请注意版本号,根据最新版本进行替换

然后,运行flutter pub get来安装依赖。

接下来,在你的Flutter项目中,你可以按照以下方式使用pull_to_refresh_new插件:

import 'package:flutter/material.dart';
import 'package:pull_to_refresh_new/pull_to_refresh_new.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Pull To Refresh Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final RefreshController _refreshController = RefreshController();
  List<String> _items = List<String>.generate(20, (i) => "Item ${i + 1}");

  @override
  void initState() {
    super.initState();
    // 监听刷新状态变化
    _refreshController.addListener(() {
      if (_refreshController.refreshState == RefreshState.Idle) {
        // 刷新完成,可以在这里处理一些逻辑
        print('Refresh completed.');
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Pull To Refresh Example'),
      ),
      body: SmartRefresher(
        controller: _refreshController,
        enablePullDown: true,
        enablePullUp: false,
        onRefresh: _onRefresh,
        header: WaterDropHeader(),  // 可以使用不同的Header,如ClassicHeader, MaterialHeader等
        child: ListView.builder(
          itemCount: _items.length,
          itemBuilder: (context, index) {
            return ListTile(
              title: Text(_items[index]),
            );
          },
        ),
      ),
    );
  }

  Future<void> _onRefresh() async {
    // 模拟网络请求
    await Future.delayed(Duration(seconds: 2));
    // 更新数据
    setState(() {
      _items = List<String>.generate(20, (i) => "New Item ${i + 1}");
    });
    // 通知刷新完成
    _refreshController.refreshCompleted();
  }

  @override
  void dispose() {
    _refreshController.dispose();
    super.dispose();
  }
}

在这个示例中:

  1. 我们创建了一个RefreshController来控制刷新状态。
  2. 使用SmartRefresher组件包裹了一个ListView,并设置了刷新时调用的onRefresh方法和一个自定义的Header(这里使用的是WaterDropHeader,你也可以选择其他的Header样式)。
  3. _onRefresh方法中,我们模拟了一个网络请求,并在请求完成后更新了列表数据,然后调用_refreshController.refreshCompleted()来通知刷新完成。
  4. dispose方法中,我们释放了RefreshController资源,以避免内存泄漏。

这个示例展示了如何使用pull_to_refresh_new插件来实现基本的下拉刷新功能。你可以根据需求进一步自定义和扩展这个示例。

回到顶部