【技術博客】Flutter—使用網絡請求的頁面搭建流程、State生命周期、一些組件的應用


Flutter—使用網絡請求的頁面搭建流程、State生命周期、一些組件的應用

使用網絡請求的頁面搭建流程

​ 在開發APP時,我們常常會遇到如下場景:進入一個頁面后,要先進行網絡調用,然后使用調用返回的數據進行頁面渲染。

​ 這種頁面搭建流程大致為:調用網絡請求,獲得json格式的數據—解析獲得的數據為Dart類 — 將Dart數據傳回UI。在返回數據前,可以在頁面先放置一個加載動畫;獲得數據后,使用數據進行進行頁面重繪。

網絡請求

​ Flutter的網絡請求常常使用的庫有httpdio,其中dio是一個強大的Dart Http請求庫,支持Restful API、FormData、攔截器、請求取消、Cookie管理、文件上傳/下載、超時、自定義適配器等。dio的詳細介紹與使用方法可以參見github-dio官方文檔

  • 在pubspec.yaml中引入包依賴

    dependencies:
      dio: ^3.x.x  // 請使用pub上3.0.0分支的最新版本
    
  • 極簡示例

    import 'package:dio/dio.dart';
    void getHttp() async {
      try {
        Response response = await Dio().get("http://www.baidu.com");
        print(response);
      } catch (e) {
        print(e);
      }
    }
    

數據解析

​ 當調用Dio.get()方法時,可以從網絡獲得json數據,我們可以將這些數據進行解析處理,方便UI搭建的使用。 實例如下:

//該函數從調用服務器某一個接口,獲得json數據,並將解析后的Dart model返回。
Future<Course> getCourse(String studentID) async {
  Dio dio = new Dio();
  Response response;
  response = await dio.request(
        'http://www.baidu.com',//將此替換為后端服務器地址
      options: Options(method: "GET", responseType: ResponseType.plain),
    );
  String ss = response.data;
  dynamic jsonList = json.decode(ss);
  Course tempStr = new Course.fromJson(jsonList);//將json解析為Dart類,
  return tempStr;
}

Course.fromJson的實現有很多種方式,可以手寫或者自動生成。在此推薦一個json-Dart 轉換的網頁json to Dart。該網頁可以自動根據給定的json數據生成Dart類。例如對於如下格式的json數據:

{
      "course_name": "計算機網絡",
      "department": "計算機學院"
}

生成的Dart 類為:

class course {
  String courseName;
  String department;

  course({this.courseName, this.department});

