Flutter 详解(三、深入了解状态管理--Provider)

From: https://juejin.cn/post/6862150535043252237

flutter中状态管理是重中之重,每当谈这个话题,总有说不完的话。

在正式介绍 Provider 为什么我们需要状态管理。如果你已经对此十分清楚,那么建议直接跳过这一节。 如果我们的应用足够简单,Flutter 作为一个声明式框架,你或许只需要将 数据 映射成 视图 就可以了。你可能并不需要状态管理,就像下面这样。 但是随着功能的增加,你的应用程序将会有几十个甚至上百个状态。这个时候你的应用应该会是这样。 这又是什么鬼。我们很难再清楚的测试维护我们的状态,因为它看上去实在是太复杂了!而且还会有多个页面共享同一个状态,例如当你进入一个文章点赞,退出到外部缩略展示的时候,外部也需要显示点赞数,这时候就需要同步这两个状态。 Flutter 实际上在一开始就为我们提供了一种状态管理方式,那就是 StatefulWidget。但是我们很快发现,它正是造成上述原因的罪魁祸首。 在 State 属于某一个特定的 Widget,在多个 Widget 之间进行交流的时候,虽然你可以使用 callback 解决,但是当嵌套足够深的话,我们增加非常多可怕的垃圾代码。 这时候,我们便迫切的需要一个架构来帮助我们理清这些关系,状态管理框架应运而生。

Provider 是什么

通过使用Provider而不用手动编写InhertedWidget,您将获取自动分配、延迟加载、大大减少每次创建新类的代码。

首先在yaml中添加,具体版本号参考:[官方Provider pub][1],当前版本号是4.1.3.

[1]: https://pub.flutter-io.cn/packages/provider

Provider: ^4.1.3
复制代码

然后运行

flutter pub get
复制代码

获取到最新的包到本地,在需要的文件夹内导入

import 'package:provider/provider.dart';
复制代码

简单例子

我们还用点击按钮新增数字的例子

首先创建存储数据的Model

class ProviderModel extends ChangeNotifier {

        int _count=0;
        ProviderModel();
   void plus() {

    _count = _count + 1;
    notifyListeners();
  }
}
复制代码

构造view

Consumer<ProviderModel>(
        builder:
            (BuildContext context, ProviderModel value, Widget child) {
          print('Consumer 0 刷新');
          _string += 'c0 ';
          return _Row(
            value: value._count.toString(),
            callback: () {
              context.read<ProviderModel>().plus();
            },
          );
        },
        child: _Row(
          value: '0',
          callback: () {
            context.read<ProviderModel>().plus();
          },
        ),
      )
复制代码

测试下看下效果:

单个Model多个小部件分别刷新(局部刷新)

单个model实现单个页面多个小部件分别刷新,是使用Selector<Model,int>来实现,首先看下构造函数:

class Selector<A, S> extends Selector0<S> {

  Selector({
    Key key,
    @required ValueWidgetBuilder<S> builder,
    @required S Function(BuildContext, A) selector,
    ShouldRebuild<S> shouldRebuild,
    Widget child,
  })  : assert(selector != null),
        super(
          key: key,
          shouldRebuild: shouldRebuild,
          builder: builder,
          selector: (context) => selector(context, Provider.of(context)),
          child: child,
        );
}
复制代码

可以看到Selector继承了Selector0,再看Selector关键build代码:

class _Selector0State<T> extends SingleChildState<Selector0<T>> {
  T value;
  Widget cache;
  Widget oldWidget;

  @override
  Widget buildWithChild(BuildContext context, Widget child) {
    final selected = widget.selector(context);

    var shouldInvalidateCache = oldWidget != widget ||
        (widget._shouldRebuild != null &&
            widget._shouldRebuild.call(value, selected)) ||
        (widget._shouldRebuild == null &&
            !const DeepCollectionEquality().equals(value, selected));
    if (shouldInvalidateCache) {
      value = selected;
      oldWidget = widget;
      cache = widget.builder(
        context,
        selected,
        child,
      );
    }
    return cache;
  }
}
复制代码

根据我们传入的_shouldRebuild来判断是否需要更新,如果需要更新则执行widget.build(context,selected,child),否则返回已经缓存的cache.当没有_shouldRebuild参数时则根据widget.selector(ctx)的返回值判断是否和旧值相等,不等则更新UI

所以我们不写shouldRebuild也是可以的。

局部刷新用法

  Widget build(BuildContext context) {
    print('page 1');
    _string += 'page ';
    return Scaffold(
      appBar: AppBar(
        title: Text('Provider 全局与局部刷新'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Text('全局刷新<Consumer>'),
            Consumer<ProviderModel>(
              builder:
                  (BuildContext context, ProviderModel value, Widget child) {
                print('Consumer 0 刷新');
                _string += 'c0 ';
                return _Row(
                  value: value._count.toString(),
                  callback: () {
                    context.read<ProviderModel>().plus();
                  },
                );
              },
              child: _Row(
                value: '0',
                callback: () {
                  context.read<ProviderModel>().plus();
                },
              ),
            ),
            SizedBox(
              height: 40,
            ),
            Text('局部刷新<Selector>'),
            Selector<ProviderModel, int>(
              builder: (ctx, value, child) {
                print('Selector 1 刷新');
                _string += 's1 ';
                return Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Text('Selector<Model,int>次数:' + value.toString()),
                    OutlineButton(
                      onPressed: () {
                        context.read<ProviderModel>().plus2();
                      },
                      child: Icon(Icons.add),
                    )
                  ],
                );
              },
              selector: (ctx, model) => model._count2,
              shouldRebuild: (m1, m2) {
                print('s1:$m1 $m2 ${m1 != m2 ? '不相等,本次刷新' : '数据相等,本次不刷新'}');
                return m1 != m2;
              },
            ),
            SizedBox(
              height: 40,
            ),
            Text('局部刷新<Selector>'),
            Selector<ProviderModel, int>(
              selector: (context, model) => model._count3,
              shouldRebuild: (m1, m2) {
                print('s2:$m1 $m2 ${m1 != m2 ? '不相等,本次刷新' : '数据相等,本次不刷新'}');
                return m1 != m2;
              },
              builder: (ctx, value, child) {
                print('selector 2 刷新');
                _string += 's2 ';
                return Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Text('Selector<Model,int>次数:' + value.toString()),
                    OutlineButton(
                      onPressed: () {
                        ctx.read<ProviderModel>().plus3();
                      },
                      child: Icon(Icons.add),
                    )
                  ],
                );
              },
            ),
            SizedBox(
              height: 40,
            ),
            Text('刷新次数和顺序:↓'),
            Text(_string),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                OutlineButton(
                  child: Icon(Icons.refresh),
                  onPressed: () {
                    setState(() {
                      _string += '\n';
                    });
                  },
                ),
                OutlineButton(
                  child: Icon(Icons.close),
                  onPressed: () {
                    setState(() {
                      _string = '';
                    });
                  },
                )
              ],
            )
          ],
        ),
      ),
    );
  }

复制代码

效果:

当我们点击局部刷新s1,执行s1builds1不相等,s2相等不刷新。输出:

flutter: s2:5 5 数据相等,本次不刷新
flutter: s1:6 7 不相等,本次刷新
flutter: Selector 1 刷新
flutter: Consumer 0 刷新
复制代码

当点击s2,s2的值不相等刷新UI,s1数据相等,不刷新UI.

flutter: s2:2 3 不相等,本次刷新
flutter: selector 2 刷新
flutter: s1:0 0 数据相等,本次不刷新
flutter: Consumer 0 刷新
复制代码

可以看到上边2次Consumer每次都刷新了,我们探究下原因。

Consumer 全局刷新

Consumer继承了SingleCHildStatelessWidget,当我们在ViewModel中调用notification则当前widget被标记为dirty,然后在build中执行传入的builder函数,在下帧则会刷新UI

Selector<T,S>则被标记dirty时执行_Selector0State中的buildWithChild(ctx,child)函数时,根据selected_shouldRebuild来判断是否需要执行widget.builder(ctx,selected,child)(刷新UI).

其他用法

多model写法

只需要在所有需要model的上级包裹即可,当我们一个page需要2model的时候,我么通常这样子写:

class BaseProviderRoute extends StatelessWidget {
  BaseProviderRoute({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider<ProviderModel>(
          create: (_) => ProviderModel(),
        ),
        ChangeNotifierProvider<ProviderModel2>(create: (_) => ProviderModel2()),
      ],
      child: BaseProvider(),
    );
  }
}
复制代码

当然是用的时候和单一model一致的。

  Selector<ProviderModel2, int>(
    selector: (context, model) => model.value,
    builder: (ctx, value, child) {
      print('model2 s1 刷新');
      _string += 'm2s1 ';
      return Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text('Selector<Model2,int>次数:' + value.toString()),
          OutlineButton(
            onPressed: () {
              ctx.read<ProviderModel2>().add(2);
            },
            child: Icon(Icons.add),
          )
        ],
      );
    },
  ),

复制代码

watch && read

watch源码是Provider.of<T>(this),默认Provider.of<T>(this)listen=true.

static T of<T>(BuildContext context, {bool listen = true}){
final inheritedElement = _inheritedElementOf<T>(context);

    if (listen) {
      context.dependOnInheritedElement(inheritedElement);
    }

    return inheritedElement.value;
}
复制代码

read源码是Provider.of<T>(this, listen: false),watch/read只是写法简单一点,并无高深结构。

当我们想要监听值的变化则是用watch,当想调用model的函数时则使用read

参考

文章汇总

[Dart 异步与多线程][5]

[5]: https://juejin.im/post/6855129006103576584

[Flutter 详解(一、深入了解状态管理–ScopeModel][6]

[6]: https://juejin.im/post/6860001014289416205

[Flutter 详解(二、深入了解状态管理–Redux)][7]

[7]: https://juejin.im/post/6860747643493515278

[Flutter 详解(三、深入了解状态管理–Provider)][8]

[8]: https://juejin.im/post/6862150535043252237

[Flutter 详解(四、深入了解状态管理–BLoC)][9]

[9]: https://juejin.im/post/6862932168729952264

[Flutter 详解 (五、深入了解–Key)][10]

[10]: https://juejin.im/post/6863300824660082701

[Flutter 详解 (六、深入了解–Stream][11]

[11]: https://juejin.im/post/6864824541764550663

[Flutter 详解(七、深入了解绘制原理][12]

[12]: https://juejin.im/post/6865554947941859335

Flutter 详解(八、深入了解布局)

项目推荐

公众号

flutter 基础知识

From: https://www.jianshu.com/p/1481fce28a02

一. Dart入口方法

每一个flutter项目的lib目录里面都有一个main.dart。这个文件就是flutter的入口文件,其中的main方法是dart的入口方法。
runApp返回的是组件。MyApp是自定义的一个组件。在flutter里面万物皆组件。

# void是当前函数的返回值 可以省略
void main() {//程序执行的入口函数 有且仅有一个 必须的
   runApp(MyApp)
   print('Hello World!')//dart通过print方法打印数据
}
//如果runApp后面只有一行代码也可以这样写
void main() => runApp(MyApp());


//以模块接入原生的, 可以用 在入口文件中新建新的入口函数, 前面加入  @pragma('vm:entry-point')
例:
 @pragma('vm:entry-point') void mainWallet(){} 

二. Dart变量,常量和命名规则

  1. 变量
    dart是一个强大的脚本类语言,可以不预先定义变量类型,dart会通过类型推断 type inference(swift也是)自动判断当前变量的类型。dart中定义变量是通过var关键字或者通过类型来声明变量。

    var str = ‘这是var变量’;
    String str1 = ‘这是类型声明的变量’;
    int number = 123;
    注意: var 后不要写类型,写了类型不要写var 写了会报错 var a int = 5

  1. 常量

常量通过final或者const修饰。

差异之处:const修饰的常量在创建时就需要赋值(编译时的常量)赋值后不可改变。final不仅有const的编译时的常量特性,最重要的是它的运行时常量。并且final是惰性初始化,即在运行时第一次使用前才初始化。如果是不改量的量,请使用final或者cont修饰它,而不是使用var或者其他变量类型。

final name = ‘huang’;
final String rename = 'hai';
const bar = 100000; 
cont double atm = 1.01325 * bar 
  1. dart的命名规则
    • 变量等名称必须由数字、字母、下划线和美元符号($)组成。
    • 注意:标识符开头不能是数字。
    • 标识符不能使保留字或者关键字。
    • 变量的名称是区分大小写的如:age和Age是不同的变量。在实际的运用中,也建议不要用一个
    • 标识符(变量名称)一定要见名思意 :变量名称建议用名称,方法名称建议用动词

三. Dart基础数据类型

  • 字符串(String)

① 字符串创建方式
通过var关键字或者String单引号、双引号、三个单引号或者三个双引号来包裹字符组成字符串变量或者常量。
②字符串拼接,通过+号或者通过 $加变量 $str或者${表达式}

对应的①
/单引号 '' 或者 双引号 "" 来包裹字符组成字符串
  String str1 = '这是字符串';
  String str2 = "这是字符串";
  //使用 + 链接两个字符串
  String str3 = str1 + str2;
  print(str3);
  //多行字符串使用三重单引号 '''字符串'''
  //三重双引号 """字符串"""
  String str4 = """ 1111
  22222
  333333""";
  print(str4);
  //使用r前缀 可以使字符串里面的特殊字符作为普通字符串
//  \n 换行符 特殊字符
  String str5 = r'这是特殊字符\n现在不会换行';
  String str6 = '这是特殊字符\n现在会换行';
  print(str5);
  print(str6);
对应的②
var str4 = str + str1;
print('$str4 ${str4.length}');
// dart判断数据类型 is关键词用来判断数据类型
  if(str4 is String){
    // print()
  }
  • num(数字)

num是数字类型的父类,有两个子类intdouble

* int 整型 数值范围在-2的53次方到2的53次方减1
* double双精度浮点型
* int 必须是整型 double 既可以接收整型也可以接收浮点型

//print函数是控制套输出函数
  print('a 是 $a');
  print('你好dart');
  double c = 10;//double可以包含整型 整型 dart sdk 2.1 之前不能使用,会报错
  double d = 10.1;
  print('d = $d');

  num aa = -3; //num 是number类型的简写  abs()取绝对值
  print('asdasdas' + aa.abs().toString());
//ceil() 带小数就向前进一位取大于或者等于表达式的最小整数
  num h = 8.3;
  num i = h.ceil();
  print(i);
//floor()舍掉小数位 不管他多大
num j = 10.9;
num k = j.floor();
//round()四舍五入
  • boolean

    • 布尔 关键字 bool 值 true 或者 false
    • 布尔类型的合法值只有两个 true 和 false 在条件判断语句中dart不会对条件语句进行类型转换
    • 与其他语言不同 例如 JavaScript 中 布尔是非空都为true dart中只有值为true才可以 比如就算一个字符串不是空 但是他的返回值不是bool值 所以是false

    bool value = true;
    // bool value2 = ‘asda’;//不合法
    bool value2 = false;
    if(value){

    print(value);
    

    }

四. Dart集合

  • List集合(数组)

    //第一种定义List的方式
    var list222 = [“asdsa”,”asdasd”,”asdsadff”];
    list222.length; 获取长度
    list222[0];获取第一个值
    //第二种定义List的方式
    var list111 = List();
    list111.add(‘asdsdddda’);//增加新值
    list111.addAll([‘hahah’,’asdasd’]); 可以添加新的数组
    //在定义List时 直接指定其类型
    var List2223 = List();//定义了一个只能放String类型的数组 调用其构造函数创建数组
    //数组 里面的元素(值)可以重复 值的类型可以不同
    List list1 = [1,2,3,4,5,6,7,8];
    List list2 = [1,true,’qwe’,1];

  • List里面常用的属性和方法 Map Set差不多通用

    //常用属性
    length 长度
    reversed 数组翻转 对列表倒序排序
    isEmpty 是否为空
    isNoTEmpty 是否不为空
    //常用方法
    add 增加元素 增加一个
    addAll 拼接数组
    indexOf 查找数据 传入具体值 查到了返回元素所在位置索引 查找不到返回-1
    remove 删除 传入具体值
    removeAt 删除 传入索引值
    fillRange 修改数据 传入开始索引和结束索引 然后传入要添加的值(1,10 ,’修改的值’);
    insert(index,value); 指定位置插值 (1 要添加的位置在索引为1的元素前添加 ,’要添加的值‘)
    insertAll(index,List); 指定位置插入List 和上面的一样 只是传入的是数组
    toList() 其他类型转换为List
    join() List转换为字符串 传入变成字符串后的分割元素之间的符号
    split() 字符串转换为List 传入通过哪些符号来分割字符串 变成List
    forEach
    map
    where
    any
    every

  • Maps(字典) 也可以叫对象是无序的键值对

    • 常用属性:
      • keys 获取所以key值
      • values 获取所有Value值
      • isEmpty 是否为空
      • isNotEmpty是否不为空
    • 常用方法
      • remove(key) 删除指定key的数据
      • addAll({….}) 合并映射字典 给映射增加属性
      • containsValue 查看字典内是否有某个值 返回true false

    //将key和Value相关联的对象
    //key和Value都可以是任何对象
    //定义方式 map字面量来定义
    var person = {
    “name” : “zhangsna”,
    “age” : 20,
    “work” : [‘haha’,’hehe’]
    };
    //可以直接添加或者修改值
    person[“name”]; //取值
    var per = Map();
    // Map类型
    Map dic = {‘name’: ‘zhangsna’, ‘age’: 20};
    Map dic2 = new Map();//dart2.0版本后可以不用写new关键字
    var dic3 = new Map();
    //从map中取值 如果map对象中没有对应的键 返回null
    print(tempMap2[‘sencond’].toString());
    // .length 取得map对象的长度

  • Set

集合里面的值不可以重复,值的类型必须统一。最主要的功能是去除数组中重复的内容。Set是没有顺序且不能重复的集合,所以不能通过索引去获取值。

定义有两种形式 Set字面量 Set类型
  var s = Set();
  var set1 = {'value1','value2'};//Set字面量
  //Set类型
  //变量类型 决定
  Set set2 = {};
  //类型参数
  var set3 = <String>{};//指定元素为String类型的Set集合
  Set<String> set4 = {};
  //.add()添加至到集合
  set4.add('value1');
  print(set4);
//  .addAll 将元素为同类型的集合添加到现有集合
  set4.addAll(set1);
  print(set4);
// .length 得到集合的长度 里面多少个元素或者值
  print(set4.length.toString());
  • 常用方法

①. forEach() 传入函数表达式 遍历

list.forEach((value){ 遍历数组 会把数组的每一个元素赋值给Value
     print("$value");
});

②. map方法和JS里map方法很像,遍历一个数组同时返回一个数组,遍历同时会给出每个元素value。

var newList = lsit.map((value){
   return value*2;
})

③. where方法遍历数组得到元素同时可以加判断语句

var newList = list.where((value){
    return value>5;
})

④. any方法遍历一个数组是否存在某个元素或者符合某些条件 返回 true false 只要集合里面有满足条件就返回true

var newList = list.any((value){
    return value>5;
})

⑤. every方法遍历一个数组是否存在某个元素 或者 符合某些条件 每一个都满足条件返回true否则返回false

var newList = list.every((value){
     return value>5;
})

五. 运算符

  • 算术运算符 + 、- 、* 、/ 、~/取整 、 %取余、 ++ 、 – 、 += 、+-

    a = b++; 会先把b赋值给a 然后在加 ++ – 标识自增 自减 1
    a = ++b; 把b加上1 在赋值给a
    //在赋值运算里面如果++ – 写在前面 这个时候先运算 在赋值 如果++ – 写在后面 先赋值后运算
    int aaaa = 13;
    int bbbb = 5;
    print(aaaa+bbbb);
    print(aaaa-bbbb);
    print(aaaa*bbbb);
    print(aaaa/bbbb);
    print(aaaa~/bbbb);
    print(aaaa%bbbb);

  • 关系运算符 == != > < >= <= 二元运算符

    print(aaaa == bbbb);
    print(aaaa != bbbb);
    print(aaaa > bbbb);
    print(aaaa < bbbb);
    print(aaaa <= bbbb);
    print(aaaa >= bbbb);

  • 逻辑运算符 ! 取反 && 并且 || 与

    • && 并且 条件全部为true 为true 否则false
    • || 或者 或 全部为false 为false 否则 true
  • 基础赋值运算符 = ??= 赋值

    b ??= 23; 表示如果b为空的话把23赋值给b

  • 复合赋值运算符 += 、 -= 、 *= 、 %= 、 ~/=

  • 三目运算符 它是唯一有3个操作数的运算符,也叫三元运算符。一般来说,三目运算符的结合性是右结合的。

    var flag = true;
    var ccc = flag ? ‘正确’ : ‘错误’;

  • ?? 运算符

    var aaa = 222;
    var bbb = a ?? 333; //当a的值为空的时候把333赋值给bbb

六. 类型转换

  • Number和String类型之间的转换

    • Number类型转换为String类型用toSting()
    • String转换为Number 用parse 通过int调用parse函数把要转换的字符串传进去

    String ahahah = ‘123’;
    var nnnnn = int.parse(ahahah);
    var nnnnn = double.parse(ahahah);//转换为double
    //如果传入的值为空 可以通过 try catch来判断
    try{

    var nnnnn = int.parse(ahahah);
    

    }catch (err){

    print('error');
    

    //如果转换失败会跳到catch中
    }
    var aaasaa = 222;
    aaasaa.isNaN //判断是否为空
    print(aaaa.toString());

七. 循环语句 流程控制语句(控制流)

  • for循环
  • 流程:

    1. 声明变量int i = 0
    2. 判断i <= 100
    3. print(i);
    4. i++
      5.从第二步再来,直到判断为false

      for(int i = 0 ; i <= 100 ; i++ ){
      print(i);
      }
      // 打印0到50所有的偶数
      for(int i = 0 ; i <= 50 ; i++ ){
      if(i % 2 == 0){

      print(i);
      

      }
      }
      //求1+2+3+4+5+++100的和
      var sum = 0;
      for(int i = 1 ; i <= 100 ; i++ ){

      sum += i;
      }
      //5050;

  • while 循环 和 do while 循环

    • 语法格式
    • 注意点
      • 最后的分号不要忘记
      • 循环条件中使用的变量需要经过初始化
      • 循环体中,应有结束循环的条件否则会死循环
      • while语句可能一次都不执行 do while 肯定会执行一次
    • 不同点 第一次循环条件不成立的情况下 while不执行循环体 do while肯定会执行一次

      while(表达式/循环条件){
      循环体
      }

      do{
      语句/循环体
      }while(表达式/循环条件)

  • if else

  • switch case

  • break语句

    1. 在switch语句中使流程跳出switch结构
    2. 在循环语句中使流程跳出当前循环,遇到break 循环终止,后面的代码不会执行
  • break语句注意点
    1.如果在循环中已经执行了break语句,就不能执行循环体中位于break后的语句。
    2.在多层循环中,一个break语句只能向外跳出一层。
    3.break可以用在switch case 中也可以用在for循环和while循环中。

  • ontinue语句

    • [注]只能在循环语句中使用,使本次循环结束,既跳过循环体重下面尚未执行的语句,接着进行下continue可以用在for循环以及while循环中,但是不建议用在while循环中,不小心容易死循环

八. Dart函数

dart中的函数 函数的定义 可选参数 默认参数 命名参数 箭头函数 匿名函数 闭包等 函数也叫方法 在类外面叫函数 在类内部叫方法 这个都无所谓 都可以叫函数 也可以叫方法

  • 自定义方法函数:

    自定义方法的基本格式
    返回类型 方法名称(形式参数1,形式参数2,……){
    方法体 具体执行逻辑
    return 返回值;
    }
    print();//内置方法/函数

  • 定义一个带可选参数的方法

    String method (String name ,[int age,String sex]){
    //形参 可选参数放到参数后面 用[]中括号包裹 用,逗号隔开
    }

  • 定义一个带默认参数的方法

    String method (String name ,[String sex = ‘男’,int age]){
    //形参 如果有默认参数 建议放到 不带默认参数的可选参数前面
    }

  • 定义一个命名参数的方法

    String method (String name ,{String sex = ‘男’,int age}){
    //参数带名称的参数 需要用大括号包裹{}并且里面也可以设置默认参数
    }

  • 实现一个把函数当做参数的方法

    fn1(){
    print(‘fn1’);
    }
    fn2(fn){
    fn();
    }
    fn2(fn1);
    //把方法函数fan1当做另一个方法fan2的形式参数传进去 然后执行

  • 匿名函数 没有名字的函数

    var fn = (){//没有方法名称
    print(‘我是一个匿名方法’);
    }//直接通过变量fn调用方法
    var printNumm = (){
    //表示把一个函数赋值给了printNumm这个变量 调用方式和普通调用方法一样 传值方式和普通方法一样
    }

  • 箭头函数 箭头函数后面只能写一句代码

    list.forEach((valye) => print(value));
    list.forEach((value) => {//里面也只能写一句代码
    print(value)//不用写分号
    })

  • 自执行方法 不主动去调用 方法自己去执行

    ((int n){
    //方法在创建好后会自动执行 因为方法后面有括号会直接调用这个方法 可以传入参数 和 接收参数 还可以指定类型
    print(‘我是自执行方法’);
    })(12);
    //就相当于在函数外面包裹了一个函数

  • 方法的递归 一个方法可以调用自己 记得写判断语句 当符合条件后跳出 否则死循环

    var sum = 1;
    fn(int n ){

    sum*=n;
    if(n == 1){
      return;
    }
    fn(n-1);//关键这句 在符合条件后在此执行当前方法 
    

    }
    fn(10);

  • 闭包:函数嵌套函数,内部函数会调用外部函数的变量或参数.

    1. 全局变量特点:全局变量常驻内存,全局变量污染全局
    2. 局部变量的特点:不会常驻内存 会被垃圾回收机制回收,不会污染全局
  • 通过闭包可以实现:常驻内存、不污染全局,产生了闭包,闭包可以解决这个问题。

闭包写法:函数嵌套函数,并return 里面的函数,这样就行成了闭包

fn(){ 
 var a = 123; /*不会污染全局 常驻内存 *//
 return (){
 a++;
 print(a);
 }
}
print(fn());

九. 类

dart所有的东西都是对象,所有的对象都继承自Object类。是一门使用类和单继承的面向对象语言,所有的对象都是类的实例,并且所有的类都是Object的子类。

一个类通常由属相和方法组成

1. 定义Person类
class Person{//类名首字母大写
  String name = '张三';
  int age = 23;
 //dart里面的构造函数可以写多个但是默认构造函数只能有一个
 Person(String name,int age){
  this.name = name;
  this.age = age;
  print('这是构造函数里面的内容,这个方法在实例化的时候触发')
 }
 //默认构造函数简写
 Person(this.name,this.age);
 //命名构造函数 可以有多个
 Person.now(){
    print('我是命名构造函数');
 }
  void getInfo(){
  print("${this.name}--$age");//this指当前类 类似self 通过this.需要用{}大括号包裹
}
}
2. 实例化类
var p1 = Person();//2.0.0后不用new关键字 写也可以 推荐不写
Person p2 = Person();//默认实例化类的时候调用的是默认构造函数
Person p3 = Person.now();//命名构造函数
var time = DateTime.now(); //实例化datetime 调用它的命名构造函数
//dart和其他面向对象语言不一样 dart中没有public private protected这些访问修饰符
但是我们可以使用 “_” 下划线 把一个属性或者方法定义成私有
String _name;//私有属性 私有属性可以通过共有的方法来访问 间接访问私有属性
_run(){//私有方法
    print('这是一个私有方法');//也可以通过公有方法来间接调用私有方法
}
alert(){
    this._run();//通过公有方法访问私有方法 私有的不能直接访问
}
3. 类中的getter和setter修饰符
get 名称{ //getter方法 也就方法获取数据
   return "返回值"
}
set 名称(形式参数){
//参数名称 = 形式参数;
}
4. 类中的初始化列表

dart中我们也可以在构造函数体运行之前初始化实例变量

int height;
int width
Rect():height = 2 , width= 3{//在实例化之前的操作

}
5. 静态成员

dart类中的静态成员:

  1. 使用static 关键字来实现类级别的变量和函数
  2. 静态方法不能访问非静态成员,非静态方法可以访问静态成员
  3. 静态方法成员变量不能在通过类的实例化对象访问 直接通过类来访问

    static String name = “zhangsan”;

    static void show(){

    }

    void getInfo(){//非静态方法可以访问静态成员以及非静态成员

    }

6. dart中的对象操作符
  • ? 条件运算符
  • as 类型转换
  • is 类型判断
  • .. 级联操作(连缀)

    p1?.方法 如果p1对象不存在会自动返回 如果存在会访问方法
    (p1 as Person).方法或者属性 //类型转换 转换为自己需要的类型
    Person p1 = Person();
    p1..name = “hhhh”;
    ..age = 35;//连缀操作符 访问属性或者方法不用对象名可以直接访问

7. dart中的类的继承

通过super关键字来继承父类的属性和方法
重写父类方法是时@override关键字 建议加 可以不加
super.父类里面的方法 通过super调用父类的方法

  1. 子类使用extends类关键字来继承父类
  2. 子类会集成父类中可见的属性和方法 但是不会继承构造函数
  3. 子类能重写父类的方法getter和setter方法
8. dart中的抽象类 多态 和接口

dart抽象类主要用于定义标准,子类可以继承抽象类,可以实现抽象类的接口

  1. 抽象类通过abstract关键字来定义
  2. dart中的抽象方法不能用abstract声明,dart中没有方法体的方法我们称为抽象方法
  3. 如果子类继承抽象类必须得实现里面的抽象方法
  4. 如果把抽象类当做接口实现的话必须得实现抽象类里面定义的所有属性和方法
  5. 抽象类不能被实例化,只有继承它的子类可以

继承抽象类extends和implements关键字的区别

  1. 如果复用抽象类的方法,并且要用抽象方法约束自类的话我们就要用extends继承抽象类
  2. 如果只是把抽象类当做标准的话我们就用implements实现抽象类、

接口:就是约定规范

首先dart的接口没有interface关键字定义接口,而是普通类或者抽象类都可以作为接口被实现
同样适用implements关键字进行实现
但是dart的接口有点奇怪如果实现的类是普通类 会将普通类和抽象中的属性的方法全部需要重写一遍
而因为抽象类可以定义抽象方法,普通类不可以,所以一般如果要实现像Java接口那样的方式,一般会使用抽象类。
建议使用抽象类定义接口

abstract class Db{
    add();//只写方法不实现 继承它的子类需要实现它的方法属性
}
class Mysql implements Db{
    @override
    add(){

    }
}

dart中一个类实现多个接口 以及dart中的Mixins混入

abstract class a{
  add();//只写方法不实现 继承它的子类需要实现它的方法属性
}
abstract class b{
  remove();//只写方法不实现 继承它的子类需要实现它的方法属性
}
class implements a,b{
  //需要实现上面俩个抽象类的方法和属性 这叫一个类实现多个接口
}

Mixins的中文意思是混入 就是类中混入其他功能,在dart中可以使用mixins实现类似多继承的功能,因为mixins使用条件 随着dart的版本一直在变 这里讲的是dart2.x中使用mixins的条件

  1. 作为mixins的类只能继承自object 不能继承其他类
  2. 作为mixins的类不能有构造函数
  3. 一个类可以mixins多个mixins类

mixins不是继承也不是接口 而是一种全新的特性,mixins的实例类型就是其超类的子类型 c 混入a b 就是ab的子类型。通过with关键字实现class c with a,b{}c继承了a和b class c extends Person with a,b {}c继承于Person类同时混入了a b 如果继承的有同样的方法和属性 后面的会覆盖前面的

9. 泛型 泛型方法 泛型类 泛型接口

通俗理解:泛型就是解决 类 接口 方法的复用性,以及对不特定数据类型的支持(类型效验)

泛型一般在方法前面加T 啥都行 说明是泛型

①. 泛型方法
T getDate<T>(T value){
return value;
}

调用:

一般 getDate(123) 这个没有类型校验 传啥返回啥。
类型校验 getData(‘123213’); 这个String会传给尖括号的T代表这个方法的返回值是String 接收值也是String

②. 泛型类
class Person<T>{
    List list = List<T>();
    void ad(T value){
        this.list.add(value);
    }
}

dart中的泛型接口:

  1. 定义一个泛型接口 约束实现它的子类必须有getByKey(Key) 和 setBuKey(key,value)
    2.要求setByKey的时候value的类型和实例化子类的时候指定的类型一致

    abstract class Cache{
    getByKey(steing key);
    void setByKey(string key ,T value);

    }
    class FlieCache implements Cache{
    //继承泛型类 把当前类定义的泛型传给父类的泛型
    void setByKey(string key ,T value){

    }
    }

③. async和await
  • 只有async方法才能使用await关键字调用方法,如果调用别的async方法必须使用await关键字
    • async是让方法变成异步
    • await是等待异步方法执行完成

记录:

函数如果有一行的话可以使用箭头函数 只要超过一行就不能使用箭头函数 (value) => print(value);
dart中所有的类都继承于Object类
$ 符 字符串插值运算符 $+变量名

//引入别的类
import 'package:async/async.dart';

Rune 符号文件 用来表达Unicode字符
Unicode为所有世界写作系统中使用的每个字母、数字和符号定义了唯一的数值。
Unicode采用UTF-32位编码 dart采用了UTF-64编码
为了在字符串中表达32位的Unicode值需要用到特殊语法
\uXXXX 以\u开始后面跟着4个十六进制数(XXXX) , x的取值范围是0到f

var tempStr = '\u0F00';
print(tempStr);

当到指定多于或者少于4个十六进制数字是,使用{}包裹该值

var smeil = '\u{1F600}';
print(smeil);

全局函数 可以在main方法中调用 全局作用域 方法参数被称为形式参数 形参 调用方法传入的参数被称为实际参数 实参

//自定义方法
void printInfo(){ 
  print('我是一个自定义方法');
  int getNum(){//方法里面还可以嵌套方法 一个返回值为int的方法 这个方法只能在 当前函数体内调用 局部作用域
    var myNum = 111;
    return myNum;
  }
}

Dart重要概念:1,在变量中可以放置的所有东西都是对象,而每个对象都是类的实例。无论数字、函数、和null都是对…


From: https://blog.csdn.net/IT_Boy_/article/details/106380206

1.获取状态栏高度

1.第一种,注意:这里需要导入 ‘dart:ui’ 包

import 'dart:ui';
MediaQueryData.fromWindow(window).padding.top

2.第二种,

MediaQuery.of(context).padding.top

说到状态栏,就要说个安全区域的概念:所谓安全区域,就是适配现在一些刘海屏之类的非常规显示屏,在flutter中除了根据上面的方法获取到状态栏高度,给页面加对应的状态栏高度padding,还有一个专门的widget用来显示安全区域内容:SafeArea

2.获取appBar高度

位于 Dart Packages/flutter/src/material/constans.dart

///  * [kMinInteractiveDimensionCupertino]
///  * The Material spec on touch targets at <https://material.io/design/usability/accessibility.html#layout-typography>.
const double kMinInteractiveDimension = 48.0;

/// The height of the toolbar component of the [AppBar].
const double kToolbarHeight = 56.0;

/// The height of the bottom navigation bar.
const double kBottomNavigationBarHeight = 56.0;

3.获取手机屏幕宽高

Material 设计规范中 状态栏、导航栏、ListTile高度分别为 24、56、56

MediaQuery.of(context).size.width
MediaQuery.of(context).size.height

我把 MediaQuery.of(context) 的值输出来了,不同的机型有些值是不同的, 其实 MediaQuery.of(context) 输出的内容和 MediaQueryData.fromWindow(window) 输出的内容是一样的

MediaQueryData(
  size: Size(360.0, 592.0),
  devicePixelRatio: 2.0,
  textScaleFactor: 1.0,
  platformBrightness: Brightness.light,
  padding: EdgeInsets(0.0, 24.0, 0.0, 0.0),
  viewPadding: EdgeInsets(0.0, 24.0, 0.0, 0.0),
  viewInsets: EdgeInsets.zero,
  physicalDepth: 1.7976931348623157e+308,
  alwaysUse24HourFormat: true,
  accessibleNavigation: false,
  disableAnimations: false,
  invertColors: false,
  boldText: false,
);

flutter 中线条,
1 主要是利用DecoratedBox的decoration属性:

1
Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ FlatButton( child: Text('打开'), ), //垂直分割线 SizedBox( width: 1, height: 12, child: DecoratedBox( decoration: BoxDecoration(color: Colors.grey), ), ), FlatButton( child: Text('关闭'), ) ], );

2 或者更简单的,直接使用VerticalDivider:

1
VerticalDivider( color: Colors.grey, width: 1, )

3 部分需求中 Container 的外边框 属性中

1
2
3
4
5
6
decoration: new BoxDecoration(
color: Colors.white,
border: new Border(bottom: BorderSide(width: 1,color: ColorTool.grayE0(),style:BorderStyle.solid )),
//设置Border属性给容器添加边框
)

flutter 点击空白处回收键盘

1
2
3
4
5
6
7
8
9
10
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
// 触摸收起键盘
FocusScope.of(context).requestFocus(FocusNode());
},
child: Container(
padding: EdgeInsets.fromLTRB(0, 0, 0, 0),
child: XXXX);
`

Flutter Dio包网络请求抓包解决方案

From: https://segmentfault.com/a/1190000023654714

发布于 8月17日

在Flutter中进行网络请求时,我们可以使用的库有3个,即Http请求库、HttpClient请求库和Dio请求库(详细介绍请参考:Flutter开发之Http网络请求),使用得最多的就是Dio请求库。因为相比Http请求库和HttpClient请求库,Dio库不仅支持常见的网络请求,还支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时等操作。

不过,默认情况下,Dio进行网络请求时是不支持抓包的,所以如果要进行抓包,就需要对Dio进行请求封装,并编写代理代码。下面是代理的几种写法:

方法一

我们可以直接在Dio里面设置ip以及端口,通过硬编码的方式进行代理,代码如下:

(_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
          (client) {
        //这一段是解决安卓https抓包的问题
        client.badCertificateCallback =
            (X509Certificate cert, String host, int port) {
          return Platform.isAndroid;
        };
        client.findProxy = (uri) {
          return "PROXY 代理ip:代理port";
        };
      };

不过,这种硬编码方式,写得太死,不够灵活,每次更改代理都需要打包。

方法二

直接在原生插件获取手代理ip和代理端口,不过Android比较难,下面是iOS的实现。

//自动获取手机代理
 NSDictionary *proxySettings = (__bridge NSDictionary *)(CFNetworkCopySystemProxySettings());
          NSArray *proxies = (__bridge NSArray *)(CFNetworkCopyProxiesForURL((__bridge CFURLRef _Nonnull)([NSURL URLWithString:call.arguments]), (__bridge CFDictionaryRef _Nonnull)(proxySettings)));
          NSString *hostName = proxySettings[@"HTTPSProxy"];
          NSString *portName = [NSString stringWithFormat:@"%@",proxySettings[@"HTTPPort"]];
          long HTTPEnable = [proxySettings[@"HTTPEnable"] longValue];
          if (HTTPEnable==0) {
              hostName = @"";
          }

方法三

除了上面的硬编码方式外,我们还可以采用scheme协议的方式传入代理ip和代理端口。此方法的步骤如下:
1,注册自己的URL Scheme,例如:scheme://
2,定义参数规则,例如:scheme://tiaoshi?host=10.0.206.163
3,引入flutter插件:uni_links: ^0.2.0
4,flutter监听解析参数,并在dio里面设置代理
5,使用[草料]https://cli.im生成一个二维码:内容:scheme://tiaoshi?host=10.0.206.163
6,使用原生相机扫码进入app就可以抓包

下面是涉及的代码,Flutter代码如下:

Future<Null> initUniLinks() async {

    // 监听插件scheme数据
      getLinksStream().listen((String link) {
        link =  Uri.decodeComponent(link);
        if(link.contains("scheme://")){
          String type = getTypeStr(link);
          String param = link.replaceAll("scheme://$type?", "");
          Map dict = getUrlParams(param);
          if(type=="tiaoshi"){//设置抓包代理
            String host = dict["host"];
            String port = dict["port"];
            //这里是网络请求封装
            Net.setHttpProxy(host,port==null?"8888":port);
           }
        }
      // Parse the link and warn the user, if it is not correct
    }, onError: (err) {
      // Handle exception by warning the user their action did not succeed
    });
  }

//获取scheme 要处理的业务类型
  String getTypeStr(String link){
    List params = link.split("?");
    String typeStr = params[0];
    typeStr =  typeStr.replaceAll("scheme://", "");
    return typeStr;
  }

//url参数转map
  Map getUrlParams(String paramStr) {
    Map map = Map();
    List params = paramStr.split("&");
    for(int i=0;i<params.length;i++){
      String str = params[i];
      List arr = str.split("=");
      map[arr[0]]= arr[1];
    }
    return map;
  }

代理层代码:

static void setHttpProxy(String host,String port) {
    Application.httpProxy = host+':'+port;
    _initDio();
  }

static Future<void> _initDio() async {
    DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
    if (Platform.isAndroid) {
      _androidInfo = await deviceInfo.androidInfo;
    } else if (Platform.isIOS) {
      _iosInfo = await deviceInfo.iosInfo;
    }

    _dio = Dio(BaseOptions(
      contentType: 'application/json',
      baseUrl: Config.BASE_URL,
    ));
    _dio.options.receiveTimeout = 5000;
    _dio.options.connectTimeout = 10000;

    if (Application.httpProxy.length != 0) {
      (_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
          (client) {
        //这一段是解决安卓https抓包的问题
        client.badCertificateCallback =
            (X509Certificate cert, String host, int port) {
          return Platform.isAndroid;
        };
       //这是抓包代理
        client.findProxy = (uri) {
          return "PROXY ${Application.httpProxy}";
        };
      };
    }
    _dio.interceptors.addAll([
      InterceptorsWrapper(
        onRequest: (Options options) {
          options.headers['DeviceName'] = 'xxxx';
          return options;
        },
        onResponse: (Response res) {
          try {

          ...

            return res;
          } catch (e) {
            return res;
          }
        },
        onError: (DioError e) {
          print(e);
         }
              break;
            default:
          }
          return e;
        },
      ),
    ]);
  }

static Future<ResponseModel> get(
    String path, {
    Map<String, dynamic> queryParameters,
    Options options,
    CancelToken cancelToken,
    void Function(int, int) onReceiveProgress,
  }) async {
    if (_dio == null) {
      await _initDio();
    }

    final res = await _dio.get<ResponseModel>(
      path,
      queryParameters: queryParameters,
      options: options,
      cancelToken: cancelToken,
      onReceiveProgress: onReceiveProgress,
    );

    return res.data;
  }

  static Future<ResponseModel> post(
    String path, {
    dynamic data,
    Map<String, dynamic> queryParameters,
    Options options,
    CancelToken cancelToken,
    void Function(int, int) onSendProgress,
    void Function(int, int) onReceiveProgress,
  }) async {
    if (_dio == null) {
      await _initDio();
    }
    final res = await _dio.post<ResponseModel>(
      path,
      data: data,
      queryParameters: queryParameters,
      options: options,
      cancelToken: cancelToken,
      onSendProgress: onSendProgress,
      onReceiveProgress: onReceiveProgress,
    );

    return res.data;
  }

阅读 779 发布于 8月17日

本作品系原创, 采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议


Flutter 记录1

使用 json_serializable 时 执行命名报错

1
2
Could not find package "build_runner". Did you forget to add a dependency?
pub finished with exit code 65

解决 加入 build_runner: ^1.10.0 依赖

https://caijinglong.github.io/json2dart/index_ch.html

https://pub.dev/packages/g_json


flutter 在xcode 上调试

在项目文件下 执行(App 的FlutterEdgin 执行后)

1
2
3
4
5
6
//
flutter attach
// 调试模式
flutter attach --isolate-filter='debug'
// 加上调试的
flutter attach --app-id com.hzzhebei.zbios

单例

Flutter中,dart的单例模式设计
创建一个单例的Manager类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Manager {
// 工厂模式
factory Manager() =>_getInstance()
static Manager get instance => _getInstance();
static Manager _instance;
Manager._internal() {
// 初始化
}
static Manager _getInstance() {
if (_instance == null) {
_instance = new Manager._internal();
}
return _instance;
}
}

调用
// 无论如何初始化,取到的都是同一个对象

1
2
Manager manager = new Manager();
Manager manager2 = Manager.instance;

—–main 函数之前执行一些函数报错—

If you’re running an application and need to access the binary messenger before runApp() has been called..

1
2
// main 函数第一行加入
WidgetsFlutterBinding.ensureInitialized();

—- 出现json 自动化生成错误, 报错non-nullable 一直循环, 清理项目解决

1
2
3
flutter clean
flutter packages pub upgrade
flutter pub run build_runner build

From: https://blog.csdn.net/worship_kill/article/details/102892349

在使用Flutter的过程中,想去自己封装一个转model的类,后来写着写着发现根本没法弄,于是就去中文官网看看。

接着就看到了json_serializable,然后就照着做呗,导入package,导入头文件,声明@JsonSerializable(),创建类,属性。然后

flutter packages pub run build_runner build,就没然后了,

没有生成.g.dart,命令行到最后还提醒Succeeded,我就蒙蔽了。

于是反复去工程里查找,就是没找到,于是乎,我又重新创建一个新的工程,再导入一遍,还是不行,我就去github上json_serializable找源码查看,然后试着将

part ‘model.g.dart’;

敲上去,发现代码是报错的,这时候再运行一次flutter packages pub run build_runner build

于是乎就在Model.dart文件下生成了.g.dart文件,欲哭无泪。

附带一个标准生成.g.dart的写法:

首先在pubspec.yaml文件里面导入依赖库,Ctrl + S或者点击一下右上角的下载箭头(VS Code),getPackage一下,注意文字的对齐

dependencies:
  flutter:
    sdk: flutter
  json_annotation: ^2.0.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  json_serializable: ^2.0.0
  build_runner: ^1.0.0

然后来到你的model类里面

复制过去(注意现在代码是报错的),只用改类名和属性名,其他不用管

运行一次

import 'package:json_annotation/json_annotation.dart';

part '你的类名.g.dart';

@JsonSerializable()

class  你的类名 {
  var name;
  var age;
  你的类名(this.name,this.age);
  factory 你的类名.fromJson(Map<String,dynamic> json) => _$你的类名FromJson(json);
  Map<String,dynamic> toJson() => _$你的类名ToJson(this);

}

cd 你的工程目录

flutter packages pub run build_runner build //使用 build_runner 生成 .g.dart 文件

flutter packages pub run build_runner watch //监控生成文件,如果有改动时自动生成/更新 .g.dart 文件

就ok了

还没有生成.g.dart文件或者报错的运行下面的命令

cd 你的工程目录

flutter packages pub run build_runner build –delete-conflicting-outputs //删除并重新创建.g.dart文件

没有生成的再运行一下

flutter packages pub run build_runner build