33Flutter flutter_easyrefresh三方下拉刷新框架

Flutter 三方下拉刷新框架

1. flutter_easyrefresh下拉框架

能够自定义酷炫的Header和Footer,也就是上拉和下拉的效果。
更新及时,不断在完善,录课截至时已经是v1.2.7版本了。
有一个辅导群,虽然文档不太完善,但是有辅导群和详细的案例。
回掉方法简单,这个具体可以看下面的例子。

2.使用方式

1.在 pubspec.yaml 中添加依赖
//pub方式
dependencies:
  flutter_easyrefresh: version

//导入方式
dependencies:
  flutter_easyrefresh:
    path: 项目路径

//git方式
dependencies:
  flutter_easyrefresh:
    git:
      url: git://github.com/xuelongqy/flutter_easyrefresh.git
2.在布局文件中添加 EasyreFresh
import 'package:flutter_easyrefresh/easy_refresh.dart';
....
  // 方式一
  EasyRefresh(
    child: ScrollView(),
    onRefresh: () async{
      ....
    },
    onLoad: () async {
      ....
    },
  )
  // 方式二
  EasyRefresh.custom(
    slivers: <Widget>[],
    onRefresh: () async{
      ....
    },
    onLoad: () async {
      ....
    },
  )
  // 方式三
  EasyRefresh.builder(
    builder: (context, physics, header, footer) {
      return CustomScrollView(
        physics: physics,
        slivers: <Widget>[
          ...
          header,
          ...
          footer,
        ],
      );
    }
    onRefresh: () async{
      ....
    },
    onLoad: () async {
      ....
    },
  )
3.触发刷新和加载动作
 EasyRefreshController _controller = EasyRefreshController();
  ....
  EasyRefresh(
    controller: _controller,
    ....
  );
  ....
  _controller.callRefresh();
  _controller.callLoad();
4.控制加载和刷新完成
  EasyRefreshController _controller = EasyRefreshController();
  ....
  EasyRefresh(
	enableControlFinishRefresh: true,
	enableControlFinishLoad: true,
    ....
  );
  ....
  _controller.finishRefresh(success: true);
  _controller.finishLoad(success: true, noMore: false);
5使用指定的 Header 和 Footer
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:flutter_easyrefresh/material_header.dart';
import 'package:flutter_easyrefresh/material_footer.dart';
....
  new EasyRefresh(
    header: MaterialHeader(),
    footer: MaterialFooter(),
    child: ScrollView(),
    ....
  )
6.添加国际化支持
不提供自带国际化支持,请自行设置ClassicalHeader和ClassicalFooter中需要展示的文字。

3细致讲解属性用法

1.ClassicalHeader
属性名称属性描述参数类型默认值要求
extentHeader的高度double60.0可选
triggerDistance触发刷新的距离double70.0可选
float是否浮动boolfalse可选
completeDuration完成延时Durationnull可选
enableInfiniteRefresh是否开启无限刷新boolfalse可选
enableHapticFeedback是否开启震动反馈boolfalse可选
headerBuilderHeader构造器RefreshControlBuildernull必需
 /// Key
  final Key? key;

  /// 方位
  final AlignmentGeometry? alignment;

  /// 提示刷新文字
  final String? refreshText;

  /// 准备刷新文字
  final String? refreshReadyText;

  /// 正在刷新文字
  final String? refreshingText;

  /// 刷新完成文字
  final String? refreshedText;

  /// 刷新失败文字
  final String? refreshFailedText;

  /// 没有更多文字
  final String? noMoreText;

  /// 显示额外信息(默认为时间)
  final bool showInfo;

  /// 更多信息
  final String? infoText;

  /// 背景颜色
  final Color bgColor;

  /// 字体颜色
  final Color textColor;

  /// 更多信息文字颜色
  final Color infoColor;
