• 【技术博客】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( 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;
        }
    }
    

  • 相关阅读:
    ZOJ 3018
    poj2464
    Gauss
    【C】关于内存地址
    【C】typedef与define的区别
    C位移操作
    操作系统使用批处理文件更改网络配置
    【Linux】查看某个进程的线程数量(转)
    数据结构快速排序
    C++Explanation of ++val++ and ++*p++ in C
  • 原文地址:https://www.cnblogs.com/zhyixuan/p/12969534.html
Copyright © 2020-2023  润新知