likes
comments
collection
share

Flutter学习之刷新组件-pull_to_refresh

作者站长头像
站长
· 阅读数 3
  • 本文主要介绍三方组件中刷新组件,下拉刷新,上拉加载更多。

1. 刷新组件

对于刷新组件一些经过时间的考验,基本上都解决了问题,类似我们MJRefresh,使用的人比较多 比如:pull_to_refresh: ^2.0.0

Flutter学习之刷新组件-pull_to_refresh 或者 flutter_easyrefresh: ^2.2.1

Flutter学习之刷新组件-pull_to_refresh

基本上都是可以解决我们的需求,自定义上拉下拉动画,文字描述等。这里我使用的是pull_to_refresh,这里作者适配了flutter 3.0 适配 我们依赖的时候添加git地址

pull_to_refresh:
  git:
    url: https://github.com/miquelbeltran/flutter_pulltorefresh

2. pull_to_refresh

这里介绍下pull_to_refresh的使用。组件的特性如下:

  • 提供上拉加载和下拉刷新
  • 几乎适合所有部件
  • 提供全局设置默认指示器和属性
  • 提供多种比较常用的指示器
  • 支持Android和iOS默认滑动引擎,可限制越界距离,打造自定义弹性动画,速度,阻尼等。
  • 支持水平和垂直刷新,同时支持翻转列表(四个方向)
  • 提供多种刷新指示器风格:跟随,不跟随,位于背部,位于前部, 提供多种加载更多风格
  • 提供二楼刷新,可实现类似淘宝二楼,微信二楼,携程二楼
  • 允许关联指示器存放在Viewport外部,即朋友圈刷新效果

这里我们看下简单的使用,这里使用默认的上拉和下拉Widget


class RefreshPage extends StatefulWidget {
  const RefreshPage({Key? key}) : super(key: key);

  @override
  State<RefreshPage> createState() => _RefreshPageState();
}

class _RefreshPageState extends State<RefreshPage> {


  List<String> items = ["1", "2", "3", "4", "5", "6", "7", "8"];
  final RefreshController _refreshController =
  RefreshController(initialRefresh: false);

  void _onRefresh() async{
    // monitor network fetch
    await Future.delayed(const Duration(milliseconds: 1000));
    // if failed,use refreshFailed()
    items = ["1", "2", "3", "4", "5", "6", "7", "8"];
    _refreshController.refreshCompleted();
    if(mounted) {
      setState(() {

      });
    }
  }

  void _onLoading() async{
    // monitor network fetch
    await Future.delayed(const 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(
      appBar: AppBar(title: Text(Get.arguments['title']),),
      body: SmartRefresher(
        enablePullUp: true,
        controller: _refreshController,
        onRefresh: _onRefresh,
        onLoading: _onLoading,
        child: ListView.builder(
          physics: const ClampingScrollPhysics(),
          itemBuilder: (c, i) => Card(child: Center(child: Text(items[i],style: const TextStyle(color: Colors.black),))),
          itemExtent: 100.0,
          itemCount: items.length,
        ),
      ),
    );
  }
}

下拉刷新

Flutter学习之刷新组件-pull_to_refresh

上拉加载的时候enablePullUp设置为true,我们模拟往数据中加载2条

Flutter学习之刷新组件-pull_to_refresh

我们也可以自定义一些

对于下拉刷新头我们可以使用默认WaterDropHeader

上拉刷新的footer我们可以像案列中一样自定义

CustomFooter get buildCustomFooter {
  return CustomFooter(
    builder: (BuildContext context, LoadStatus? mode) {
      Widget body;
      if (mode == LoadStatus.idle) {
        body = const Text("pull up load");
      } else if (mode == LoadStatus.loading) {
        body = const CupertinoActivityIndicator();
      } else if (mode == LoadStatus.failed) {
        body = const Text("Load Failed!Click retry!");
      } else if (mode == LoadStatus.canLoading) {
        body = const Text("Release to Load more");
      } else {
        body = const Text("No more Data");
      }
      return Container(
        height: 55.0,
        child: Center(child: body),
      );
    },
  );
}

Flutter学习之刷新组件-pull_to_refresh

这样一个简单的下拉刷新和上拉加载就完成了

3. 全局配置RefreshConfiguration

我们实际开发肯定要对其进行二次封装,首先我们知道官方说全局配置子树下的SmartRefresher

