最近学了一下flutter,下班后仿着现在做的app用flutter做了主页的几个新闻数据,用到刷新加载的时候,想着自己手动实现一下。
实现方法为:官方refresh+ScrollController+GestureDetector。
用了官方的pull to refresh ,不过这个东西限制条件很多,连基本的功能都无法完全实现,限制归限制,该用的时候还是得用它的,不然自己完全实现起来有点费劲。
官方的下拉刷新,需要用RefreshIndicator来实现,代码如下:
@override
Widget build(BuildContext context) {
super.build(context); // See AutomaticKeepAliveClientMixin.
return new RefreshIndicator(child: new ListView(
controller: _scrollController,
children: <Widget>[
new MyBanner(images: _images),
Container(
height: 55,
child: Row(
children: <Widget>[
Expanded(
child: new Text(""),
flex: 1,
),
Expanded(
child: new GestureDetector(onTap: () {
Fluttertoast.showToast(
msg: "您没有权限",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIos: 1,
textColor: Colors.black
);
}, child: new Row(
children: <Widget>[
new Icon(Icons.autorenew),
new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text("title1"),
new Text("subtitle1"),
],
)
],
),),
flex: 20,
),
Expanded(
child: new Text(""),
flex: 1,
),
Expanded(
child: new GestureDetector(onTap: () {
Fluttertoast.showToast(
msg: "敬请期待",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIos: 1,
textColor: Colors.black
);
}, child: new Row(
children: <Widget>[
new Icon(Icons.autorenew),
new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text("title2"),
new Text("subtitle2"),
],
)
],
),),
flex: 20,
)
],
),
),
new Container(child: new Text("新闻", style: _biggerFont),
margin: const EdgeInsets.only(left: 20),)
,
new NewsList(menu_code: news_menu_code)
],
),
onRefresh: getListData)
);
}
emmm, csdn都还没有支持flutter的代码插入,令人非常难过。
主要代码为
new RefreshIndicator(child:new ListView(),onRefresh:getListdata)
但是官方的刷新有一个问题,那就是在整个listview?页面 不足一页的时候,它的刷新是用不了的,即使下拉也触发不了刷新。
这时候就需要我们的GestureDetector登场了,使用GestureDetector来做手势处理,代码如下:
double startPointy, startPointx;//手势起点的y 和 x
bool isAnimShowing = false;//用来解决手势处理中多次触发的问题
OverlayEntry moverlayEntry;
bool isLoading = false;//用来预防万一多次刷新加载
@override
Widget build(BuildContext context) {
super.build(context); // See AutomaticKeepAliveClientMixin.
return new GestureDetector(
onVerticalDragUpdate: (dragDetails) {
if (startPointy-dragDetails.globalPosition.dy > 5 &&//获取底部数据 loadmore
!isAnimShowing &&
dragDetails.globalPosition.dx - startPointx < 5) {
//todo 动画开始
isAnimShowing = true;
if(!isLoading){
moverlayEntry = creatOverlay(true);
isLoading=true;
Overlay.of(context).insert(moverlayEntry);
}
getMoreData();
}
if(dragDetails.globalPosition.dy-startPointy > 5 &&
!isAnimShowing &&
dragDetails.globalPosition.dx - startPointx < 5){
isAnimShowing = true;
if(!isLoading){
moverlayEntry = creatOverlay(false);
isLoading=true;
Overlay.of(context).insert(moverlayEntry);
}
getListData();//refresh
}
print("" + dragDetails.globalPosition.dy.toString());
},
onVerticalDragStart: (startDetails) {
isDragging = true;
startPointy = startDetails.globalPosition.dy;
startPointx = startDetails.globalPosition.dx;
},
onVerticalDragEnd: (endDetails) {
isDragging = false;
isAnimShowing = false;
print("---------onVerticalDragEnd--------");
},
child: new Container(
padding: EdgeInsets.all(0),
child: new RefreshIndicator(child: new ListView(
controller: _scrollController,
children: <Widget>[
new MyBanner(images: _images),
Container(
height: 55,
child: Row(
children: <Widget>[
Expanded(
child: new Text(""),
flex: 1,
),
Expanded(
child: new GestureDetector(onTap: () {
Fluttertoast.showToast(
msg: "您没有权限",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIos: 1,
textColor: Colors.black
);
}, child: new Row(
children: <Widget>[
new Icon(Icons.autorenew),
new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text("title1"),
new Text("subtitle1"),
],
)
],
),),
flex: 20,
),
Expanded(
child: new Text(""),
flex: 1,
),
Expanded(
child: new GestureDetector(onTap: () {
Fluttertoast.showToast(
msg: "敬请期待",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIos: 1,
textColor: Colors.black
);
}, child: new Row(
children: <Widget>[
new Icon(Icons.autorenew),
new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text("title2"),
new Text("subtitle2"),
],
)
],
),),
flex: 20,
)
],
),
),
new Container(child: new Text("新闻", style: _biggerFont),
margin: const EdgeInsets.only(left: 20),)
,
new NewsList(menu_code: news_menu_code)
],
),
onRefresh: getListData)
)
)
;
}
这段代码的主要功能呢,就是为了处理上拉和下拉的手势(>5和<5仅仅是为了实现功能,如果需要精准判断手势提高体验的话还请自行设定),这段代码应该还是比较好懂的。
不过经过测试,GestureDetector仅仅能处理列表不能滑动的时候的事件,就是列表长度很短,短于屏幕,如果要处理超出屏幕的列表的坐标的话,就需要ScrollController来上场了。
至此,对于能滑动的下拉刷新--官方refresh,不能滑动的上拉下拉--GestureDetector,能滑动的上拉加载ScrollController,这三个组合完美实现上拉下拉功能。
ScrollController这个的实现也比较简单。
ScrollController _scrollController = new ScrollController();
@override
void initState() {
super.initState();
// getListData();
_scrollController.addListener(() {
print('滑动到了最底部' + _scrollController.position.maxScrollExtent.toString());
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
print('滑动到了最底部');
if(!isLoading){
moverlayEntry=creatOverlay(true);
isLoading=true;
Overlay.of(context).insert(moverlayEntry);
getMoreData();
}
}
});
}
OverlayEntry creatOverlay(bool isBottom) {
double heightPercent=0.2;
if(isBottom){
heightPercent=0.7;
}
OverlayEntry overlayEntry = new OverlayEntry(builder: (context) {
return new Positioned(
top: MediaQuery
.of(context)
.size
.height * heightPercent,
child: new Material(
color: Colors.transparent,
child: new Container(
width: MediaQuery
.of(context)
.size
.width,
alignment: Alignment.center,
child: new Center(
child: new Card(
child: new Padding(
padding: EdgeInsets.all(5.0),
child: new Column(
children: <Widget>[
SizedBox(
height: 40,
width: 40,
child: CircularProgressIndicator(
backgroundColor: Colors.red,
),
),
new Text("loading")
],
),
),
),
),
)
));
});
return overlayEntry;
}
通过
_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent
来判断是否滑动到底部了,这里也贴出了来一个简易的"圈圈"吧,当然这个跟官方的出入很大,我也是今晚上才实现出来功能,对界面没有处理。如果有大佬实现一个自定义的CircularProgressIndicator,请一定通知我!!!
至此,上拉加载下拉刷新的功能基本已经实现了,不过还有一个小问题,就是可滑动的列表如果在最底端的时候,ScrollController也获取不到滑动的数据,如果谁有好的方案的话可以交流一下。
审核那么慢,顺遍把这个bug解决了,就是每次滑到最低端的时候,让它往上滑一点点,
_scrollController.addListener(() {
print('滑动到了最底部' + _scrollController.position.maxScrollExtent.toString()+_scrollController.keepScrollOffset.toString());
if (_scrollController.position.maxScrollExtent==_scrollController.position.pixels
) {
print('滑动到了最底部');
if(!isLoading){
double _position = _scrollController.position.maxScrollExtent-10;
_scrollController.animateTo(_position, duration: Duration(seconds: 1),
curve:Curves.ease );
isLoading=true;
moverlayEntry=creatOverlay(true);
Overlay.of(context).insert(moverlayEntry);
getMoreData();
}
// getListData();
}
});
在确定loadmore之后,调用_scrollController.animateTo方法,让listview往上再滑动一小段距离,这样用户永远无法滑到最底端。完美解决这个问题,滑动距离可以根据需要自己调整。