33Flutter flutter_easyrefresh三方下拉刷新框架

  • Post author:
  • Post category:其他




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
属性名称 属性描述 参数类型 默认值 要求
extent Header的高度 double 60.0 可选
triggerDistance 触发刷新的距离 double 70.0 可选
float 是否浮动 bool false 可选
completeDuration 完成延时 Duration null 可选
enableInfiniteRefresh 是否开启无限刷新 bool false 可选
enableHapticFeedback 是否开启震动反馈 bool false 可选
headerBuilder Header构造器 RefreshControlBuilder null 必需
 /// 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
属性名称 属性描述 参数类型 默认值 要求
extent Footer的高度 double 60.0 可选
triggerDistance 触发加载的距离 double 70.0 可选
completeDuration 完成延时 Duration null 可选
enableInfiniteLoad 是否开启无限加载 bool false 可选
enableHapticFeedback 是否开启震动反馈 bool false 可选
footerBuilder Footer构造器 LoadControlBuilder null 必需


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
属性名称 属性描述 参数类型 默认值 要求
key EasyRefresh的键 GlobalKey null 可选
controller EasyRefresh控制器 EasyRefreshController null 可选
onRefresh 刷新回调(为null时关闭刷新) Future Function() null 可选
onLoad 加载回调(为null时关闭加载) Future Function() null 可选
enableControlFinishRefresh 是否开启控制结束刷新 bool false 可选
enableControlFinishLoad 是否开启控制结束加载 bool false 可选
taskIndependence 任务独立(刷新和加载状态独立) bool false 可选
defaultHeader 全局默认Header样式 static Header ClassicalHeader 可选
defaultFooter 全局默认Footer样式 static Footer ClassicalFooter 可选
header Header样式 Header _defaultHeader 可选
footer Footer样式 Footer _defaultFooter 可选
builder 子组件构造器 EasyRefreshChildBuilder null EasyRefresh.builder必需
child 子组件 Widget null EasyRefresh必需
slivers Slivers集合 List null EasyRefresh.custom必需
firstRefresh 首次刷新 bool false 可选
firstRefreshWidget 首次刷新组件(为null时使用Header) Widget null 可选
emptyWidget 空视图(当不为null时,只会显示空视图) emptyWidget null 可选
topBouncing 顶部回弹(onRefresh为null时生效) bool true 可选
bottomBouncing 底部回弹(onLoad为null时生效) bool true 可选
scrollController 滚动控制器 ScrollController null 可选
behavior 滚动行为 Behavior EmptyOverScrollScrollBehavior 可选
其他参数 与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 首次刷新 bool false 可选
firstRefreshWidget 首次刷新组件(为null时使用Header) Widget null 可选
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],
        ),



版权声明:本文为LJLThomson原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。