2. ClassicalFooter
属性名称属性描述参数类型默认值要求
extentFooter的高度double60.0可选
triggerDistance触发加载的距离double70.0可选
completeDuration完成延时Durationnull可选
enableInfiniteLoad是否开启无限加载boolfalse可选
enableHapticFeedback是否开启震动反馈boolfalse可选
footerBuilderFooter构造器LoadControlBuildernull必需
3.EasyRefreshController
要求
callRefresh触发刷新void Function({Duration duration})Duration duration = const Duration(milliseconds: 300)可选
callLoad触发加载void Function({Duration duration})Duration duration = const Duration(milliseconds: 300)可选
finishRefresh完成刷新void Function({{bool success,bool noMore,}})success = true, noMore = false可选
finishLoad完成加载void Function({Duration duration})success = true, noMore = false可选
resetRefreshState重置刷新状态void Function()void可选
resetLoadState重置加载状态void Function()void可选
4.EasyRefresh
属性名称属性描述参数类型默认值要求
keyEasyRefresh的键GlobalKeynull可选
controllerEasyRefresh控制器EasyRefreshControllernull可选
onRefresh刷新回调(为null时关闭刷新)Future Function()null可选
onLoad加载回调(为null时关闭加载)Future Function()null可选
enableControlFinishRefresh是否开启控制结束刷新boolfalse可选
enableControlFinishLoad是否开启控制结束加载boolfalse可选
taskIndependence任务独立(刷新和加载状态独立)boolfalse可选
defaultHeader全局默认Header样式static HeaderClassicalHeader可选
defaultFooter全局默认Footer样式static FooterClassicalFooter可选
headerHeader样式Header_defaultHeader可选
footerFooter样式Footer_defaultFooter可选
builder子组件构造器EasyRefreshChildBuildernullEasyRefresh.builder必需
child子组件WidgetnullEasyRefresh必需
sliversSlivers集合ListnullEasyRefresh.custom必需
firstRefresh首次刷新boolfalse可选
firstRefreshWidget首次刷新组件(为null时使用Header)Widgetnull可选
emptyWidget空视图(当不为null时,只会显示空视图)emptyWidgetnull可选
topBouncing顶部回弹(onRefresh为null时生效)booltrue可选
bottomBouncing底部回弹(onLoad为null时生效)booltrue可选
scrollController滚动控制器ScrollControllernull可选
behavior滚动行为BehaviorEmptyOverScrollScrollBehavior可选
其他参数与CustomScrollView一致与CustomScrollView参数一致null可选(EasyRefresh.custom)

—————— enableControlFinishRefresh

——————enableControlFinishLoad

——————controller EasyRefreshController类型

——————scrollController 滚动控制器 ScrollController null 可选

——————header

——————footer

——————onRefresh

——————onLoad

——————taskIndependence一般不独立,刷新的时候,不能加载

4.使用

2.方式2
 // 方式一
  EasyRefresh(
    child: ScrollView(),
    onRefresh: () async{
      ....
    },
    onLoad: () async {
      ....
    },
  )
EasyRefresh.custom(
            ///EasyRefresh.custom 构造器 内部继承CustomScrollow 必须配合slivers 使用
            controller: _controller,
            emptyWidget: _count == 0 ? _emptyViewWidget() : null,
            header: BallPulseHeader(),
            footer: BallPulseFooter(),
            onRefresh: () async {  
              await Future.delayed(Duration(seconds: 2), () { //模拟网络请求
                setState(() {
                  _count = 10;
                  onRefreshData();
                });
                _controller.resetLoadState();
              });
            },
            onLoad: () async {
              await Future.delayed(Duration(seconds: 2), () {//模拟网络请求
                setState(() {
                  _count += 10;
                  onLoadData();
                });
                _controller.finishLoad(noMore: _count >= 20);
              });
            },
            slivers: <Widget>[
              SliverList(delegate: SliverChildBuilderDelegate(
                    (context, index) {
                  return listItemBuilder(context, index);
                },
                childCount: listDeat.length,
              ),
              ),
            ],
          ),
3方式3
 // 方式三
  EasyRefresh.builder(
    builder: (context, physics, header, footer) {
      return CustomScrollView(
        physics: physics,
        slivers: <Widget>[
          ...
          header,
          ...
          footer,
        ],
      );
    }
    onRefresh: () async{
      ....
    },
    onLoad: () async {
      ....
    },
  )
import 'dart:async';

import 'package:example/widget/sample_list_item.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:flutter_swiper_null_safety/flutter_swiper_null_safety.dart';

/// Swiper示例
class SwiperPage extends StatefulWidget {
  @override
  SwiperPageState createState() {
    return SwiperPageState();
  }
}

