material实际上自带有下拉刷新控件,不过既然是学习就自己实现一个,这里是实现一个IOS中比较常见的tableviewHeader式的下拉刷新控件 。效果类似于MJNormalHeader。
MJHEADER
先踩个坑
在做下拉刷新的时候,我首先是利用滚动控件拉出滚动范围时,利用offset做处理。 也实现了,在IOS端listview有弹簧效果,因此会被拉出负的offset,但是在安卓端运行时弹簧效果却会被系统的下拉特效覆盖掉 。
因此在IOS端运行很好的下拉事件却在安卓端无法完成功能。
安卓下拉
Ios下拉
接下来实现下拉刷新
既然无法使用弹簧效果来实现,那么换一个思路,在listview的头部添加一个padding占位。这样就只需要改动padding的高度就可以实现将listview拉出滚动范围的效果了。
- 先用自定义statefulWidget包装一下listview,并在顶部合适的位置放一个指示下拉刷新的text,设置两个属性分别用来给内部的listview传递元素数量和build,封装一个方法,用以调起刷新事件,并将结束刷新的事件传递出去
typedef headerRefersh = void Function(Function stopRef);
class RefershScroller extends StatefulWidget{
IndexedWidgetBuilder builder;
int count;
bool refershing = false;
//下拉刷新回调
headerRefersh ref;
@override
RefershScroller({Key key,this.builder,this.ref,this.count});
.......
- 界面布局 用一个属性记录一下顶部的偏移量作为下拉的效果使用,并用Listener用来监听触摸事件。
class _RefershScrollerState extends State<RefershScroller>{
//滚动控制器
ScrollController controller = new ScrollController(keepScrollOffset: false,initialScrollOffset: 0,);
//刷新标记
String loadingStr = '最后刷新时间:';
String time ='--:--';
//顶部偏移量
double topMargin = 0;
//开始下拉刷新的标记
bool onTop = false;
......
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Stack(
children: <Widget>[
//下拉控件的提示信息
Positioned(top: 20,child:Text(loadingStr + time,style: TextStyle(color: SkinConfig().titleTextColor()),textAlign: TextAlign.center,),),
Container(padding: EdgeInsets.fromLTRB(0, topMargin, 0, 0),
//顶部间隔
//包裹触摸事件监听
child:Listener(
onPointerUp:onPointUp ,
onPointerMove: onPointMove,
child: new ListView.builder(
itemCount: widget.count,
itemBuilder: widget.builder,
controller: controller,
)
),
)
],
alignment: Alignment.center,
);
}
完成业务逻辑:
- 利用滚动控制器,我们可以监听到listview滚动到顶部的事件,在这个事件中,记录下控件到达顶部这一状态。
@override
void initState() {
// TODO: implement initState
controller..addListener((){
if(controller.offset == 0){
onTop = true;
}
if(controller.offset > 0){
onTop = false;
}
});
super.initState();
}
- 利用触摸事件获取手指在Y轴方向的移动数据delta.dy,在到达顶部这一状态为true的时候将delta.dy加到padding的高度上。
//触摸移动事件
void onPointMove(PointerMoveEvent event){
//listview到达顶部时记录标记
if(controller.offset == 0){
this.onTop = true;
}
if(onTop){
//到达顶部且继续下拉时将下拉的高度加到padding并更新状态
if(this.topMargin + event.delta.dy > 0){
this.topMargin = this.topMargin + event.delta.dy;
}
setState(() {
});
}
}
- 在触摸结束事件中开始刷新事件
void onPointUp(PointerUpEvent event){
//判断拉到一定高度
if(this.topMargin > 60 ){
//判断当前是否已经在刷新状态
if( widget.refershing == false){
widget.refershing = true;
//回调刷新事件并将停止刷新的方法传递出去
widget.ref(stopRefersh);
// 让控件保持在刷新状态的高度
controller.jumpTo(60 - this.topMargin);
this.topMargin = 60;
setState(() {
});
}
}else if(this.topMargin > 0){
//如果没有拉到足够高度则回到原位
double current = this.topMargin;
this.topMargin = 0;
controller.jumpTo(-current);
setState(() {
});
}
}
你可能注意到了 在这里我是先让listview跳到一个-的offset , controller.jumpTo(60 - this.topMargin);
再让顶部保持高度
this.topMargin = 60;
这么做的目的是平滑动画
实际上android中也是有拉出边缘的弹簧效果的 这个效果由
listview的shrinkWrap这个字段控制.它的默认值是false,如果将这个字设置为true则不管在哪个平台就都没有弹簧效果了
因此虽然拉出范围在android中被屏蔽了,仍然可以利用jumpTo跳到一个-的offset,控件就会自然的"收缩"回到offset=0的位置。这样只要算一下当前位置和需要跳到位置的差值jump过去,listview就会自己平滑的滚动到目标位置了。
- 外部的回调
void stopRefersh(){
widget.refershing = false;
setState(() {
time = TimeOfDay.now().hour.toString() + ":" + TimeOfDay.now().minute.toString();
this.topMargin = 0;
controller.jumpTo(-60);
});
}
同上 先把顶部的间隔归零 然后jumpTo(-60);listview就会自然地回到顶部了
在这里插入图片描述
对比一下两边的效果
看看使用
在这里插入图片描述自己试试吧!
网友评论