先附上连接
https://github.com/dikeboy/flutter-refrensh
这里涉及到 flutter 中的 几块 动画 , 事件点击, 异步,
要自定义下拉刷新 首先必须要了解 Flutter 中的事件监听方法
https://flutter.io/docs/development/ui/advanced/gestures
flutter 的手势主要是两个类 listener 和 GestureDecetor
Listener 主要类似 touchevent 包括 按下, 移动, 松开 取消 (划出屏幕)
GestureDecetor 主要就是一些手势形成效果 点击 双击 长按之类的
我这里是写在基类里, 因为调用到修改界面 当然你也可以改成 Mixin
因为我这里是对 ListView 进行处理 也就是通过修改 ListView 的第一个 header 高度来实现下拉效果
- getRow(int position){
- if(position==0){
- return getRfrenshHeader();
- }
- else{
- return Text(list[position-1]);
- }
- }
这里 -1 是因为本身 header 的高度已经算一个 Item 了
首先看下 state 的 initState 方法
- @override
- initState(){
- super.initState();
- animationController = AnimationController(
- duration: const Duration(milliseconds: 300), vsync: this); // 定义了一个 300 毫秒的动画控制器
- animation = Tween(begin: 1.0, end: 0.0).animate(animationController); //begin end 也就是 300 毫秒时间内 aimation 的值从 1.0 到 0.0 变化
- animation.addListener(() {
- setState(() {
- // the animation object's value is the changed state
- headHeight = (len-headNormalHeight) *animation.value+headNormalHeight; // 从 touchUp 释放开始 header 偏移量从 1.0 到 0.0 移动 最终剩下 header 的高度
- });
- });
- }
Listener 的实例化
- typedef MStartRefrensh = void Function(); // 定义一个方法用户回调 类似接口回调吧
- getPullListener({Widget child, MStartRefrensh startRefrensh,double height}){
- this.pointerUpListener = startRefrensh;
- headNormalHeight =height; // 初始化 Header 的高度
- if(scrollController==null){
- scrollController = ScrollController(initialScrollOffset: height); // 初始化滚动条 flutter 中如果需要监听 列表滑动 都需要滚动条
- }
- Listener listener = new Listener( child: child, onPointerDown: pointDown,onPointerUp: pointUp,onPointerMove: pointMove,onPointerCancel: pointCancle,);// 这里也就是我们 4 个手势的监听方法
- if(!firstJump){
- setState(() {
- if(!firstJump&&scrollController.hasClients){ //hasClients 可以用来判断 scrollerController 是否已经绑定到 listview 上
- firstJump =true;
- scrollController.jumpTo(headNormalHeight); // 第一次加载的时候 我们直接跳到 Header 的高度 从而让 header 隐藏在屏幕上面
- }
- });
- }
- return listener;
- }
下面看下手势事件
- pointDown(PointerDownEvent event){ // 没没啥特别 记录下 touchdown 的坐标
- dx = event.position.dx;
- dy=event.position.dy;
- isTouchDown = true;
- }
- pointUp(PointerUpEvent event){ // 释放的时候 如果 head 大于我们初始化的 head 就说明需要刷新 启动一个回滚动画
- if(headHeight>headNormalHeight){
- startUpAnimation(headHeight);
- }
- else{
- setState((){
- headHeight=headNormalHeight; // 这里主要是重置 避免未知问题
- });
- }
- isTouchDown = false;
- }
- pointCancle(PointerCancelEvent event){ //cancel 跟 UP 逻辑一样 也可以自定义
- if(headHeight>headNormalHeight){
- startUpAnimation(headHeight);
- }
- else{
- setState((){
- headHeight=headNormalHeight;
- });
- }
- isTouchDown = false;
- }
- pointMove(PointerMoveEvent event){
- // print(_scrollController.position.pixels);
- setState(() {
- if(event.position.dy - dy>0)
- headHeight = (event.position.dy - dy)/2+headNormalHeight; // 这里我设置 header 高度是滑动距离的 1/2 实际效果有些也会有越滑越慢 可以根据开根号 或者 2 次方实现
- });
- }
下面是动画
- startUpAnimation(double len) {
- this.len = len;
- if (animationController.isCompleted) {
- animationController.reset(); // 动画结束后重置 以便下次接着用
- }
- animationController.forward().then((_) {
- headHeight = headNormalHeight;
- pointerUpListener(); // 这个是自定义的回调事件 也就是初始化的时候传入的
- //
- });
- }
刷新的 header
- // 这里就用了系统的滚动条 要重写下来动画啥的都可以修改这里
- getRfrenshHeader(){
- if(headHeight==headNormalHeight&&!isTouchDown){
- return Container(
- child: SizedBox(
- child: CircularProgressIndicator(valueColor: new AlwaysStoppedAnimation<Color>(Colors.orange)),
- height: 20.0,
- width: 20.0,
- ),
- alignment: Alignment.center,
- height: headHeight,
- );
- }
- else{
- return Container(
- child: SizedBox(
- child: Row(children: <Widget>[
- new Image.asset('images/drop_refrensh.png'),
- Text("释放刷新")
- ],
- mainAxisAlignment: MainAxisAlignment.center,),
- height: 20.0,
- ),
- alignment: Alignment.center,
- height: headHeight,
- );
- }
- }
刷新结束需要主动关闭
- finishLoading(){
- if(scrollController!=null&&scrollController.hasClients){
- scrollController.animateTo(
- headNormalHeight,
- curve: Curves.easeOut,
- duration: const Duration(milliseconds: 100),
- );
- }
- }
来源: http://www.bubuko.com/infodetail-2893930.html