class SwiperPageState extends State<SwiperPage> {
  // 条目总数
  int _count = 20;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: EasyRefresh.builder(
        builder: (context, physics, header, footer) {
          return CustomScrollView(
            physics: physics,
            slivers: <Widget>[
              SliverAppBar(
                expandedHeight: 100.0,
                pinned: true,
                backgroundColor: Colors.white,
                flexibleSpace: FlexibleSpaceBar(
                  centerTitle: false,
                  title: Text('Swiper'),
                ),
              ),
              header!,
              SliverList(
                delegate: SliverChildListDelegate([
                  Container(
                    height: 210.0,
                    child: ScrollNotificationInterceptor(
                      child: Swiper(
                        itemBuilder: (BuildContext context, int index) {
                          return SampleListItem(direction: Axis.horizontal);
                        },
                        itemCount: 5,
                        viewportFraction: 0.8,
                        scale: 0.9,
                        autoplay: true,
                      ),
                    ),
                  ),
                ]),
              ),
              SliverList(
                delegate: SliverChildBuilderDelegate((context, index) {
                  return SampleListItem();
                }, childCount: _count),
              ),
              footer!,
            ],
          );
        },
        onRefresh: () async {
          await Future.delayed(Duration(seconds: 2), () {
            if (mounted) {
              setState(() {
                _count = 20;
              });
            }
          });
        },
        onLoad: () async {
          await Future.delayed(Duration(seconds: 2), () {
            if (mounted) {
              setState(() {
                _count += 20;
              });
            }
          });
        },
      ),
    );
  }
}

5.其他类型

1.首次刷新
firstRefresh首次刷新boolfalse可选
firstRefreshWidget首次刷新组件(为null时使用Header)Widgetnull可选
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'SampleListItem.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // home: CustomScrollViewDemo(),
      home: Scaffold(
        body: FirstRefreshPage(),
      ),
    );
  }
}


/// 首次刷新示例
class FirstRefreshPage extends StatefulWidget {
  @override
  FirstRefreshPageState createState() {
    return FirstRefreshPageState();
  }
}

class FirstRefreshPageState extends State<FirstRefreshPage> {
  // 总数
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("第一次刷新"),
        backgroundColor: Colors.white,
      ),
      body: EasyRefresh.custom(
        firstRefresh: true,
        firstRefreshWidget: Container(
          width: double.infinity,
          height: double.infinity,
          child: Center(
              child: SizedBox(
                height: 200.0,
                width: 300.0,
                child: Card(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: <Widget>[
                      Container(
                        width: 50.0,
                        height: 50.0,
                        child: SpinKitFadingCube(
                          color: Theme.of(context).primaryColor,
                          size: 25.0,
                        ),
                      ),
                      Container(
                        child: Text("加载中"),
                      )
                    ],
                  ),
                ),
              )),
        ),
        slivers: <Widget>[
          SliverList(
            delegate: SliverChildBuilderDelegate(
                  (context, index) {
                return SampleListItem();
              },
              childCount: _count,
            ),
          ),
        ],
        onRefresh: () async {
          await Future.delayed(Duration(seconds: 2), () {
            if (mounted) {
              setState(() {
                _count = 20;
              });
            }
          });
        },
        onLoad: () async {
          await Future.delayed(Duration(seconds: 2), () {
            if (mounted) {
              setState(() {
                _count += 20;
              });
            }
          });
        },
      ),
    );
  }
}
2.空视图
        emptyWidget: _count == 0
            ? Container(
                height: double.infinity,
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: <Widget>[
                    Expanded(
                      child: SizedBox(),
                      flex: 2,
                    ),
                    SizedBox(
                      width: 100.0,
                      height: 100.0,
                      child: Image.asset('assets/image/nodata.png'),
                    ),
                    Text(
                      S.of(context).noData,
                      style: TextStyle(fontSize: 16.0, color: Colors.grey[400]),
                    ),
                    Expanded(
                      child: SizedBox(),
                      flex: 3,
                    ),
                  ],
                ),
              )
            : null,
3二楼

主要是更具高度计算,滑动屏幕

4.聊天页面 上拉加载,下拉刷新,模拟聊天

原理:首先根据LayoutBuilder确定显示的空间,然后计算每条消息的高度,如果总和消息超过最大高度,则使用ListView,如果没有超过,使用SliverToBoxAdapter循环遍历消息

reverse: true,
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:flutter_first/day36easyRefresh/FirstRefreshPageState.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';


/// 聊天界面示例
class ChatPage extends StatefulWidget {
  @override
  ChatPageState createState() {
    return ChatPageState();
  }
}

class ChatPageState extends State<ChatPage> {
  // 信息列表
  List<MessageEntity> _msgList;

  // 输入框
  TextEditingController _textEditingController;

  // 滚动控制器
  ScrollController _scrollController;

  @override
  void initState() {
    super.initState();
    _msgList = [
      MessageEntity(true, "It's good!"),
      MessageEntity(false, 'EasyRefresh'),
    ];
    _textEditingController = TextEditingController();
    _textEditingController.addListener(() {
      setState(() {});
    });
    _scrollController = ScrollController();
  }

  @override
  void dispose() {
    super.dispose();
    _textEditingController.dispose();
    _scrollController.dispose();
  }