  course.fromJson(Map<String, dynamic> json) {
    courseName = json['course_name'];
    department = json['department'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['course_name'] = this.courseName;
    data['department'] = this.department;
    return data;
  }
}

FutureBuilder頁面渲染

​ 在實現網絡請求的調用和數據解析后,我們需要把獲得的數據應用到UI的渲染中。此時就要用到``FutureBuilder`。

FutureBuilder是一個將異步操作和異步UI更新結合在一起的類,通過它我們可以將網絡請求,數據庫讀取等的結果更新的頁面上。其構造方法如下:

FutureBuilder({
    Key key, Future<T> future, //表示此構建器當前連接的異步計算。
    T initialData, 
    @required 
    AsyncWidgetBuilder<T> builder //是一個基於異步交互構建widget的函數,這個函數接受兩個參數`BuildContext context`與 `AsyncSnapshot<T> snapshot`,並應該返回一個widget。其中異步計算獲得的數據就保存在`snapshot.data`中,在builder中可以通過`snapshot.data`獲得異步數據。
})

一個使用實例

//網絡調用未返回數據時,返回加載動畫;返回數據后展示返回數據的'course_name'值。
Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('主題'),
        ),
        body: FutureBuilder<CommonModel>(
            future: getCourse(),//調用上面數據解析部分封裝好的網絡請求函數getCourse()為異步計算函數。
            builder:
                (BuildContext context, AsyncSnapshot<CommonModel> snapshot) {
              switch (snapshot.connectionState) {
                case ConnectionState.none:
                  return new Text('Input a URL to start');
                case ConnectionState.waiting:
                  return new Center(child: new CircularProgressIndicator());
                case ConnectionState.active:
                  return new Text('');
                case ConnectionState.done:
                  if (snapshot.hasError) {
                    return new Text(
                      '${snapshot.error}',
                      style: TextStyle(color: Colors.red),
                    );
                  } else {
                    return Text(${snapshot.data.courese_name}$);
                  }
              }
            }),
      ),
    );
  }

State生命周期

​ State是構造StatefulWidget必不可少的組件。State的生命周期主要包括三個階段:創建、更新、銷毀。

  • 創建

    State Widget的創建流程為:

    1. 構造方法,接受父組件的數據,並調用createState
    2. initState。在這個函數中可以對State中的狀態值進行初始化。
    3. didChangeDependencies 。在initState后被調用,可以用來處理 State 對象依賴關系的變化
    4. build。完成以上的函數后,在build方法中,將根據父 Widget 傳遞過來的初始化配置數據及 State 的當前狀態,創建一個 Widget 然后返回。
  • 更新

    State Widget的狀態更新可以通過三種途徑:setState、didChangeDependencies 或 didUpdateWidget 觸發。

    • setState。當狀態值發生改變的時候,可以通過調用setState方法通知flutter對Widget進行重繪(build)。
    • didChangeDependencies。當State依賴的數據發生改變時,Flutter 會回調該方法,隨后觸發組件構建。
    • didUpdateWidget。Widget 的配置發生變化時,或熱重載時,系統會回調該方法。
  • 銷毀

    當 State 被永久地從視圖樹中移除時,Flutter 會調用 dispose 方法,而一旦 dispose 方法被調用,組件就要被銷毀了,因此可以在 dispose 方法中進行最終的資源釋放、移除監聽、清理環境等工作

State生命周期可以用一下圖來描述:

一些組件的應用

在出現彈窗時禁用實體返回鍵

​ 典型場景:在進行網絡請求過程中,我們不希望用戶在中途進行其他操作。此時,我們可以使用一個過渡動畫來提示用戶目前正在進行網絡調用。

​ 過渡動畫的實現方法有很多,最常見的是進行彈窗提醒。所以我們需要維持網絡請求過程中UI始終為彈窗頁。即要求:

  1. 點擊對話框以外的區域不隱藏彈窗——barrierDismissible屬性
  2. 點擊實體返回鍵不隱藏彈窗——在原有彈窗組件中外包裹一層WillPopScope,並設置onWillPop屬性為false。

舉例:

showDialog(
    barrierDismissible: false,//barrierDismissible屬性,控制點擊對話框以外的區域是否隱藏對話框,為flase時不隱藏
    context: context
    builder: (context) {
    return WillPopScope(//在原有的彈窗Wigdet外,嵌套WillPopScope,並設置onWillPop的返回值為false
    	child: Center(
    		Text('加載中'),
            ),
        onWillPop: () {
     	    return new Future.value(false);
            },
    );
});

點擊空白處回收鍵盤

​ 在與鍵盤有交互的頁面,用戶常常進行的一個操作是點擊空白處回收鍵盤。實現這一功能的也很簡單,只需要在頁面的最外層包裹一層GestureDetector,並在onTap時取消鍵盤焦點。

GestureDetector用於用戶交互,進行基本的手勢控制,其基本屬性如下所示。

GestureDetector({
    Key key,
    this.child,
    this.onTapDown,//可能導致點擊的指針已聯系到屏幕的特定位置
    this.onTapUp,//觸發點的指針已停止在特定位置與屏幕聯系
    this.onTap,//發生了點擊。
    this.onTapCancel,//觸發onTapDown的指針取消觸發
    this.onDoubleTap,//雙擊
    this.onLongPress,//長按
    this.onLongPressUp,//長按結束
    this.onVerticalDragDown,//
    this.onVerticalDragStart,//指針已經接觸到屏幕,而且可能開始垂直移動。
    this.onVerticalDragUpdate,//與屏幕接觸並垂直移動的指針沿垂直方向移動
    this.onVerticalDragEnd,//以前與屏幕接觸並垂直移動的指針不再與屏幕接觸,並且當其停止接觸屏幕時以特定速度移動。
    this.onVerticalDragCancel,//
    this.onHorizontalDragDown,//
    this.onHorizontalDragStart,//
    this.onHorizontalDragUpdate,//
    this.onHorizontalDragEnd,//
    this.onHorizontalDragCancel,//
//    onPan可以取代onVerticalDrag或者onHorizontalDrag,三者不能並存
    this.onPanDown,//指針已經接觸屏幕並開始移動
    this.onPanStart,//與屏幕接觸並移動的指針再次移動
    this.onPanUpdate,//先前與屏幕接觸並移動的指針不再與屏幕接觸,並且當它停止接觸屏幕時以特定速度移動
    this.onPanEnd,//先前觸發 onPanDown 的指針未完成
    this.onPanCancel,//
//    onScale可以取代onVerticalDrag或者onHorizontalDrag,三者不能並存,不能與onPan並存
    this.onScaleStart,//
    this.onScaleUpdate,//
    this.onScaleEnd,//
    this.behavior,
    this.excludeFromSemantics = false
    })

實例:

class _CourseCommentWritePage extends State<CourseCommentWritePage> {
  TextEditingController commentController = new TextEditingController();
  FocusNode commentNode = new FocusNode();
  
  Widget build(BuildContext context) {
    print("build_write");
    return Scaffold(
      appBar: AppBar(
        title: Text("標題"),
      ),
      body: GestureDetector(
        onTap: () {
          commentNode.unfocus();//取消鍵盤焦點
        },
        child:Container(
                decoration: BoxDecoration(
                  border: new Border.all(width: 2.0, color: Colors.black12),
                  borderRadius: new BorderRadius.all(new Radius.circular(10.0)),
                ),
                child: TextField(
                  enabled: _enable,
                  maxLines: 12,
                  focusNode: commentNode,
                  controller: commentController,
                  decoration: InputDecoration(
                    hintText: '請輸入你對課程的評價',
                    border: InputBorder.none,
                  ),
                ),
              ),
      )
   }
   

在彈出鍵盤時,防止頁面溢出

在我們實際的項目開發中,經常會遇到頁面UI內容過多,導致手機一屏展示不完的情況出現,在Flutter中我們通常使用SingleChildScrollView處理滑動,即使用SingleChildScrollView組件將普通組件包裹。

同樣,可以使用SingleChildScrollView中將表單TextFiled包裹,以防止溢出鍵盤彈出導致的溢出。

SingleChildScrollView組件:

const SingleChildScrollView({
  Key key,//滾動方向,默認是垂直方向
  this.scrollDirection = Axis.vertical,//是否按照閱讀方向相反的方向滑動
  this.reverse = false,//內容邊距
  this.padding,//是否使用widget樹中默認的PrimaryScrollController
  bool primary,//此屬性接受一個ScrollPhysics類型的對象,它決定可以滾動如何響應用戶操作,比如用戶滑動完抬起手指后,繼續執行動畫,或者滑動到邊界時,如何顯示。
  this.controller,
  this.child,
})

在特定條件下禁用RaiseButton

​ RaiseButton是一個按鈕組件,其一些屬性作用如下:

const RaisedButton({
    Key key,
    @required VoidCallback onPressed,//被點擊時的回調函數
    ValueChanged<bool> onHighlightChanged,//水波紋高亮變化回調,按下返回true,抬起返回false
    ButtonTextTheme textTheme,//按鈕的主題
    Color textColor,//文字的顏色
    Color disabledTextColor,//按鈕禁用時候文字的顏色
    Color color,//按鈕的背景顏色
    Color disabledColor,//按鈕被禁用的時候顯示的顏色
    Color highlightColor,//點擊或者toch控件高亮的時候顯示在控件上面,水波紋下面的顏色
    Color splashColor,//水波紋的顏色
    Brightness colorBrightness,//按鈕主題高亮
    double elevation,//按鈕下面的陰影
    double highlightElevation,//高亮時候的陰影
    double disabledElevation,//按下的時候的陰影
    EdgeInsetsGeometry padding,
    ShapeBorder shape,//設置形狀
    Clip clipBehavior = Clip.none,
    MaterialTapTargetSize materialTapTargetSize,
    Duration animationDuration,
    Widget child,
    })

disabledColor字段用於設置按鈕被禁用時顯示的顏色。但是可以發現其實並沒有將按鈕設為禁用的字段。查找資料發現,當onpress()的返回值為null時,按鈕自動被設為禁用。

例如:

class MyPage extennds StatelessWidget(){
    bool _enable = true;
    Widget build(BuildContext context){
        return RaisedButton(
   			child: Text("修改"),
  			color: Colors.lightBlue,
   			disabledColor: Colors.grey,
   			onPressed:
   			_enable ? () => setTextEnable() :null,//當_enable為false時,禁用按鈕
   		),
   }
   void setTextEnable() {
    	setState(() {
      		_enable = !_enable;
    	});
  	}
}

//值得注意的是,不能將onpress的回調函數寫為以下形式,寫成這樣並不能在當_enable為false時,禁用按鈕
onpress:() {
    if(_enable){
        setTextEnable()
    }else{
        return null;
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM