Flutter PullToRefresh上下拉刷新控件

Flutter爲我們提供了相當豐富的控件,但是有時候還是不能滿足大家的需求,官方的控件只有下拉刷新,沒有上拉加載,感覺很彆扭;我從安卓轉過來的,所以我做了一套和安卓原生的一樣效果的控件

首先請允許我介紹下我控件:理論上適配所有可滑動View, Android IOS 雙平臺通用. 上下拉分別可控, 可單獨使用上拉或下拉; 支持上下拉頭完全自定義,現已支持是否加載成功的狀態通知,支持通過方法調用觸發下拉刷新,重點:很少BUG!很少BUG!很少BUG! 看下效果圖吧

可能好多人只是想用這個控件,那我們先看看怎麼去使用,後面我們會講解代碼

第一步 添加下面的代碼到 pubspec.yaml  文件

dependencies:
  pulltorefresh_flutter: "^0.1.9"

第二步 如果使用本項目默認圖片,請下載 https://raw.githubusercontent.com/baoolong/PullToRefresh_Flutter/master/images/refresh.png 等圖片到你的images文件夾下,並在Pubspec.yaml添加如下配置

assets:
   - images/refresh.png

第三步 在你要使用的文件裏 導包

import 'package:pulltorefresh_flutter/pulltorefresh_flutter.dart';

第四步 寫你的業務代碼(以下爲示例代碼)

這裏我示例加載github首頁,由於我自定義了下拉頭的文字提示,所以就有 “快尼瑪給老子鬆手!”這個變量了,哈哈哈;要注意的是,有時ListView的Item太少而不能鋪滿屏幕,ListView 不能Scroll,導致PullToRefresh也不可使用,同時爲了兼容IOS,所以初始化ScrollPhysics必須是RefreshAlwaysScrollPhysics。所以要定義RefreshAlwaysScrollPhysics這個變量,別忘了定義這個變量哦!build方法裏面使用了上下拉控件:PullAndPush,並對各個參數進行了定義,下面是代碼

///PullAndPush可以使用默認的樣式,在此樣式的基礎上可以使用default**系列的屬性改變顯示效果,也可以自定義RefreshBox的樣式(footerRefreshBox or headerRefreshBox),也就是說可以定義其中一個,另一個用默認的,也可以全部自定義
///isPullEnable;isPushEnable屬性可以控制RefreshBox 是否可用,無論是自定義的還是默認的
///PullAndPush can use the default style,Based on this style, you can use the properties of the default** series to change the display,
///You can also customize the style of the RefreshBox (footerRefreshBox or headerRefreshBox), which means you can define one of them, and the other can be customized by default or all.
class PullAndPushTestState extends State<PullAndPushTest> with TickerProviderStateMixin{
  List<String> addStrs=["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"];
  List<String> strs=["1","2","3","4","5","6","7","8","9","0"];
  ScrollController controller=new ScrollController();
  //For compatibility with ios ,must use RefreshAlwaysScrollPhysics ;爲了兼容ios 必須使用RefreshAlwaysScrollPhysics
  ScrollPhysics scrollPhysics=new RefreshAlwaysScrollPhysics();
  //使用系統的請求
  var httpClient = new HttpClient();
  var url = "https://github.com/";
  var _result="";
  String customRefreshBoxIconPath="images/icon_arrow.png";
  AnimationController customBoxWaitAnimation;
  int rotationAngle=0;
  String customHeaderTipText="快尼瑪給老子鬆手!";
  String defaultRefreshBoxTipText="快尼瑪給老子鬆手!";
  ///button等其他方式,通過方法調用觸發下拉刷新
  TriggerPullController triggerPullController=new TriggerPullController();


