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 进度指示器
- 点击:
- onTapDown 按下
2.onTapUp 抬起
3.onTap 点击
4.onTapCancel 触发了 onTapDown 但没能触发 tap
双击
1>.onDoubleTap长按
1>.onLongPress垂直拖动
1>.onVerticalDragStart
2>.onVerticalDragUpdate
3>.onVerticalDragEnd水平拖动
1>.onHorizontalDragStart
2>.onHorizontalDragUpdate
3>.onHorizontalDragEndFlutter 中设置自定义字体,只需要在文件夹中放置字体文件,并在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

- 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

-
调试的时候开启全局断点,这样程序有异常的话,就会指到异常的地方,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、启动AppBuild Setting -->Build Options -->Enable Bitcode 中禁用bitcode
Flutter 模块中可以通过window.defaultRouteName 来获取Native中通过setInitialRoute设置的字符串(该字符串可以用来确定路由,传递参数)
使用FlutterEngine 的方式中调用setInitialRoute会无效,这是flutter SDK 的一个bug
















