flutter trip

  • 2014 年 10 月 open the sky 诞生

  • 2015 年 10 月 open the sky 改名 Flutter

  • 2017 年 5 月 google I/O 大会上向外界公布了 Flutter

  • 2018 年 6 月 Flutter 1.0预览版发布

  • 2018 年 12 月 1.0 版本发布

  • 2019 年 2 月 1.2 版本发布,主要增加了对 web 的支持

  • dart 默认使用 main 函数作为程序的入口,也可以自己指定其他函数作为程序的入口。

  • main函数会调用几次?这个跟启用Flutter 引擎的次数有关,如果只启用一个引擎那么就调用一次,如果启用多个引擎就会调用多次?

  • dart 是类型安全的,它使用静态类型检查和运行时的组合,检查以确保变量的值始终与变量的静态值匹配类型。尽管类型是必需的,但是某些类型注释是可选的,因为 dart 会执行类型推导

  • dart中未初始化的变量其初始值默认置为null。

  • dart 中数字也是对象

  • dart 中只有 bool 值被当做 true或 false

  • dart 的 null 检查,dart 1.12 版本开始,null-aware 运算符可以帮助我们做 null 的检查 。
    null-aware(null感知运算符) 操作符语言特性:

`??`: if null operator. `expr1 ?? expr2` evaluates to `expr1` if not `null`, otherwise `expr2`.
`??=`: null-aware assignment. `v ??= expr` causes `v` to be assigned `expr` only if `v` is `null`.
`x?.p`: null-aware access. `x?.p` evaluates to `x.p` if `x` is not `null`, otherwise evaluates to `null`.
`x?.m()`: null-aware method invocation. `x?.m()` invokes `m` only if `x` is not `null`.

?.运算符在左边为 null 的情况下会阻断右边的调用,
?? 运算符主要作用是在左侧表达式为 null 时为其设置默认值
对于 bool temp = dict[a]?.contains(b) ?? false;
如果 dict[a] 为 null ,或者 contains(b)的值为 null,都会导致表达式dict[a].contain(b)为 null,当表达式为 null 的时候将 false 赋值给 temp.注意:这里需要保证dict不为null,如果dict为null,则会报NoSuchMethodError: The method '[]' was called on null.的错误;

  • flutter 采用声明式UI,为了减轻开发人员在各种 UI状态之间转换的编程负担,flutter 让开发人员描述当前的 UI状态,并且不需要关心她是如何过渡到框架。
    flutter 构建新的 widget实例,而不是更改旧的Widget实例,该框架使用 RenderObjects 管理传统 UI 对象的许多职责(例如维护布局的状态)。RenderObjects 在帧之间保持不变,flutter 的轻量级 Widget 告诉框架在状态之间改变 RenderObjects,接下来 flutter 框架会处理其余的部分

  • 要使用更基本的窗口 widget 集,需要导入 widget 库;import 'package:flutter/widget.dart';

  • flutter中有两套组件库,material组件库和cupertino组件库,material组件库是符合谷歌设计规范标准的库,cupertino组件库是符合苹果设计规范标准的库。

  • 在 flutter 中几乎所有的东西都是 widget,widget 是用户界面的基本构建块,我们将 widget 组成一个层次结构,调用 widget 树,每个窗口 widget 都嵌套在父窗口的 widget 中,并从其父类窗口继承属性,甚至应用程序对象本身也是一个组件,没有单独的应用程序对象,根 widget 担任此角色

  • flutter 目前没有专门的字符串资源系统,目前最佳的做法是将 string 资源作为静态字段保存在类中。
    例如:

