Flutter及Dart入门

目录

  • Dart语言下的Flutter
  • Flutter Widget
  • Flutter 布局
  • Flutter 页面
  • 路由跳转
  • 网络请求
  • Json序列号
  • Redux

一、Dart语言下的Flutter

这里详细的不做详解,只简单讲述下 Dart 的一些特性,主要涉及的是 Flutter下使用。

1.1、基本类型

  • var 可以定义变量,如 var tag = "666 ,这和 Swift、JS 、 Kotlin 等语言类似,同时支持闭包。
  • Dart 属于是强类型语言 ,但可以用 var 来声明变量,Dart 会自推导出数据类型,所以 var 实际上是编译期的“语法糖”。dynamic 表示动态类型, 被编译后,实际是一个 object 类型,在编译期间不进行任何的类型检查,而是在运行期进行类型检查。

  • Dart 中 number 类型分为 intdouble ,Dart 中没有 float 类型。

  • Dart 下只有 bool 型可以用于 if 等判断 。
  • Dart 中,switch 支持 String 类型
类型对照表
Dart Android ios
null null nil
bool java.lang.Boolean Bool
int java.lang.Integer Int、Int64等
List java.util.ArrayList Array
Map java.util.HashMap Dictionary

1.2、变量

  • Dart 不需要给变量设置 settergetter 方法,Dart 中所有的基础类型、类等都继承 Object ,默认值是 NULL, 自带 getter 和 setter ,而如果是 final 或者 const 的话,那么它只有一个 getter 方法。
  • Dart 中 final 和 const 表示常量,比如 final name = 'GSY'; const value= 1000000; 同时 static const 组合代表了静态常量,其中 const 的值在编译期确定,final 的值要到运行时才确定。
  • Dart 下的数值,在作为字符串使用时,是需要显式指定的。比如:int i = 0; print("aaaa" + i); 这样并不支持,需要 print("aaaa" + i.toString()); 这样使用,这和 Java 与 JS 存在差异,所以在使用动态类型时,需要注意不要把 number 类型当做 String 使用。
  • Dart 中数组等于列表,所以 var list = []; 和 List list = new List() 可以简单看做一样。

1.3、方法

  • Dart 下 ?? 属于操作符,如: AA ?? "999" 表示如果 AA 为空,返回999;AA ??= "999" 表示如果 AA 为空,给 AA 设置成 999。
  • Dart 方法可以设置 参数默认值 和 指定名称 。比如: getDetail(Sting userName, reposName, {branch = "master"}){} 方法,这里 branch 不设置的话,默认是 “master” 。参数类型 可以指定或者不指定。调用效果: getRepositoryDetailDao(“aaa", "bbbb", branch: "dev");
  • Dart 不像 Java ,没有关键词 publicprivate 等修饰符,==_==下横向直接代表 private 。
  • Dart 中多构造函数,可以通过如下代码实现的。默认构造方法只能有一个,而通过Model.empty() 方法可以创建一个空参数的类,而变量初始化值时,只需要通过 this.name 在构造方法中指定即可:
class ModelA {
  String name;
  String tag;
  
  //默认构造方法,赋值给name和tag
  ModelA(this.name, this.tag);

  //返回一个空的ModelA
  ModelA.empty();
  
  //返回一个设置了name的ModelA
  ModelA.forName(this.name);
}


1.4、Flutter

Flutter 中支持 async/await ,如下代码所示, async/await 其实只是语法糖,最终会编译为 Flutter 中返回 Future 对象,之后通过 then 可以执行下一步。如果返回的还是 Future 便可以 then().then.() 的流式操作了 。

 ///模拟等待两秒,返回OK
  request() async {
    await Future.delayed(Duration(seconds: 1));
    return "ok!";
  }

  ///得到"ok!"后,将"ok!"修改为"ok from request"
  doSomeThing() async {
    String data = await request();
    data = "ok from request";
    return data;
  }

  ///打印结果
  renderSome() {
    doSomeThing().then((value) {
      print(value);
      ///输出ok from request
    });
  }

  • Flutter 中 setState 很有 React Native 的既视感,Flutter 中也是通过 State 跨帧实现管理数据状态的,这个后面会详细讲到。
  • Flutter 中一切皆 Widget 呈现,通过 build 方法返回 Widget,这也是和 React Native 中,通过 render 函数返回需要渲染的 component 一样的模式。

二、Flutter Widget

  • Flutter 中一切皆 Widget ,Widget 是一切的基础,利用响应式模式进行渲染。

  • 我们可以通过修改数据,再用 setState 设置数据,Flutter 会自动通过绑定的数据更新 Widget , 所以你需要做的就是实现 Widget 界面,并且和数据绑定起来。

  • Widget 分为 有状态无状态 两种,在 Flutter 中每个页面都是一帧,无状态就是保持在那一帧,而有状态的 Widget 当数据更新时,其实是创建了新的 Widget,只是 State 实现了跨帧的数据同步保存。

这里有个小 Tip ,当代码框里输入stl 的时候,可以自动弹出创建无状态控件的模板选项,而输入 stf 的时,就会弹出创建有状态 Widget 的模板选项。

3.1 无状态StatelessWidget

直接进入主题,如下下代码所示是无状态 Widget 的简单实现。继承 StatelessWidget,通过 build 方法返回一个布局好的控件。

Widget 和 Widget 之间通过 child: 进行嵌套。其中有的 Widget 只能有一个 child,比如下方的 Container ;有的 Widget 可以多个 child ,也就是 children ,比如Column` 布局,下方代码便是 Container Widget 嵌套了 Text Widget。

import 'package:flutter/material.dart';

class DemoWidget extends StatelessWidget {
  final String text;

  //数据可以通过构造方法传递进来
  DEMOWidget(this.text);

  @override
  Widget build(BuildContext context) {
    //这里返回你需要的控件
    //这里末尾有没有的逗号,对于格式化代码而已是不一样的。
    return Container(
      //白色背景
      color: Colors.white,
      //Dart语法中,?? 表示如果text为空,就返回尾号后的内容。
      child: Text(text ?? "这就是无状态DMEO"),
    );
  }
}

3.2 有状态StatefulWidget

如下代码,是有状态的widget的简单实现,你需要创建管理的是主要是 State , 通过 State 的 build 方法去构建控件。在 State 中,你可以动态改变数据,在 setState 之后,改变的数据会触发 Widget 重新构建刷新,而下方代码中,是通过延两秒之后,让文本显示为 "这就变了数值"。

import 'dart:async';
import 'package:flutter/material.dart';

class DemoStateWidget extends StatefulWidget {

  final String text;

  ////通过构造方法传值
  DemoStateWidget(this.text);

  ///主要是负责创建state
  @override
  _DemoStateWidgetState createState() => _DemoStateWidgetState(text);
}

class _DemoStateWidgetState extends State<DemoStateWidget> {

  String text;

  _DemoStateWidgetState(this.text);
  
  @override
  void initState() {
    ///初始化,这个函数在生命周期中只调用一次
    super.initState();
    ///定时1秒
    new Future.delayed(const Duration(seconds: 1), () {
      setState(() {
        text = "这就变了数值";
      });
    });
  }

  @override
  void dispose() {
    ///销毁
    super.dispose();
  }

  @override
  void didChangeDependencies() {
    ///在initState之后调 Called when a dependency of this [State] object changes.
    super.didChangeDependencies();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(text ?? "这就是有状态DMEO"),
    );
  }
}

三、Flutter布局

Flutter 中拥有需要将近30种内置的 布局Widget,其中常用有 Container、Padding、Center、Flex、Stack、Row、Column、ListView 等,下面简单讲解它们的特性和使用,大家了解知道有这些即可,使用时候具体属性可以再查。

  • Container :最常用的默认控件,但是实际上它是由多个内置控件组成的模版,只能包含一个child,支持 padding,margin,color,宽高,decoration(一般配置边框和阴影)等配置,在 Flutter 中,不是所有的控件都有 宽高、padding、margin、color 等属性,所以才会有 Padding、Center 等 Widget 的存在。
        ///四周10大小的maring
        margin: EdgeInsets.all(10.0),
        height: 120.0,
        width: 500.0,
        ///透明黑色遮罩
        decoration: new BoxDecoration(
            ///弧度为4.0
            borderRadius: BorderRadius.all(Radius.circular(4.0)),
            ///设置了decoration的color,就不能设置Container的color。
            color: Colors.black,
            ///边框
            border: new Border.all(color: Color(GSYColors.subTextColor), width: 0.3)),
        child:new Text("666666"));
    
  • ColumnRow 绝对是必备布局, 横竖布局也是日常中最常见的场景。如下方所示,它们常用的有这些属性配置:主轴方向是 start 或 center 等;副轴方向方向是 start 或 center 等;mainAxisSize 是充满最大尺寸,或者只根据子 Widget 显示最小尺寸。
//主轴方向,Column的竖向、Row我的横向
mainAxisAlignment: MainAxisAlignment.start, 
//默认是最大充满、还是根据child显示最小大小
mainAxisSize: MainAxisSize.max,
//副轴方向,Column的横向、Row我的竖向
crossAxisAlignment :CrossAxisAlignment.center,

  • Expanded 在 Column 和 Row 中代表着平均充满的作用,当有两个存在的时候默认均分充满。同时页可以设置 flex 属性决定比例。
new Column(
     ///主轴居中,即是竖直向居中
     mainAxisAlignment: MainAxisAlignment.center,
     ///大小按照最小显示
     mainAxisSize : MainAxisSize.min,
     ///横向也居中
      crossAxisAlignment : CrossAxisAlignment.center,
      children: <Widget>[
        ///flex默认为1
        new Expanded(child: new Text("1111"), flex: 2,),
        new Expanded(child: new Text("2222")),
      ],
    );

Flutter 中,布局很多时候一层一层嵌套出来的,当然还有其他更高级的布局方式,这里就先不展开了。

四、Flutter 页面

Flutter 中除了布局的 Widget,还有交互显示的 Widget 和完整页面呈现的Widget,其中常见的有 MaterialApp、Scaffold、Appbar、Text、Image、FlatButton等,下面简单介绍这些 Wdiget。

类型 使用
MaterialApp 一般作为APP顶层的主页入口,可配置主题,多语言,路由等
Scaffold 一般用户页面的承载Widget,包含appbar、snackbar、drawer等material design的设定。
Appbar 一般用于Scaffold的appbar ,内有标题,二级页面返回按键等,当然不止这些,tabbar等也会需要它 。
Text 显示文本,几乎都会用到,主要是通过style设置TextStyle来设置字体样式等。
RichText 富文本,通过设置TextSpan,可以拼接出富文本场景。
TextField 文本输入框 :new TextField(controller: //文本控制器, obscureText: "hint文本");
Image 图片加载: new FadeInImage.assetNetwork( placeholder: "预览图", fit: BoxFit.fitWidth, image: "url");
FlatButton 按键点击: new FlatButton(onPressed: () {},child: new Container());

实现一个简单完整的页面试试。直接上代码:

  • 首先我们创建一个StatefulWidget:DemoPage。
  • 然后在_DemoPageState 中,通过build创建了一个Scaffold。
  • Scaffold内包含了一个AppBar和一个ListView。
  • AppBar类似标题了区域,其中设置了 title为 Text Widget。
  • body是ListView, 返回自定义Widget。
import 'package:flutter/material.dart';
import 'package:gsy_github_app_flutter/test/DemoItem.dart';

class DemoPage extends StatefulWidget {
  @override
  _DemoPageState createState() => _DemoPageState();
}

class _DemoPageState extends State<DemoPage> {
  @override
  Widget build(BuildContext context) {
    ///一个页面的开始
    ///如果是新页面,会自带返回按键
    return new Scaffold(
      ///背景样式
      backgroundColor: Colors.blue,
      ///标题栏,当然不仅仅是标题栏
      appBar: new AppBar(
        ///这个title是一个Widget
        title: new Text("Title"),
      ),
      ///正式的页面开始
      ///一个ListView,20个Item
      body: new ListView.builder(
        itemBuilder: (context, index) {
          return new CustomItem(); //类似于ios的cell
        },
        itemCount: 20,
      ),
    );
  }
}

这里主要讲解都是一些入坑常用的东西,方便大家首先对一些基础知识有个大概了解。

五、路由

Flutter 中的页面跳转一般是通过 Navigator 实现的,路由跳转又分为:带参数跳转和不带参数跳转。不带参数跳转比较简单,默认可以通过 MaterialApp 的路由表跳转,也可以使用PageRouteBuider;而带参数的跳转自定义跳转动画,参数通过跳转页面的构造方法传递。常用的跳转有如下几种使用:

///不带参数的路由表跳转
Navigator.pushNamed(context, routeName);

///跳转新页面并且替换,比如登录页跳转主页
Navigator.pushReplacementNamed(context, routeName);

///跳转到新的路由,并且关闭给定路由的之前的所有页面
Navigator.pushNamedAndRemoveUntil(context, '/calendar', ModalRoute.withName('/'));

///带参数的路由跳转,并且监听返回
Navigator.push(context, new MaterialPageRoute(builder: (context) => new NotifyPage())).then((res) {
      ///获取返回处理
    });
    
///带参数的路由跳转,自定义跳转动画
Navigator.push(context, new PageRouteBuider(pageBuilde: (context, anination, secondAnimation) => new NotifyPage())).then((res) {
      ///获取返回处理
    });


同时我们可以看到,Navigator 的 push 返回的是一个 Future,这个Future 的作用是在页面返回时被调用的。也就是你可以通过 Navigator 的 pop 时返回参数,之后在 Future 中可以的监听中处理页面的返回结果。

六、网络请求

当前 Flutter 网络请求封装中,国内最受欢迎的就是 Dio 或者 http 了,这两个都是对dart请求进行了二次封装,使用起来非常方便。

这里看下一个Http网络请求的例子

/// 请求列表数据,返回model
 static Future<FYContractListModelEntity> getSelectContractList(Map params,{String pre ="t8"})
  async {
  /// 获取请求url
    String url = EnvirmentUtils.getBaseUrlByPre(pre,FYNetUrl.selectContractList);
    final response = await http.post(url,body: params);
    if(response.statusCode == 200){
    /// 请求成功后对结果进行处理
      final result = json.decode(Utf8Decoder().convert(response.bodyBytes));
      return JsonConvert.fromJsonAsT<FYContractListModelEntity>(result);
    }else{
    /// 抛出异常
      throw Exception('Failed to load ContractList json');
    }
  }

七、Json序列化

项目json解析是不可避免的操作,Dart的解析如果手动解析的话是非常复杂的,可以看下代码:

class OrderListItem {
  List<Data> data;
  Property property;
  Page page;
  Status status;

  OrderListItem({this.data, this.property, this.page, this.status});

  OrderListItem.fromJson(Map<String, dynamic> json) {
    if (json['data'] != null) {
      data = new List<Data>();
      (json['data'] as List).forEach((v) {
        data.add(new Data.fromJson(v));
      });
    }
    property = json['property'] != null
        ? new Property.fromJson(json['property'])
        : null;
    page = json['page'] != null ? new Page.fromJson(json['page']) : null;
    status =
        json['status'] != null ? new Status.fromJson(json['status']) : null;
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    if (this.data != null) {
      data['data'] = this.data.map((v) => v.toJson()).toList();
    }
    if (this.property != null) {
      data['property'] = this.property.toJson();
    }
    if (this.page != null) {
      data['page'] = this.page.toJson();
    }
    if (this.status != null) {
      data['status'] = this.status.toJson();
    }
    return data;
  }
}

class Data {
  int goodsLoadDate;
  String orderSn;
  String unDrawMoney;
  int brokerPeriod;
  String startCityName;
  String goodsWeight;
  int checkStatus;
  String payStatusEnum;
  int buttonStatus;
  String carLength;
  String driverMobile;
  double noPayMoney;
  String statusDesc;
  String hasPay;
  int isNew;
  int needReceipt;
  String buttonStatusDesc;
  String endCityName;
  String exceptionStatusEnum;
  String backStatusEnum;
  int driverId;
  int backStatus;
  String checkStatusEnum;
  String driverName;
  String waitPay;
  int payStatus;
  String carModel;
  String goodsNum;
  String goodsCubage;
  String locationAddress;
  int driverArrangeDeadline;
  int expectDelayTime;
  int expectGoodsUnloadDate;
  int positionStatus;

  Data(
      {this.goodsLoadDate,
      this.orderSn,
      this.unDrawMoney,
      this.brokerPeriod,
      this.startCityName,
      this.goodsWeight,
      this.checkStatus,
      this.payStatusEnum,
      this.buttonStatus,
      this.carLength,
      this.driverMobile,
      this.noPayMoney,-->
      this.statusDesc,
      this.hasPay,
      this.isNew,
      this.needReceipt,
      this.buttonStatusDesc,
      this.endCityName,
      this.exceptionStatusEnum,
      this.backStatusEnum,
      this.driverId,
      this.backStatus,
      this.checkStatusEnum,
      this.driverName,
      this.waitPay,
      this.payStatus,
      this.carModel,
      this.goodsNum,
      this.goodsCubage,
      this.locationAddress,
      this.driverArrangeDeadline,
      this.expectDelayTime,
      this.expectGoodsUnloadDate,
      this.positionStatus});

  Data.fromJson(Map<String, dynamic> json) {
    goodsLoadDate = json['goodsLoadDate'];
    orderSn = json['orderSn'];
    unDrawMoney = json['unDrawMoney'];
    brokerPeriod = json['brokerPeriod'];
    startCityName = json['startCityName'];
    goodsWeight = json['goodsWeight'];
    checkStatus = json['checkStatus'];
    payStatusEnum = json['payStatusEnum'];
    buttonStatus = json['buttonStatus'];
    carLength = json['carLength'];
    driverMobile = json['driverMobile'];
    noPayMoney = json['noPayMoney'];
    statusDesc = json['statusDesc'];
    hasPay = json['hasPay'];
    isNew = json['isNew'];
    needReceipt = json['needReceipt'];
    buttonStatusDesc = json['buttonStatusDesc'];
    endCityName = json['endCityName'];
    exceptionStatusEnum = json['exceptionStatusEnum'];
    backStatusEnum = json['backStatusEnum'];
    driverId = json['driverId'];
    backStatus = json['backStatus'];
    checkStatusEnum = json['checkStatusEnum'];
    driverName = json['driverName'];
    waitPay = json['waitPay'];
    payStatus = json['payStatus'];
    carModel = json['carModel'];
    goodsNum = json['goodsNum'];
    goodsCubage = json['goodsCubage'];
    locationAddress = json['locationAddress'];
    driverArrangeDeadline = json['driverArrangeDeadline'];
    expectDelayTime = json['expectDelayTime'];
    expectGoodsUnloadDate = json['expectGoodsUnloadDate'];
    positionStatus = json['positionStatus'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['goodsLoadDate'] = this.goodsLoadDate;
    data['orderSn'] = this.orderSn;
    data['unDrawMoney'] = this.unDrawMoney;
    data['brokerPeriod'] = this.brokerPeriod;
    data['startCityName'] = this.startCityName;
    data['goodsWeight'] = this.goodsWeight;
    data['checkStatus'] = this.checkStatus;
    data['payStatusEnum'] = this.payStatusEnum;
    data['buttonStatus'] = this.buttonStatus;
    data['carLength'] = this.carLength;
    data['driverMobile'] = this.driverMobile;
    data['noPayMoney'] = this.noPayMoney;
    data['statusDesc'] = this.statusDesc;
    data['hasPay'] = this.hasPay;
    data['isNew'] = this.isNew;
    data['needReceipt'] = this.needReceipt;
    data['buttonStatusDesc'] = this.buttonStatusDesc;
    data['endCityName'] = this.endCityName;
    data['exceptionStatusEnum'] = this.exceptionStatusEnum;
    data['backStatusEnum'] = this.backStatusEnum;
    data['driverId'] = this.driverId;
    data['backStatus'] = this.backStatus;
    data['checkStatusEnum'] = this.checkStatusEnum;
    data['driverName'] = this.driverName;
    data['waitPay'] = this.waitPay;
    data['payStatus'] = this.payStatus;
    data['carModel'] = this.carModel;
    data['goodsNum'] = this.goodsNum;
    data['goodsCubage'] = this.goodsCubage;
    data['locationAddress'] = this.locationAddress;
    data['driverArrangeDeadline'] = this.driverArrangeDeadline;
    data['expectDelayTime'] = this.expectDelayTime;
    data['expectGoodsUnloadDate'] = this.expectGoodsUnloadDate;
    data['positionStatus'] = this.positionStatus;
    return data;
  }
}

class Property {
  int searchType;
  int systemTime;

  Property({this.searchType, this.systemTime});

  Property.fromJson(Map<String, dynamic> json) {
    searchType = json['searchType'];
    systemTime = json['systemTime'];
  }

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

class Page {
  int totalCou;
  int curPage;
  int totalPage;
  int nextPage;
  bool useCache;
  int pageSize;
  bool queryCount;

  Page(
      {this.totalCou,
      this.curPage,
      this.totalPage,
      this.nextPage,
      this.useCache,
      this.pageSize,
      this.queryCount});

  Page.fromJson(Map<String, dynamic> json) {
    totalCou = json['totalCou'];
    curPage = json['curPage'];
    totalPage = json['totalPage'];
    nextPage = json['nextPage'];
    useCache = json['useCache'];
    pageSize = json['pageSize'];
    queryCount = json['queryCount'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['totalCou'] = this.totalCou;
    data['curPage'] = this.curPage;
    data['totalPage'] = this.totalPage;
    data['nextPage'] = this.nextPage;
    data['useCache'] = this.useCache;
    data['pageSize'] = this.pageSize;
    data['queryCount'] = this.queryCount;
    return data;
  }
}

class Status {
  int code;
  String desc;

  Status({this.code, this.desc});

  Status.fromJson(Map<String, dynamic> json) {
    code = json['code'];
    desc = json['desc'];
  }

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

所以 json_annotation 插件就诞生了,只需写入类名和返回的json数据即可自动生成

dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons
  json_annotation: ^2.0.0

dev_dependencies:
  flutter_test:
    sdk: flutter

  json_serializable: ^2.0.0

可以自己在项目中操作一把看下,非常方便。

八、Redux

在前端领域,Redux 并不是一个陌生的概念,作为全局状态管理机,用于 Flutter 中再合适不过,简单来说就是:它可以跨控件管理、同步State 。

前面已经讲了,在 Flutter 中 是通过实现 StatesetState 来渲染和改变 StatefulWidget 的,如果使用了flutter_redux 会有怎样的效果?

比如把用户信息存储在 reduxstore 中, 好处在于: 比如某个页面修改了当前用户信息,所有绑定的该 State 的控件将由 Redux 自动同步修改,State 可以跨页面共享。

redux 中主要引入了 action、reducer、store 概念:

  • action 简单点就是动作,通过发起一个action来告诉Reducer该更新状态了
  • reducer 用于根据 action 产生新状态
  • store 位于整个APP的顶层,用于存储和管理 state

直接上代码:

8.1. 创建State

///创建State,全局Redux store 的对象,保存State数据
//定义一个state
class ReduxState {
  String name;
  ReduxState.initState() : name = "666";
}

8.2. 创建action

//定义action
enum Action {
  Change
}

8.3. 创建reducer

//定义reducer
ReduxState getReduce(ReduxState state, action) {
  if(action == Action.Change) {
    String nname = "1";
    state.name = state.name + nname;
  }
  return state;
}

8.4. 创建全局Store

在main.dart中创建一个全局的store

main() {
  final store = Store<ReduxState>(
      getReduce,
      initialState: ReduxState.initState()
  );
  runApp(ReduxDemo3(store,));
}

8.5. 将Store跟根Widget关联

注意:

  • 最顶层必须是 StoreProvider 开始
  • StoreBuilder后要跟上我们定义的那个State类,要不会报错,
class ReduxDemo3 extends StatelessWidget {

  final Store<ReduxState> store;
  ReduxDemo3(this.store);

  @override
  Widget build(BuildContext context) {
    return StoreProvider(
        store: store,
        child: StoreBuilder<ReduxState>(builder: (BuildContext context, Store<ReduxState> store){
          return MaterialApp(
            title: 'ReduxDemo3',
            theme: new ThemeData(
              primarySwatch: Colors.blue,
            ),
            home: FirstPage(),
          );
        })
    );
  }
}

8.6. 一级子界面构建

注意:builder方法里有2个值,context和store,我们自定义的State类可以从store中获取

@override
  Widget build(BuildContext context) {

    return StoreBuilder<ReduxState>(
        builder: (BuildContext context, Store<ReduxState> store){
          return Scaffold(
            appBar: AppBar(
              title: Text("ReduxDemo3"),
            ),
            body: Center(
                child: Column(
                  children: <Widget>[
                    Text(store.state.name),
                    SizedBox(height: 100,),
                    FlatButton(
                        onPressed: (){
                          Navigator.of(context).push(MaterialPageRoute(builder: (context){
                            return NextPage();
                          }));
                        },
                        child: Text("下一页")
                    )
                  ],
                )
            ),
          );
        }
    );
  }

8.7. 二级子界面构建

State数据的修改是由Store来发起Action,通知Reducer修改数据,方法为store.dispatch(定义的action)

@override
  Widget build(BuildContext context) {
    return StoreBuilder<ReduxState>(
        builder: (BuildContext context, Store<ReduxState> store){
          return Scaffold(
            body: Center(
                child: Column(
                  children: <Widget>[
                    Text(store.state.name),
                    SizedBox(height: 100,),
                    FlatButton(
                        onPressed: (){
                          store.dispatch(Action.Change);
                        },
                        child: Text("点击变换数据")
                    )
                  ],
                )
            ),
          );
        }
    );
  }

最终效果:


image

8.8. StoreBuilder和StoreConnector

例子里的界面构建使用的是StoreBuilder来构建,也可以使用 ==StoreConnector== 来构建,两者就差一个参数。
StoreConnector主要是有个数据转化的作用,可以对数据先做一些转化操作再赋值到组件上

/// 多一个ViewModel
class StoreConnector<S, ViewModel> extends StatelessWidget 

class StoreBuilder<S> extends StatelessWidget

可以修改第二个界面里的body的代码,将显示的Text换成用StoreConnector来包装,效果是一样的

body: Center(
        child: Column(
          children: <Widget>[
            StoreConnector<ReduxState, String>(
            ///转换为要使用的 name 字符串
              converter: (store) => store.state.name,
                builder: (BuildContext context, String name) {
                  return Text(name);
                },
            ),
            SizedBox(height: 100,),
            StoreBuilder<ReduxState>(
                builder: (context, store) {
                  return FlatButton(
                      onPressed: (){
                        store.dispatch(Action.Change);
                      },
                      child: Text("点击变换数据")
                  );
                }
            ),
          ],
        )
      ),

StoreConnector 需要两个泛型

  1. 一个是我们创建的 State(ReduxState)
  2. 一个是 ViewModel

StoreConnector 要定义两个函数

  1. 一个是 converter,转化函数,从 Store 中拿出修改的数据 store.state.name
  2. 一个是 builder,将 converter 返回的 name 进一步转化为界面:Text(name)。

ViewModel决定了converter(转换函数)那边的返回值类型,这边我们将它定义为String,因为转换函数里返回的是ReduxState里的name字段。

这里就是对flutter 入门的一些简单知识分享,一些概念性的东西这里不一一分享,大家看官网就可以看到,这里主要针对一些重要概念和一些坑进行分析,希望对大家有所帮助。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容