先附上连接
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), ); } }