class Strings{
  static String welcomeString = "Welcome to flutter";
}
print('${Strings.welcomeString}');
  • flutter 的 widget 很轻巧,部分原因在于它的不变性,因为他本身不是视图,并且不是直接绘制任何东西,而是对 UI 及其语义的描述。

  • 在 flutter 中,widget是不可变的,不会直接更新,我们可以通过操纵 widget 的状态来更新它,这就是有状态和无状态的概念来源。

  • stateslessWidgets适用于描述的用户界面不依赖对象中的配置信息。例如 logo

  • flutter 中推荐组合多个小的 widget 来构建一个自定义的 widget,而不是扩展它

  • 可以用 Opacity widget 包裹一个 widget 来设置透明度
    Opacity(
    opacity:0.5,
    child: Text('透明度0.5');
    )

  • statelessWidget 的 build 方法通常只会在一下三种情况下调用:
    1、将 widget 插入视图树中时
    2、当 widget 的父级更改其配置时
    3、当它依赖的 InheritedWidget 发生变化时

  • flutter 中 widget 是有状态的还是无状态的,取决于是否依赖于状态的变化;如果用户交互或数据改变导致 widget 改变,那么他就是有状态的;如果一个 widget 是最终的或不可变的,那么他就是无状态的。

  • flutter 中管理状态有三种主要方式:
    1、每个 widget 管理自己的状态;
    2、父 widget 管理 widget 的状态
    3、混合搭配管理的方法
    决定使用哪种方式,可以参考一下原则:
    如果所讨论的状态是用户数据,例如复选框的已选中为选中状态,或滑块的位置,则状态最好有父 widget 管理
    如果 widget 的状态取决于动作,例如动画,那么最后有 widget 自身来管理状态;
    如果还是不确定谁管理状态,请让父 widget 管理子 widget 的状态

  • flutter 中有两种主要的 widget 用于在页面之间导航
    1、Route是一个应用程序抽象的屏幕或页面
    2、Navigator 是一个管理路由的 widget
    以上两种 widget 对应 flutter 中是想页面导航的有两种选择
    1、具体指定一个路由名构成的 Map(MaterialAPP)
    2、直接跳转到一个路由(WidgetApp)


    image.png

    image.png

    image.png
  • dart 有一个单线程执行模型,支持 Isolate(一种在另一个线程上运行 Dart 代码的方法),一个事件循环和异步编程。除非自己创建一个 Isolate,否则 dart 代码永远运行在主 UI 线程,并由 event loop 驱动。Flutter 的 event loop 和 ios 中的 main loop 相似:looper 是附加在主线程上的。

  • dart 的单线程模型并不意味着代码一定要作为阻塞操作的方式运行,从而卡住 UI,Dart 提供了异步工具,例如 async / await 来实现异步操作。

  • 有时候需要处理大量的数据,这就会导致 UI挂起,在 flutter 中,使用 Isolate 来发挥多核心 CPU 的优势来处理那些长期运行或是计算密集型的任务。Isolate是分离的运行线程,并且不和主线程的内存堆共享内存,这意味着你不能访问主线程中的变量,或者使用 setStatus()来更新 UI,正如他们的名字一样,Isolate 不能共享内存

loadData() async {
    //打开ReceivePort已接收传入的消息
    ReceivePort receivePort = ReceivePort();
    // 创建并生成与当前 Isolate 共享相同代码的 Isolate
    await Isolate.spawn(dataLoader, receivePort.sendPort);
    // 取出流的第一个元素
    SendPort sendPort = await receivePort.first;
    // 流的第一个元素被收到后监听会关闭,所以需要新打开一个 ReceivePort 以接收传入的消息
    ReceivePort response = ReceivePort();
    // 通过此发送端口向其对应的 ReceivePort发送异步消息,这个消息指的是发送的参数。
    sendPort.send(['https://jsonplaceholder.typicode.com/posts',response.sendPort]);
    // 获取端口发送来的数据
    List msg = await response.first;
    // setState((){
    //   widgets = msg;
    // });
  }
  // isolate 的入口函数,该函数会在新的 Isolate 中调用,Isolate。spawn 的 messge 参数会作为调用它时的唯一参数
  static dataLoader(SendPort sendPort) async {
    // 打开 ReceivePort 以加收传入的消息
    ReceivePort port = ReceivePort();
    // 通知其他的 Isolates 本 isolate 所监听的端口
    sendPort.send(port.sendPort);
    // 获取其他端口发送的异步消息
    await for (var msg in port) {
      String data = msg[0];
      SendPort replyTo = msg[1];
      String dataUrl = data;
      http.Response response = await http.get(dataUrl);
      // 其对应的 ReceivePort 发送解析出来的 Json 数据
      replyTo.send(json.decode(response.body));
    }
  }