  @override
  void initState() {
    super.initState();
    //這個是刷新時控件旋轉的動畫,用來使刷新的Icon動起來
    customBoxWaitAnimation=new AnimationController(duration: const Duration(milliseconds: 1000*100), vsync: this);
    //第一次layout後會被調用
    WidgetsBinding.instance.addPostFrameCallback((context){
      print("addPostFrameCallback is  invoke");
      //triggerPullController.triggerPull();
    });
  }


  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: new AppBar(
          title: new Text("上下拉刷新"),
        ),
        body: new PullAndPush(
          //如果你headerRefreshBox和footerRefreshBox全都自定義了,則default**系列的屬性均無效,假如有一個RefreshBox是用默認的(在該RefreshBox Enable的情況下)則default**系列的屬性均有效
          //If your headerRefreshBox and footerRefreshBox are all customizable,then the default** attributes of the series are invalid,
          // If there is a RefreshBox is the default(In the case of the RefreshBox Enable)then the default** attributes of the series are valid
          defaultRefreshBoxTipText: defaultRefreshBoxTipText,
          headerRefreshBox: _getCustomHeaderBox(),
          triggerPullController:triggerPullController,

          //你也可以自定義底部的刷新欄;you can customize the bottom refresh box
          animationStateChangedCallback:(AnimationStates animationStates,RefreshBoxDirectionStatus refreshBoxDirectionStatus){
            _handleStateCallback( animationStates, refreshBoxDirectionStatus);
          },
          listView: new ListView.builder(
            //ListView的Item
              itemCount: strs.length,//+2,
              controller: controller,
              physics: scrollPhysics,
              itemBuilder: (BuildContext context,int index){
                return  new Container(
                  height: 35.0,
                  child: new Center(
                    child: new Text(strs[index],style: new TextStyle(fontSize: 18.0),),
                  ),
                );
              }
          ),
          loadData: (isPullDown) async{
            await _loadData(isPullDown);
          },
          scrollPhysicsChanged: (ScrollPhysics physics) {
            //這個不用改,照抄即可;This does not need to change,only copy it
            setState(() {
              scrollPhysics=physics;
            });
          },
        )
    );
  }
}

上面代碼那麼多變量,看起來是不是很懵逼?到底有啥用呢?下面我來一一展示變量的用途

triggerPullController

可通過此對象的方法調用主動觸發下拉刷新

loadData

加載數據的回調

scrollPhysicsChanged

ListView的scrollPhysics發生改變時回調,通過SetState改變ListView的Physics, 這個回調直接抄demo裏面的就行

listView

ListView控件

isShowLeadingGlow

在拉到頂部時,是否顯示光暈效果,默認不顯示

isShowTrailingGlow

在拉到底部時,是否顯示光暈效果,默認不顯示

backgroundColor

設置上下拉框的顏色

refreshIconPath

設置上下拉框中旋轉的圖片的路徑

tipText

設置上下拉框中的提示文字

textColor

設置上下拉框中文字的顏色

isPullEnable

是否使用下拉刷新,默認啓用

isPushEnable

是否啓用上拉加載,默認啓用;

glowColor

設置光暈的顏色,默認爲藍色;

headerRefreshBox ; footerRefreshBox

分別 自定義下拉頭Box 和 上拉頭部Box(就是說你想用自己設計的箭頭,自己的字體,自己的等待動畫,自己的圖片等,那你就設計一個頭部替換我寫的默認頭部)

animationStateChangedCallback

動畫各種狀態下的回調

你想怎麼搞裏面 是不是都有啊!很隨意的。

下面是我自定義的頭部,感覺還行,可以參考下↓↓↓

Widget _getCustomHeaderBox(){
    return new Container(
        color: Colors.grey,
        child:  new Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Align(
              alignment: Alignment.centerLeft,
              child: new RotatedBox(
                quarterTurns: rotationAngle,
                child: new RotationTransition( //佈局中加載時動畫的weight
                  child: new Image.asset(
                    customRefreshBoxIconPath,
                    height: 45.0,
                    width: 45.0,
                    fit:BoxFit.cover,
                  ),
                  turns: new Tween(
                      begin: 100.0,
                      end: 0.0
                  )
                      .animate(customBoxWaitAnimation)
                    ..addStatusListener((animationStatus) {
                      if (animationStatus == AnimationStatus.completed) {
                        customBoxWaitAnimation.repeat();
                      }
                    }
                    ),
                ),
              ),
            ),

            new Align(
              alignment: Alignment.centerRight,
              child:new ClipRect(
                child:new Text(customHeaderTipText,style: new TextStyle(fontSize: 18.0,color: Color(0xffe6e6e6)),),
              ),
            ),
          ],
        )
    );
  }