    // 全局配置子树下的SmartRefresher,下面列举几个特别重要的属性
     RefreshConfiguration(
         headerBuilder: () => WaterDropHeader(),        // 配置默认头部指示器,假如你每个页面的头部指示器都一样的话,你需要设置这个
         footerBuilder:  () => ClassicFooter(),        // 配置默认底部指示器
         headerTriggerDistance: 80.0,        // 头部触发刷新的越界距离
         springDescription:SpringDescription(stiffness: 170, damping: 16, mass: 1.9),         // 自定义回弹动画,三个属性值意义请查询flutter api
         maxOverScrollExtent :100, //头部最大可以拖动的范围,如果发生冲出视图范围区域,请设置这个属性
         maxUnderScrollExtent:0, // 底部最大可以拖动的范围
         enableScrollWhenRefreshCompleted: true, //这个属性不兼容PageView和TabBarView,如果你特别需要TabBarView左右滑动,你需要把它设置为true
         enableLoadingWhenFailed : true, //在加载失败的状态下,用户仍然可以通过手势上拉来触发加载更多
         hideFooterWhenNotFull: false, // Viewport不满一屏时,禁用上拉加载更多功能
         enableBallisticLoad: true, // 可以通过惯性滑动触发加载更多
        child: MaterialApp(
            ........
        )
    );

我们创建一个Widget

Widget refreshScaffold({required Widget child}) => RefreshConfiguration(
    headerBuilder: () => const WaterDropHeader(), footerBuilder: () => const ClassicFooter(), child: child);

在入口包裹

Flutter学习之刷新组件-pull_to_refresh

这样我们对一些页面就不用在每个页面配置header和footer等组件了,统一在RefreshConfiguration设置即可。

4. 自定义封装数据请求逻辑

我们一般分页的时候也是对数据进行判断处理,我们可以抽出来这个类进行处理

enum RefreshType { refresh, loadMore }

class PagingData<T> {
  int count;
  List<T> items;
  int current = 1;

  PagingData(this.count, this.items);

  factory PagingData.fromJson(Map<String, dynamic> json, T Function(Map<String, dynamic>) decoder) {
    final count = json['count'];
    final items = (json['results'] as List<dynamic>).map((e) => decoder(e)).toList();
    return PagingData(count, items);
  }

  @override
  String toString() => '分页: $count';

  bool get isEnd => items.length >= count;

  void merge(PagingData<T> other, RefreshType refreshType) {
    if (refreshType == RefreshType.refresh) items.clear();
    count = other.count;
    items.addAll(other.items);
  }

  void prepare(RefreshType refreshType) => current = refreshType == RefreshType.refresh ? 1 : (current + 1);
}

根据RefreshType类型,进行对应的处理

mixin RefreshMixin<T> {
  final refreshController = RefreshController(initialRefresh: true);

  List<T> get items => paging.items;
  final PagingData<T> paging = PagingData(0, []);

  Future<PagingData> startRefresh(RefreshType refreshType) {
    paging.prepare(refreshType);
    return refreshRequest.then((value) {
      paging.merge(value, refreshType);
      if (refreshType == RefreshType.refresh) {
        refreshController.refreshCompleted(resetFooterState: true);
        if (paging.isEnd) refreshController.loadNoData();
      } else {
        paging.isEnd ? refreshController.loadNoData() : refreshController.loadComplete();
      }
      return paging;
    });
  }

  Future<PagingData<T>> get refreshRequest => throw UnimplementedError('子类必须重写');
}

我们处理刷新请求,根据请求是否是刷新还是加载更多进行判断,同时判断是否是加载完成,改变状态。 对于请求子类必须重写。 比如我们的帖子类实现该协议,进行上拉下拉

class PostListController extends GetxController with RefreshMixin<Post>, NetMixin {
  /// 刷新
  onRefresh() {
    startRefresh(RefreshType.refresh).then((value) => update());
  }

  /// 加载更多
  onLoading() {
    startRefresh(RefreshType.loadMore).then((value) => update());
  }
 
  @override
  Future<PagingData<Post>> get refreshRequest => get('post/', (data) => PagingData.fromJson(data, Post.fromJson),
      query: {'page': paging.current.toString(), 'start_id': items.isNotEmpty ? items.first.id.toString() : ''});
}

重写我们的refreshRequest这样一个请求就处理好了

4.小结

我们在使用一些优秀的三方组件的时候可以多了解了解,有时间的话。看下其实现的思想,对于刷新的逻辑基本就是那么一套,我们可以根据自己开发经验,抽出来做成公共类