  // 发送消息
  void _sendMsg(String msg) {
    setState(() {
      _msgList.insert(0, MessageEntity(true, msg));
    });
    _scrollController.animateTo(0.0,
        duration: Duration(milliseconds: 300), curve: Curves.linear);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('KnoYo'),
        centerTitle: false,
        backgroundColor: Colors.grey[200],
        elevation: 0.0,
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.more_horiz),
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (BuildContext context) {
                    return FirstRefreshPage();
                  },
                ),
              );
            },
          ),
        ],
      ),
      backgroundColor: Colors.grey[200],
      body: Column(
        children: <Widget>[
          Divider(
            height: 0.5,
          ),
          Expanded(
            flex: 1,
            child: LayoutBuilder(
              builder: (context, constraints) {
                // 判断列表内容是否大于展示区域
                bool overflow = false;
                double heightTmp = 0.0;
                for (MessageEntity entity in _msgList) {
                  heightTmp +=
                      _calculateMsgHeight(context, constraints, entity);
                  if (heightTmp > constraints.maxHeight) {
                    overflow = true;
                  }
                }
                return EasyRefresh.custom(
                  scrollController: _scrollController,
                  reverse: true,
                  footer: CustomFooter(
                      enableInfiniteLoad: false,
                      extent: 40.0,
                      triggerDistance: 50.0,
                      footerBuilder: (context,
                          loadState,
                          pulledExtent,
                          loadTriggerPullDistance,
                          loadIndicatorExtent,
                          axisDirection,
                          float,
                          completeDuration,
                          enableInfiniteLoad,
                          success,
                          noMore) {
                        return Stack(
                          children: <Widget>[
                            Positioned(
                              bottom: 0.0,
                              left: 0.0,
                              right: 0.0,
                              child: Container(
                                width: 30.0,
                                height: 30.0,
                                child: SpinKitCircle(
                                  color: Colors.green,
                                  size: 30.0,
                                ),
                              ),
                            ),
                          ],
                        );
                      }),
                  slivers: <Widget>[
                    if (overflow)
                      SliverList(
                        delegate: SliverChildBuilderDelegate(
                          (context, index) {
                            return _buildMsg(_msgList[index]);
                          },
                          childCount: _msgList.length,
                        ),
                      ),
                    if (!overflow)
                      SliverToBoxAdapter(
                        child: Container(
                          height: constraints.maxHeight,
                          width: double.infinity,
                          child: Column(
                            children: <Widget>[
                              for (MessageEntity entity in _msgList.reversed)
                                _buildMsg(entity),
                            ],
                          ),
                        ),
                      ),
                  ],
                  onLoad: () async {
                    await Future.delayed(Duration(seconds: 2), () {
                      if (mounted) {
                        setState(() {
                          _msgList.addAll([
                            MessageEntity(true, "It's good!"),
                            MessageEntity(false, 'EasyRefresh'),
                          ]);
                        });
                      }
                    });
                  },
                );
              },
            ),
          ),
          SafeArea(
            child: Container(
              color: Colors.grey[100],
              padding: EdgeInsets.only(
                left: 15.0,
                right: 15.0,
                top: 10.0,
                bottom: 10.0,
              ),
              child: Row(
                children: <Widget>[
                  Expanded(
                    flex: 1,
                    child: Container(
                      padding: EdgeInsets.only(
                        left: 5.0,
                        right: 5.0,
                        top: 10.0,
                        bottom: 10.0,
                      ),
                      decoration: BoxDecoration(
                        color: Colors.white,
                        borderRadius: BorderRadius.all(Radius.circular(
                          4.0,
                        )),
                      ),
                      child: TextField(
                        controller: _textEditingController,
                        decoration: null,
                        onSubmitted: (value) {
                          if (_textEditingController.text.isNotEmpty) {
                            _sendMsg(_textEditingController.text);
                            _textEditingController.text = '';
                          }
                        },
                      ),
                    ),
                  ),
                  InkWell(
                    onTap: () {
                      if (_textEditingController.text.isNotEmpty) {
                        _sendMsg(_textEditingController.text);
                        _textEditingController.text = '';
                      }
                    },
                    child: Container(
                      height: 30.0,
                      width: 60.0,
                      alignment: Alignment.center,
                      margin: EdgeInsets.only(
                        left: 15.0,
                      ),
                      decoration: BoxDecoration(
                        color: _textEditingController.text.isEmpty
                            ? Colors.grey
                            : Colors.green,
                        borderRadius: BorderRadius.all(Radius.circular(
                          4.0,
                        )),
                      ),
                      child: Text(
                        '发送',
                        style: TextStyle(
                          color: Colors.white,
                          fontSize: 16.0,
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  // 构建消息视图
  Widget _buildMsg(MessageEntity entity) {
    if (entity.own) {
      return Container(
        margin: EdgeInsets.all(
          10.0,
        ),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.end,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Column(
              crossAxisAlignment: CrossAxisAlignment.end,
              children: <Widget>[
                Text(
                  '我',
                  style: TextStyle(
                    color: Colors.grey,
                    fontSize: 13.0,
                  ),
                ),
                Container(
                  margin: EdgeInsets.only(
                    top: 5.0,
                  ),
                  padding: EdgeInsets.all(10.0),
                  decoration: BoxDecoration(
                    color: Colors.lightGreen,
                    borderRadius: BorderRadius.all(Radius.circular(
                      4.0,
                    )),
                  ),
                  constraints: BoxConstraints(
                    maxWidth: 200.0,
                  ),
                  child: Text(
                    entity.msg,
                    overflow: TextOverflow.clip,
                    style: TextStyle(
                      fontSize: 16.0,
                    ),
                  ),
                )
              ],
            ),
            Card(
              margin: EdgeInsets.only(
                left: 10.0,
              ),
              clipBehavior: Clip.hardEdge,
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.all(Radius.circular(20.0)),
              ),
              elevation: 0.0,
              child: Container(
                height: 40.0,
                width: 40.0,
                child: Image.asset('assets/image/head.jpg'),
              ),
            ),
          ],
        ),
      );
    } else {
      return Container(
        margin: EdgeInsets.all(
          10.0,
        ),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Card(
              margin: EdgeInsets.only(
                right: 10.0,
              ),
              clipBehavior: Clip.hardEdge,
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.all(Radius.circular(20.0)),
              ),
              elevation: 0.0,
              child: Container(
                height: 40.0,
                width: 40.0,
                child: Image.asset('assets/image/head_knoyo.jpg'),
              ),
            ),
            Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Text(
                  'KnoYo',
                  style: TextStyle(
                    color: Colors.grey,
                    fontSize: 13.0,
                  ),
                ),
                Container(
                  margin: EdgeInsets.only(
                    top: 5.0,
                  ),
                  padding: EdgeInsets.all(10.0),
                  decoration: BoxDecoration(
                    color: Colors.white,
                    borderRadius: BorderRadius.all(Radius.circular(
                      4.0,
                    )),
                  ),
                  constraints: BoxConstraints(
                    maxWidth: 200.0,
                  ),
                  child: Text(
                    entity.msg,
                    overflow: TextOverflow.clip,
                    style: TextStyle(
                      fontSize: 16.0,
                    ),
                  ),
                )
              ],
            ),
          ],
        ),
      );
    }
  }

  // 计算内容的高度
  double _calculateMsgHeight(
      BuildContext context, BoxConstraints constraints, MessageEntity entity) {
    return 45.0 +
        _calculateTextHeight(
          context,
          constraints,
          text: '我',
          textStyle: TextStyle(
            fontSize: 13.0,
          ),
        ) +
        _calculateTextHeight(
          context,
          constraints.copyWith(
            maxWidth: 200.0,
          ),
          text: entity.msg,
          textStyle: TextStyle(
            fontSize: 16.0,
          ),
        );
  }

  /// 计算Text的高度
  double _calculateTextHeight(
    BuildContext context,
    BoxConstraints constraints, {
    String text = '',
    @required
    TextStyle textStyle,
    List<InlineSpan> children = const [],
  }) {
    final span = TextSpan(text: text, style: textStyle, children: children);

    final richTextWidget = Text.rich(span).build(context) as RichText;
    final renderObject = richTextWidget.createRenderObject(context);
    renderObject.layout(constraints);
    return renderObject.computeMinIntrinsicHeight(constraints.maxWidth);
  }
}

/// 信息实体
class MessageEntity {
  bool own;
  String msg;

  MessageEntity(this.own, this.msg);
}

6样式

————————可以自行通过自定义实现,

1.经典样式
2.Material样式
3.球脉冲样式BallPulse
        header: BezierCircleHeader(),
        footer: BezierBounceFooter(),
4.贝塞尔圆圈样式
        header: BezierHourGlassHeader(
          color: Theme.of(context).scaffoldBackgroundColor,
        ),
        footer: BezierBounceFooter(
          color: Theme.of(context).scaffoldBackgroundColor,
        ),
5.快递
        header: DeliveryHeader(
          backgroundColor: Colors.grey[100],
        ),
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值