↓↓↓有人吐槽Pub上的上下拉刷新的狀態不好控制,不準確,我的狀態可謂是豐富啊(這都是刪了一些狀態後的結果),先看看我的回調狀態:animationStateChangedCallback的回調狀態

DragAndRefreshEnabled RefreshBox高度達到50 , 可觸發上下拉刷新,可提示用戶鬆手即可刷新
StartLoadData 開始加載數據時,可顯示加載等待的動畫
LoadDataEnd 加載完數據時;RefreshBox會留在屏幕2秒,並不馬上消失,提示用戶是否加載成功
RefreshBoxIdle RefreshBox已經消失,並且閒置,不管是未觸發刷新還是刷新結束,可通過該通知重值圖片,提示等狀態

有了上面的狀態,簡直可以爲所欲爲啊,是不是能滿足大多數的需求,看下代碼是怎麼用的吧

void _handleStateCallback(AnimationStates animationStates,RefreshBoxDirectionStatus refreshBoxDirectionStatus){
    switch (animationStates){
    //RefreshBox高度達到50,上下拉刷新可用;RefreshBox height reached 50,the function of load data is  available
      case AnimationStates.DragAndRefreshEnabled:
        setState(() {
          //3.141592653589793是弧度,角度爲180度,旋轉180度;3.141592653589793 is radians,angle is 180⁰,Rotate 180⁰
          rotationAngle=2;
        });
        break;

    //開始加載數據時;When loading data starts
      case AnimationStates.StartLoadData:
        setState(() {
          customRefreshBoxIconPath="images/refresh.png";
          customHeaderTipText="正尼瑪在拼命加載.....";
        });
        customBoxWaitAnimation.forward();
        break;

    //加載完數據時;RefreshBox會留在屏幕2秒,並不馬上消失,這裏可以提示用戶加載成功或者失敗
    // After loading the data,RefreshBox will stay on the screen for 2 seconds, not disappearing immediately,Here you can prompt the user to load successfully or fail.
      case AnimationStates.LoadDataEnd:
        customBoxWaitAnimation.reset();
        setState(() {
          rotationAngle = 0;
          if(refreshBoxDirectionStatus==RefreshBoxDirectionStatus.PULL) {
            customRefreshBoxIconPath = "images/icon_cry.png";
            customHeaderTipText = "加載失敗!請重試";
          }else if(refreshBoxDirectionStatus==RefreshBoxDirectionStatus.PUSH){
            defaultRefreshBoxTipText="可提示用戶加載成功Or失敗";
          }
        });
        break;

    //RefreshBox已經消失,並且閒置;RefreshBox has disappeared and is idle
      case AnimationStates.RefreshBoxIdle:
        setState(() {
          rotationAngle=0;
          defaultRefreshBoxTipText=customHeaderTipText="快尼瑪給老子鬆手!";
          customRefreshBoxIconPath="images/icon_arrow.png";
        });
        break;
    }
  }

是不是思路清晰明確啊,也不是很難嘛

↓↓↓最後我補充下加載數據的方法,這個方法必須是一個Future方法,變量isPullDown用來判斷是下拉還是上拉,true表示下拉刷新,False表示上拉加載

Future _loadData(bool isPullDown) async{
    try {
      var request = await httpClient.getUrl(Uri.parse(url));
      var response = await request.close();
      if (response.statusCode == HttpStatus.ok) {
        _result = await response.transform(utf8.decoder).join();
        setState(() {
          //拿到數據後,對數據進行梳理
          if(isPullDown){
            strs.clear();
            strs.addAll(addStrs);
          }else{
            strs.addAll(addStrs);
          }
        });
      } else {
        _result = 'error code : ${response.statusCode}';
      }
    } catch (exception) {
      _result = '網絡異常';
    }
    print(_result);
  }

到這裏,怎麼使用上下拉控件已經講完了,雖然用起來有那麼一點點複雜,可是自定義程度高,可擴展性強,穩定度高,需要寫的代碼思路也清晰,最最主要的:BUG少;如果在使用的過程中有什麼問題,請留言,隨時爲你解答

最後附上源碼地址:https://github.com/OpenFlutter/PullToRefresh

裏面有很多更酷的控件,歡迎Star;如果喜歡Flutter,可以加入我們哦,我們的QQ羣是 :892398530

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章