由于Flutter是单线程并且跑着一个event loop,因此你不必担心线程管理或生成后台线程,如果你正在做I/O操作,如访问磁盘或网络请求,可以安全地使用async/await来完成,如果你需要做让CPU执行繁忙的计算密集型任务,你需要使用Isolate来避免阻塞event loop。

  • ProgressIndicator 进度指示器
  • 点击:
  1. onTapDown 按下
    2.onTapUp 抬起
    3.onTap 点击
    4.onTapCancel 触发了 onTapDown 但没能触发 tap
  • 双击
    1>.onDoubleTap

  • 长按
    1>.onLongPress

  • 垂直拖动
    1>.onVerticalDragStart
    2>.onVerticalDragUpdate
    3>.onVerticalDragEnd

  • 水平拖动
    1>.onHorizontalDragStart
    2>.onHorizontalDragUpdate
    3>.onHorizontalDragEnd

  • Flutter 中设置自定义字体,只需要在文件夹中放置字体文件,并在pubspec.yaml中引用它,就像添加图片那样

  • Flutter 动画
    flutter 中动画分为两类:基于tween 或基于物理仿真
    1、补间(Tween) 动画:在tween动画中,定义了开始点和结束点、时间线以及定义转换时间和速度的曲线。然后由框架计算如何从开始点过度到结束点。
    2、基于物理动画:在基于物理动画中,运动被模拟为与真实世界的行为相似,例如弹球落地

  • flutter 动画中的常用类

  • Animation: 是flutter动画库中的一个核心类,它生成指导动画的值

  • CurvedAnimation:Animation的一个子类,将过程抽象为一个非线性曲线

  • AnimationController: Animation的一个子类,用来管理Animation;

  • Tween: 在正在执行动画的对象所使用的数据范围之间生成值,例如色彩渐变过程中的色值

  • 在flutter中Animation对象本身和UI渲染没有任何关系,Animation是一个抽象类,它拥有其当前值和状态(完成或停止)。其中一个比较常用的Animation类是Animation<double>.

  • AnimationController是一个特属于的Animation对象,在屏幕刷新的每一帧,就会生成一个新的值,默认情况下AnimationController在给定的时间段内会线性的生成从0.0到1.0的数字。

  • flutter 中可以用Hero widget创建这个动画,当hero通过动画从源页面飞到目标页面时,目标页面逐渐淡入视野,通常hero是用户界面的一小部分,它通常在两个页面都有,从用户的角度来看,hero在页面之间飞翔。

  • 如何给动画添加监听器,在Flutter中我们可以通过Animation的addListener、addStatusListener方法为动画添加监听器:
    addListener:动画的值发生变化时被调用
    addStatusListener:动画状态发生变化时被调用

  • AnimatedWidget 可以理解为Animation的助手,它可以简化我们对动画的使用,在不使用AnimatedWidget的情况下需要手动调用动画的addListener()并在回调中添加setState才能看到动画效果,AnimatedWidget将为我们简化这一操作。

  • AnimatedBuilder,也是自动监听来自Animation对象的通知,不需要手动调用addListener().

  • flutter 调试技巧

  • 条件断点

  • 善用变量Variables视窗与观察Watches视窗

  • 通过Frames回退

  • 善用控制台

    image.png

  • 在使用 ListView 的时候,flutter开发团队可能对不同设备做了适配,所以在iphone X 上 listView 的顶部会留有安全区的距离,如果想让 ListView 显示到安全区内,可以使用
    MediaQuery.removePadding(...)将 ListView 包一层
  • 滚动改变 appbar 透明度


    image.png

    使用 stack 布局控件
    使用 Opacity 包裹 自定义 appbar

  • future 和 FutureBuilder 使用
    Future 表示在接下来的某个时间的值或错误,借助 future我们可以在 Future 实现异步操作。Future 是 dart:async 包中的一个类,使用时需要导入 dart:async
    Future 有两种状态:pending-执行中;completed-执行结束,分两种情况要么成功,要么失败;
    Future的常见用法
    使用 future.then 获取 future 的值与捕获 future 的异常;
    结合 async,await
    future.whenComplete
    future.timeout
    使用 future.then获取 future 的值与捕获 future 的异常
Future<String> testFuture(){
  return Future.value('success');
}
main() {
  testFuture().then((s){
    print(s);
  },onError:(error){
    print(error);
  }).catchError((error){
    print(error);
  }).whenComplete((){
    print('completed');
  });
}

如果 catchError 与 onError 同时存在则只会调用 onError
then 方法源码
Future<R> then<R>(FutureOr<R> onValue(T value),{Function onError});
第一个参数是成功的结果回调,第二个参数 onError 表示执行出现异常
Future 是异步的可以,如果我们需要将异步转为同步,那么可以借助 async awiait 完成

有时候我们需要在 Future 结束的时候做些事情,我们知道 then().catchError()的模式类似于 try-catch,try-catch 有个 finally 代码,而Future.whenComplete就是 future 的 finally


