最近学了一下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往上再滑动一小段距离,这样用户永远无法滑到最底端。完美解决这个问题,滑动距离可以根据需要自己调整。