image.png
  • FutureBuilder 是一个将异步操作和异步 UI更新结合在一起的类,通过它我们可以将网络请求,数据库读取等的结果更新在页面上。
    FutureBuilder的构造方法
    FutureBuilder({Key key, Future<T> future, T initialData, @required AsyncWidgetBuilder<T> builder})
    future :Future 对象表示此构造器当前联机的异步计算;
    initialData:表示一个非空的 Future 完成前的初始化数据:
    builder:AsyncWidgetBuilder 类型的回调函数,是一个基于异步交互构建widget 的函数
    image.png
  • 如何序列化
String jsonString = '{"title":"zhangsan","age":"30"}'
Map<String, dynamic> map = JSON.decode(jsonStr);
  • 列表布局
    ListView: 列表布局,给 ListView 设置高度是无效的,需要给 ListView 外层的 Container 设置高度
    GradeView: 网格布局
    ExpansionTile: 可展开收起的列表


    image.png
  • FractionallySizeBox 组件
  • list.reversed.toList() 数组取反
  • RefreshIndicator 系统下拉刷新组件
  • 上拉加载更多


    image.png

混合开发

  • 应用场景:
    1、作为独立页面进行加入
    2、作为页面的一部分嵌入

  • 混编热加载


    image.png

    image.png
  • 混编调试代码


    image.png
  • ios 混编
    1、以 FlutterViewController的方式接入
    2、以 FlutterEngine 的方式接入:这种方式提前初始化了一个 FlutterEngine,所以在打开 flutterViewController 的时候会快很多,但是这种方式有个官方已知的 bug,就是在给 flutterViewController 设置 setInitialRoute 的时候会无效,另外这种方式也比较麻烦

  • ios 混编调试
    热重启/热加载


    image.png

    image.png
  • 遇到问题可以去 github 的 flutter 官方地址去搜索别人有没有遇到同样的问题

  • 在安装或更新 homebrew 的时候如果下载的比较慢,可以搜索 homebrew 镜像,然后找到清华大学的开源镜像,按照步骤进行替换

  • iOS 混合调试代码


    image.png
  • 通信


    image.png

    image.png
image.png
  • 调试的时候开启全局断点,这样程序有异常的话,就会指到异常的地方,AndroidStudio 中全局断点的位置如下


    image.png

    另外调试的时候如果有 try-catch,可以先把 try-catch 注释掉,因为它会 catch 异常,不方便调试

  • 在将对象转换成 json 字符串的时候需要 实现 toJson 方法,因为
    String jsonString = json.encode(model);用的到
  • Row 组件中的 mainAxisAlignment: 设置为MainAxisAlignment.spaceAround时是平均排列
    设置为mainAxisAlignment. spaceBetween时,两边没有间距,只有 child 与 child 之间显示间距
  • Future 有两种状态:
    pending-执行中
    completed-执行结束,要么成功要么失败
    常见方法future.whenComplete、future.timeout
    catchError与onError同时存在时,只会调用onError
 body: FutureBuilder<AnyModel>(
      future: doSomething(),
      initialData: AnyModel(),
      builder: (BuildContext context, AsyncSnapshot<AnyModel> snaphot){
        switch (snaphot.connectionState){
          case ConnectionState.none:
            return Container();
          case ConnectionState.waiting:
            return Container();
          case ConnectionState.active:
            return Container();
          case ConnectionState.done:
            if(snaphot.hasData) {
              return Container();
            } else {
              return Container();
            }
          default:
            return Container();
        }
      },
    ),
  • ExpansionTile 实现可展开的列表
  • RefreshIndicator 系统的下拉刷新组件
ScrollController _scrollController = ScrollController();
    _scrollController.addListener(() {
      if(_scrollController.position.pixels == _scrollController.position.maxScrollExtent){
          _loadMore();
      }
    });

// 注意需要将_scrollController在dispost中调用dispost,从而移除添加的监听,减少资源损耗
  • 混合开发热加载/热重启
    1.打开一个模拟器,或链接一个设备到电脑上;
    2、关闭App,然后运行flutter attach;flutter attach -d ‘设备名’

  • 混合开发调试
    1、关闭app
    2、点击AS中的 Flutter attach 按钮
    3、启动App

  • Build Setting -->Build Options -->Enable Bitcode 中禁用bitcode

  • Flutter 模块中可以通过window.defaultRouteName 来获取Native中通过setInitialRoute设置的字符串(该字符串可以用来确定路由,传递参数)

  • 使用FlutterEngine 的方式中调用setInitialRoute会无效,这是flutter SDK 的一个bug

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容