2019年4月

0x00 前言
BuildContext是Flutter的重要部分,但是目前网上讲BuildContext的文章太少了,所以本篇文章讲讲BuildContext。
0x01 BuildContext 介绍
BuildContext,顾名思义,Build(构建Widget) Context(应用上下文),就是构建Widget中的应用上下文。
所以BuildContext只出现在两个地方:

StatelessWidget.build 方法中:创建StatelessWidget的build方法
State对象中:一个是创建StatefulWidget的State对象的build方法中,另一个是State的成员变量

BuildContext实际是Element,BuildContext是为了阻止直接对Element操作而抽象出来的,所以BuildContext是Element的抽象类,所有Element都继承自BuildContext。
每一个Widget都有一个BuildContext。
BuildContext是Widget在Widget树中位置的句柄。
0x02 访问 BuildContext 实例对象
BuildContext被WidgetBulder(例如:StatelessWidget.build, State.build)方法传递;
BuildContext也是State的成员变量,在State内部,可以通过context直接访问
0x03 BuildContext 使用
BuildContext的作用主要是通过上下文获取指定的数据;
例如:Theme.of(context) 或者 showDialog(context: context,....)都需要BuildContext作为参数,这里的BuildContext就是调用这些方法的Widget的代表。
0x05 BuildContext 注意事项
每一个Widget都有一个BuildContext。假设有个A Widget,A Widget里肯定会有StatelessWidget.build或者State.build的build方法,build方法创建了 B Widget并返回,A Widget就是B Widget的父Widget,相应的, A Widget的BuildContext也是是B Widget 的BuildContext的父节点。
下面给一个例子加深理解:
@override
Widget build(BuildContext context) {

// here, Scaffold.of(context) returns null,
//在Scaffold创建之前,如果在这里调用Scaffold.of(context)会返回null,是因为此时Scaffo//ld还没创建,所以其BuildContext也没有创建,为了在创建Scaffold的时候就取到他的BuildC//ontext,要使用Builder
return Scaffold(
  appBar: AppBar(title: Text('Demo')),
  body: Builder(
    builder: (BuildContext context) {
      return FlatButton(
        child: Text('BUTTON'),
        onPressed: () {
          // here, Scaffold.of(context) returns the locally created Scaffold
          Scaffold.of(context).showSnackBar(SnackBar(
            content: Text('Hello.')
          ));
        }
      );
    }
  )
);

}
0x04 BuildContext 内部方法解析
为什么BuildContext可以用来获取上下文的数据,主要是因为BuildContext具有的以下方法:

ancestorInheritedElementForWidgetOfExactType(Type targetType) → InheritedElement

Obtains the element corresponding to the nearest widget of the given type, which must be the type of a concrete InheritedWidget subclass. [...]

ancestorRenderObjectOfType(TypeMatcher matcher) → RenderObject

Returns the RenderObject object of the nearest ancestor RenderObjectWidget widget that matches the given TypeMatcher. [...]

ancestorStateOfType(TypeMatcher matcher) → State

Returns the State object of the nearest ancestor StatefulWidget widget that matches the given TypeMatcher. [...]

ancestorWidgetOfExactType(Type targetType) → Widget

Returns the nearest ancestor widget of the given type, which must be the type of a concrete Widget subclass. [...]

findRenderObject() → RenderObject

The current RenderObject for the widget. If the widget is a RenderObjectWidget, this is the render object that the widget created for itself. Otherwise, it is the render object of the first descendant RenderObjectWidget. [...]

inheritFromElement(InheritedElement ancestor, { Object aspect }) → InheritedWidget

Registers this build context with ancestor such that when ancestor's widget changes this build context is rebuilt. [...]

inheritFromWidgetOfExactType(Type targetType, { Object aspect }) → InheritedWidget

Obtains the nearest widget of the given type, which must be the type of a concrete InheritedWidget subclass, and registers this build context with that widget such that when that widget changes (or a new widget of that type is introduced, or the widget goes away), this build context is rebuilt so that it can obtain new values from that widget. [...]

rootAncestorStateOfType(TypeMatcher matcher) → State

Returns the State object of the furthest ancestor StatefulWidget widget that matches the given TypeMatcher. [...]

visitAncestorElements(bool visitor(Element element)) → void

Walks the ancestor chain, starting with the parent of this build context's widget, invoking the argument for each ancestor. The callback is given a reference to the ancestor widget's corresponding Element object. The walk stops when it reaches the root widget or when the callback returns false. The callback must not return null. [...]

visitChildElements(ElementVisitor visitor) → void

Walks the children of this widget. [...]

让我们先抛开Flutter这个平台说话,如果让你实现数据共享,你能想到的基础方案有哪些。
全局静态变量
单例(XXXMnager,如UserManger)
持久化(SharePref)
ok,以上方案真的是简单粗暴,好用到哭,然而,设计到数据数据变更之后及时通知到各个关注方就显得有点捉襟见肘了。因此,因为有这样的一些需求存在,业界的一些大神(懒人)们通过不懈的努力,做出了一些惊天地,泣鬼神的全局数据共享通知方案。那么,在flutter上,我比较关注的是,Redux,和event_bus 了,本文就是想来总结一下自己对Redux和event_bus的理解心得。

Redux
理解成本比较高,个人觉得耦合性也比较高,要搞清楚redux的原理,先要了解一下几个概念
Store
这里是他的构造函数,我们先不要管里面的一堆参数,看一眼就好。

Store(
this.reducer, {
State initialState,
List<Middleware<State>> middleware = const [],
bool syncStream: false,

/// If set to true, the Store will not emit onChange events if the new State
/// that is returned from your [reducer] in response to an Action is equal
/// to the previous state.
///
/// Under the hood, it will use the `==` method from your State class to
/// determine whether or not the two States are equal.
bool distinct: false,

})
Store可以简单的理解为一个容纳各种数据以及对数据处理的action的一个仓库,可以看到可以给它配置一个泛型,这个泛型代表的就是下面的State,好,我们接着看State。

State
State实际上并不是Dart的基础类型,他其实就是上面Store定义中的那个S,对的,他就是一个泛型,他可以是dart基础类型String,int,double,也可以是你定义的class,都ok。总之一句话,他就是Store要守护的和维护的那个份数据。

StoreProvider
这里是他的构造函数,这里的参数比较简单,可以直接就了解一下
const StoreProvider({

Key key,
@required Store<S> store,
@required Widget child,

})
一个store,一个child是一个Widget类型,所以理解起来是不是就是将这个store和child绑定起来的桥梁啊,嗯,牵线媒婆,store中的数据有变更可以通知到到child更新tree,那么具体child中的哪些个子child需要更新,是有谁决定的,当然是StoreConnector,媒婆把人给你放一起,你牵不牵手他可不管,谁管,肯定是StoreConnector啊,好,我们看StoreConnector。

StoreConnector
还是来看一下构造函数
StoreConnector({

Key key,
@required this.builder,
@required this.converter,
this.distinct = false,
this.onInit,
this.onDispose,
this.rebuildOnChange = true,
this.ignoreChange,
this.onWillChange,
this.onDidChange,
this.onInitialBuild,

})
这个构造函数的参数就有点多了,如果你感兴趣可以都了解一下,没时间的话,只需要了解required标记的。第一个是builder,这个就是WidgetBuilder,很明显,构建view用的,非常重点的converter这个参数,看一下converter的定义:
/// Convert the entire Store into a ViewModel. The ViewModel will be used
/// to build a Widget using the ViewModelBuilder.
typedef StoreConverter<S, ViewModel> = ViewModel Function(

Store<S> store,

);

看到之后也就没那么神秘了,就是将store转换为了ViewModel,转了之后,实际上就是可以更好的将数据交给builder去构建view,是吗?难道不是吗?我们前面提到了store可以接受到一个改变里面数据的action,那么这些action是谁给处理的呢?回过头来看Store的构造函数,里面的第一个参数是reducer,reducer的英文翻译为减速器,还原剂,反正就是听着挺别扭的,他不就是一个状态转换器嘛,数据有一个状态,经过action的处理,变成另外一个状态,是吗,这样你好理解了么?好吧,来看看Reducer。

Reducer
Reducer的定义如下:
typedef State Reducer<State>(State state, dynamic action);
一目了然,就是上面所说的状态应该action处理,变为另外一个状态,那么,state的处理仅仅只有Reducer处理,加入需要加入一些日志记录的,性能监控等处理,该怎么办呢?这种需求到处都有啊,大名鼎鼎的okhttp,处理一个http请求也可以说成是一个一系列的请求参数json请过action后端服务器的处理变为另外一串json,对么,那么对请求头,请求参数校验的一些处理,是不是都交给了拦截器interceptor?这点设计思路是想通的,因此这里的Middleware中间件虽然叫起来很神秘,但是他实际上就是拦截器,他在Reducer们之前执行,这点我了解到的是如此,有不同的见解的同学可以在评论中留下建议。
Middleware
定义如下
/// ### Example
///
/// loggingMiddleware(Store<int> store, action, NextDispatcher next) {
/// print('${new DateTime.now()}: $action');
///
/// next(action);
/// }
///
/// // Create your store with the loggingMiddleware
/// final store = new Store<int>(
/// counterReducer,
/// middleware: [loggingMiddleware],
/// );
typedef void Middleware<State>(
Store<State> store,
dynamic action,
NextDispatcher next,
);
对,处理完之后,交给后面的action,理解起来也没什么成本了,好,那么总结一下我们将的这些概念,将他们串起来,用一副图来表达。
165d5c8da8ec4959.png
用一句话来描述就是:store通过storeProvider将自己给暴露出来,交给StoreConnector来更好链接到控件上,控件(也不一定需要在控件哪里,只不过我们好理解点)发送action交给store中的reducer处理,如果有中间件存在,那么中间件先拦截之后在交给recuder处理。处理之后store中的数据变更了,将会经有storeConnector通知组件更新。ok流程就这么跑完了。

event_bus
理解成本略低,耦合性也较低初始化
import 'package:event_bus/event_bus.dart';

EventBus eventBus = new EventBus();
发送事件eventBus.fire(event);
监听事件eventBus.on().listen((event) {

print(event.runtimeType);

});
也没有太多的概念,无耻的盗一幅图来说就是
165d5c921d5ffd4d.png
用一句话总结就是:对于某处所做的变更,如果想通知出去,那么交给总线吧,谁想关心谁问总线取。总结总体上来看,redux和bus都可以实现全局数据共享及变更通知,但是bus更加好理解概念也每有那么多,也不像redux需要通过storeConnector那么与控件绑定,造成不必要的耦合,个人倾向于使用bus解决全局数据共享变更通知的需求。

前言
和其他的视图框架比如android的Activity一样,flutter中的视图Widget也存在生命周期,生命周期的回调函数提现在了State上面。理解flutter的生命周期,对我们写出一个合理的控件至关重要。组件State的生命周期整理如下图所示:
1768723716-5b188ca549250_articlex.png
大致可以看成三个阶段

初始化(插入渲染树)
状态改变(在渲染树中存在)
销毁(从渲染树种移除)

各个函数

构造函数

这个函数不属于生命周期,因为这个时候State的widget属性为空,如果要在构造函数中访问widget的属性是行不通的。但是构造函数必然是要第一个调用的。

initState
/// Called when this object is inserted into the tree.
当插入渲染树的时候调用,这个函数在生命周期中只调用一次。这里可以做一些初始化工作,比如初始化State的变量。
didChangeDependencies
/// Called when a dependency of this [State] object changes.
这个函数会紧跟在initState之后调用,并且可以调用BuildContext.inheritFromWidgetOfExactType,那么BuildContext.inheritFromWidgetOfExactType的使用场景是什么呢?最经典的应用场景是
new DefaultTabController(length: 3, child: new TabBar(

  tabs: [ "主页","订单","我的" ]
  .map( (data)=>new Text(data) ).toList(),

TabBar本来需要定义一个TabController,但是在外面套一层DefaultTabController就不需要定义TabContrller了,看下源码:
@override
void didChangeDependencies() {

super.didChangeDependencies();
_updateTabController();
_initIndicatorPainter();

}

void _updateTabController() {

final TabController newController = widget.controller ?? DefaultTabController.of(context);
...

注意到这里DefaultTabController.of(context)
static TabController of(BuildContext context) {

final _TabControllerScope scope = context.inheritFromWidgetOfExactType(_TabControllerScope);
return scope?.controller;

}
实际上就是调用BuildContext.inheritFromWidgetOfExactType,也就说在didChangeDependencies中,可以跨组件拿到数据。
didUpdateWidget
/// Called whenever the widget configuration changes.
当组件的状态改变的时候就会调用didUpdateWidget,比如调用了setState.
实际上这里flutter框架会创建一个新的Widget,绑定本State,并在这个函数中传递老的Widget。
这个函数一般用于比较新、老Widget,看看哪些属性改变了,并对State做一些调整。
需要注意的是,涉及到controller的变更,需要在这个函数中移除老的controller的监听,并创建新controller的监听。
比如还是TabBar:
2277621881-5b1896db77e0d_articlex.png
3952856631-5b18971e0d004_articlex.png
deactivate
/// Called when this object is removed from the tree.
在dispose之前,会调用这个函数。
dispose
/// Called when this object is removed from the tree permanently.
一旦到这个阶段,组件就要被销毁了,这个函数一般会移除监听,清理环境。
还是TabBar:
3786704441-5b18a28ddc8be_articlex.png

总结一下
阶段 调用次数 是否支持setState
构造函数 1 否
initState 1 无效(使用setState和不使用一样)
didChangeDependencies >=1 无效
didUpdateWidget >=1 无效
deactivate >=1 否
dispose 1 否

在向队列中添加任务时,可以直接在对应的函数中添加 block。但是如果想对任务进行操作,比如监听任务、取消任务,就需要获取对应的 block。
创建block

object-c
创建 block 有两种方式,第一种方式如下:
dispatch_block_t dispatch_block_create(dispatch_block_flags_t flags, dispatch_block_t block);

在该函数中,flags 参数用来设置 block 的标记,block 参数用来设置具体的任务。flags 的类型为 dispatch_block_flags_t 的枚举,用于设置 block 的标记,定义如下:
DISPATCH_ENUM(dispatch_block_flags, unsigned long,

DISPATCH_BLOCK_BARRIER = 0x1,
DISPATCH_BLOCK_DETACHED = 0x2,
DISPATCH_BLOCK_ASSIGN_CURRENT = 0x4,
DISPATCH_BLOCK_NO_QOS_CLASS = 0x8,
DISPATCH_BLOCK_INHERIT_QOS_CLASS = 0x10,
DISPATCH_BLOCK_ENFORCE_QOS_CLASS = 0x20,

);

创建 block 的另一种方式如下:
dispatch_block_t dispatch_block_create_with_qos_class(dispatch_block_flags_t flags,

dispatch_qos_class_t qos_class, int relative_priority,
dispatch_block_t block);

相比于 dispatch_block_create 函数,这种方式在创建 block 的同时可以指定了相应的优先级。dispatch_qos_class_t 是 qos_class_t 的别名,定义如下:

if __has_include(<sys/qos.h>)

typedef qos_class_t dispatch_qos_class_t;

else

typedef unsigned int dispatch_qos_class_t;

endif

而 qos_class_t 是一种枚举,有以下类型:

QOS_CLASS_USER_INTERACTIVE:user interactive 等级表示任务需要被立即执行,用来在响应事件之后更新 UI,来提供好的用户体验。这个等级最好保持小规模。
QOS_CLASS_USER_INITIATED:user initiated 等级表示任务由 UI 发起异步执行。适用场景是需要及时结果同时又可以继续交互的时候。
QOS_CLASS_DEFAULT:default 默认优先级
QOS_CLASS_UTILITY:utility 等级表示需要长时间运行的任务,伴有用户可见进度指示器。经常会用来做计算,I/O,网络,持续的数据填充等任务。这个任务节能。
QOS_CLASS_BACKGROUND:background 等级表示用户不会察觉的任务,使用它来处理预加载,或者不需要用户交互和对时间不敏感的任务。
QOS_CLASS_UNSPECIFIED:unspecified 未指明

事例:
dispatch_queue_t concurrentQuene = dispatch_queue_create("concurrentQuene", DISPATCH_QUEUE_CONCURRENT);

dispatch_block_t block = dispatch_block_create(0, ^{

NSLog(@"normal do some thing...");

});
dispatch_async(concurrentQuene, block);

//
dispatch_block_t qosBlock = dispatch_block_create_with_qos_class(0, QOS_CLASS_DEFAULT, 0, ^{

NSLog(@"qos do some thing...");

});
dispatch_async(concurrentQuene, qosBlock);

swift 3.0
swift 3.0 中使用了 DispatchWorkItem 对任务进行了封装。可以在 DispatchWorkItem 初始化方法中指定任务的内容和优先级。事例如下:
let concurrentQueue = DispatchQueue.init(label: "concurrentQueue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil);

let block = DispatchWorkItem.init(block: {

print("normal do some thing...")

})
concurrentQueue.async(execute: block);

let qosBlock = DispatchWorkItem.init(qos: .default, flags: .noQoS, block: {

print("qos do some thing...")

})

监听 block 执行结束
有时我们需要等待特定的 block 执行完成之后,再去执行其他任务。有两种方法可以获取到指定 block 执行结束的时机。

object-c
在 object-c 中,第一种为使用下面的函数:
long dispatch_block_wait(dispatch_block_t block, dispatch_time_t timeout);

该函数会阻塞当前线程进行等待。传入需要设置的 block 和等待时间 timeout。timeout 参数表示函数在等待 block 执行完毕时,应该等待多久。如果执行 block 所需的时间小于 timeout,则返回 0,否则返回非 0 值。此参数也可以取常量 DISPATCH_TIME_FOREVER,这表示函数会一直等待 block 执行完,而不会超时。可以使用 dispatch_time 函数和 DISPATCH_TIME_NOW 常量来方便的设置具体的超时时间。
如果 block 执行完成,dispatch_block_wait 就会立即返回。不能使用 dispatch_block_wait 来等待同一个 block 的多次执行全部结束;这种情况可以考虑使用 dispatch_group_wait 来解决。也不能在多个线程中,同时等待同一个 block 的结束。同一个 block 只能执行一次,被等待一次。
注意:因为 dispatch_block_wait 会阻塞当前线程,所以不应该放在主线程中调用。
事例
dispatch_queue_t concurrentQuene = dispatch_queue_create("concurrentQuene", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(concurrentQuene, ^{

dispatch_queue_t allTasksQueue = dispatch_queue_create("allTasksQueue", DISPATCH_QUEUE_CONCURRENT);

dispatch_block_t block = dispatch_block_create(0, ^{
    NSLog(@"开始执行");
    [NSThread sleepForTimeInterval:3];
    NSLog(@"结束执行");
});

dispatch_async(allTasksQueue, block);
// 等待时长,10s 之后超时
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC));
long resutl = dispatch_block_wait(block, timeout);
if (resutl == 0) {
    NSLog(@"执行成功");
} else {
    NSLog(@"执行超时");
}

});

输入结果:

开始执行
结束执行
执行成功

swift 3.0
在 swift 3.0 中,只需调用 DispatchWorkItem 的实例方法即可等待任务结束,如下:
public func wait()
public func wait(timeout: DispatchTime) -> DispatchTimeoutResult
public func wait(wallTimeout: DispatchWallTime) -> DispatchTimeoutResult

事例:
let concurrentQueue = DispatchQueue.init(label: "concurrentQueue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil);

concurrentQueue.async(execute: {

let allTesksQueue = DispatchQueue.init(label: "allTesksQueue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil);

let workItem = DispatchWorkItem.init(block: {
    print("开始执行")
    sleep(3)
    print("结束执行")
})

allTesksQueue.async(execute: workItem)

// 设置超时时间,超时时间为 10s
let timeout = DispatchTime.now() + 10
let result = workItem.wait(timeout: timeout)

if result == .success {
    print("执行成功")
} else {
    print("执行超时")
}

})

输入结果同 object-c。
第二种监听 block 执行完成的方法如下:

object-c
在 object-c 中可用下面的函数:
void dispatch_block_notify(dispatch_block_t block,

    dispatch_queue_t queue, 
    dispatch_block_t notification_block);

该函数接收三个参数,第一个参数是需要监视的 block,第二个参数是监听的 block 执行结束之后要提交执行的队列 queue,第三个参数是待加入到队列中的 block。 和 dispatch_block_wait 的不同之处在于:dispatch_block_notify 函数不会阻塞当前线程。
例如:
NSLog(@"---- 开始设置任务 ----");
dispatch_queue_t serialQueue = dispatch_queue_create("com.fyf.serialqueue", DISPATCH_QUEUE_SERIAL);

// 耗时任务
dispatch_block_t taskBlock = dispatch_block_create(0, ^{

NSLog(@"开始耗时任务");
[NSThread sleepForTimeInterval:2.f];
NSLog(@"完成耗时任务");

});

dispatch_async(serialQueue, taskBlock);

// 更新 UI
dispatch_block_t refreshUI = dispatch_block_create(0, ^{

NSLog(@"更新 UI");

});

// 设置监听
dispatch_block_notify(taskBlock, dispatch_get_main_queue(), refreshUI);
NSLog(@"---- 完成设置任务 ----");

输出:

---- 开始设置任务 ----
---- 完成设置任务 ----
开始耗时任务
完成耗时任务
更新 UI

swift 3.0
在 swift 3.0 中,可以使用 DispatchWorkItem 的实例方法进行设置,方法如下:
public func notify(qos: DispatchQoS = default, flags: DispatchWorkItemFlags = default, queue: DispatchQueue, execute: @escaping @convention(block) () -> Swift.Void)

public func notify(queue: DispatchQueue, execute: DispatchWorkItem)

事例:
print("---- 开始设置任务 ----")

let tasksQueue = DispatchQueue.init(label: "com.fyf.tasksQueue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil);

let refreshUIWorkItem = DispatchWorkItem.init(block: {

print("更新 UI")

})

let tasksWorkItem = DispatchWorkItem.init(block: {

print("开始耗时任务")
sleep(3)
print("完成耗时任务")

})

tasksWorkItem.notify(queue: DispatchQueue.main, execute: refreshUIWorkItem)
tasksQueue.async(execute: tasksWorkItem)

print("---- 完成设置任务 ----")

输出:

---- 开始设置任务 ----
---- 完成设置任务 ----
开始耗时任务
完成耗时任务
更新 UI

任务的取消
iOS8 后 GCD 支持对 dispatch block 的取消。方法如下:

object-c
可以使用下面的函数取消 dispatch block:
void dispatch_block_cancel(dispatch_block_t block);

这个函数用异步的方式取消指定的 block。取消操作使将来执行 dispatch block 立即返回,但是对已经在执行的 dispatch block 没有任何影响。当一个 block 被取消时,它会立即释放捕获的资源。如果要在一个 block 中对某些对象进行释放操作,在取消这个 block 的时候,需要确保内存不会泄漏。
例子:
dispatch_queue_t serialQueue = dispatch_queue_create("com.fyf.serialqueue", DISPATCH_QUEUE_SERIAL);

// 耗时任务
dispatch_block_t firstTaskBlock = dispatch_block_create(0, ^{

NSLog(@"开始第一个任务");
[NSThread sleepForTimeInterval:1.5f];
NSLog(@"结束第一个任务");

});

// 耗时任务
dispatch_block_t secTaskBlock = dispatch_block_create(0, ^{

NSLog(@"开始第二个任务");
[NSThread sleepForTimeInterval:2.f];
NSLog(@"结束第二个任务");

});

dispatch_async(serialQueue, firstTaskBlock);
dispatch_async(serialQueue, secTaskBlock);

// 等待 1s,让第一个任务开始运行
[NSThread sleepForTimeInterval:1];

dispatch_block_cancel(firstTaskBlock);
NSLog(@"尝试过取消第一个任务");

dispatch_block_cancel(secTaskBlock);
NSLog(@"尝试过取消第二个任务");

输出:

开始第一个任务
尝试过取消第一个任务
尝试过取消第二个任务
结束第一个任务

可见 dispatch_block_cancel 对已经在执行的任务不起作用,只能取消尚未执行的任务。

swift 3.0
在 swift 3.0 中,可以使用 DispatchWorkItem 的实例方法进行设置,方法如下:
public func cancel()

事例:
let tasksQueue = DispatchQueue.init(label: "com.fyf.tasksQueue");

let firstWorkItem = DispatchWorkItem.init(block: {

print("开始第一个任务")
sleep(2)
print("结束第一个任务")

})

let secondWorkItem = DispatchWorkItem.init(block: {

print("开始第二个任务")
sleep(2)
print("结束第二个任务")

})

tasksQueue.async(execute: firstWorkItem)
tasksQueue.async(execute: secondWorkItem)

// 等待 1s,让第一个任务开始运行
sleep(1)

firstWorkItem.cancel()
print("尝试过取消第一个任务")
secondWorkItem.cancel()
print("尝试过取消第二个任务")

输出内容同 object-c。

flutter虽然可以一套代码生成安卓和IOS的APP,但是这两者有着各自的设计语言:Material和Cupertino。因此为了让APP能够更好的融合进对应的系统设计语言,我们要对页面中的控件进行一些处理。

引入IOS控件库:
前面已经引入Material.dart控件库,但还缺少了IOS的Cupertino控件库,因此在main.dart的头部中引入:
import 'package:flutter/cupertino.dart';
定义Material和Cupertino的主题风格
Material为默认主题,当检测到APP运行在IOS时使用Cupertino主题:

final ThemeData kIOSTheme = new ThemeData( //Cupertino主题风格
primarySwatch: Colors.orange,
primaryColor: Colors.grey[100],
primaryColorBrightness: Brightness.light,
);

final ThemeData kDefaultTheme = new ThemeData( //默认的Material主题风格
primarySwatch: Colors.purple,
accentColor: Colors.orangeAccent[400],
);

根据运行的操作系统判断对应的主题:
首先要引入一个用于识别操作系统的工具库,其内的defaultTargetPlatform值可帮助我们识别操作系统:

import 'package:flutter/foundation.dart';
到程序的入口控件FriendlychatApp中应用对应的操作系统主题:

class FriendlychatApp extends StatelessWidget {
@override
Widget build(BuildContext context) {

return new MaterialApp(
  title: "Friendlychat",
  theme: defaultTargetPlatform == TargetPlatform.iOS         //newdefaultTargetPlatform用于识别操作系统
    ? kIOSTheme                                              //new
    : kDefaultTheme,                                         //new
  home: new ChatScreen(),
);

}
}
页面标题的风格适配
页面顶部显示Friendlychat的标题栏的下方,在IOS的Cupertino设计语言中没有阴影,与下面的应用主体通过一条灰色的线分隔开,而Material则通过标题栏下方的阴影达到这一效果,因此将两种特性应用到代码中:
// Modify the build() method of the ChatScreenState class.
Widget build(BuildContext context) {

  return new Scaffold(
    appBar: new AppBar(
      title: new Text("Friendlychat"),
      elevation: Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0),    //new  适配IOS的扁平化无阴影效果
      body: new Container(                    //modified    使用Container控件,方便加入主题风格装饰
        child: new Column(                      //modified
          children: <Widget>[
            new Flexible(
              child: new ListView.builder(
                padding: new EdgeInsets.all(8.0),
                reverse: true,
                itemBuilder: (_, int index) => _messages[index],
                itemCount: _messages.length,
              ),
            ),
          new Divider(height: 1.0),
          new Container(
          decoration: new BoxDecoration(color: Theme.of(context).cardColor),
          child: _buildTextComposer(),
        ),
      ],
    ),
      decoration: Theme.of(context).platform == TargetPlatform.iOS //new    加入主题风格
    ? new BoxDecoration(                                     //new
        border: new Border(                                  //new  为适应IOS加入边框特性
          top: new BorderSide(color: Colors.grey[200]),      //new  顶部加入灰色边框
        ),                                                   //new
      )                                                      //new
    : null),                                                 //modified  
  );
}

发送按钮的风格适配
发送按钮在APP遇到IOS时,使用Cupertino风格的按钮:
// Modify the _buildTextComposer method.

new Container(
margin: new EdgeInsets.symmetric(horizontal: 4.0),
child: Theme.of(context).platform == TargetPlatform.iOS ? //modified
new CupertinoButton( //new 使用Cupertino控件库的CupertinoButton控件作为IOS端的发送按钮

 child: new Text("Send"),                         //new
 onPressed: _isComposing                                  //new
     ? () =>  _handleSubmitted(_textController.text)      //new
     : null,) :                                           //new

new IconButton( //modified

   icon: new Icon(Icons.send),
   onPressed: _isComposing ?
       () =>  _handleSubmitted(_textController.text) : null,
   )

),

7700793-3badada7f204e17e.png
我们可以创建个抽象的组件,并让它根据其运行的平台,显示不同风格的Widget。

代码实现
创建一个抽象类,子类只需要重写对应平台的抽象方法就可以了。
/**

  • 会根据平台,去适配所在平台的小部件
  • Flutter中包含了适用于IOS和Android的两套原生小部件,名为Cupertino和Material
    */

abstract class BasePlatformWidget

extends StatelessWidget {

A createAndroidWidget(BuildContext context);

I createIosWidget(BuildContext context);

@override
Widget build(BuildContext context) {

/**如果是IOS平台,返回ios风格的控件
 * Android和其他平台都返回materil风格的控件
 */
if (Platform.isIOS) {
  return createIosWidget(context);
}
return createAndroidWidget(context);

}
}
下面根据这个基础的平台类,去创建一些基本的Widget。比如去创建一个在Android平台显示AppBar,在ios平台显示CupertinoNavigationBar的Widget。
/**

class PlatformScaffold

extends BasePlatformWidget<Scaffold, CupertinoPageScaffold> {

PlatformScaffold({this.appBar, this.body});

final PlatformAppBar appBar;
final Widget body;

@override
Scaffold createAndroidWidget(BuildContext context) {

return Scaffold(
  appBar: appBar.createAndroidWidget(context),
  body: body,
);

}

@override
CupertinoPageScaffold createIosWidget(BuildContext context) {

return CupertinoPageScaffold(
  navigationBar: appBar.createIosWidget(context),
  child: body,
);

}
}

/**

class PlatformAppBar

extends BasePlatformWidget<AppBar, CupertinoNavigationBar> {

final Widget title;
final Widget leading;

PlatformAppBar({this.title, this.leading});

@override
AppBar createAndroidWidget(BuildContext context) {

return new AppBar(leading: leading, title: title,);

}

@override
CupertinoNavigationBar createIosWidget(BuildContext context) {

return new CupertinoNavigationBar(leading: leading, middle: title,);

}

}

/**

class PlatformAlertDialog

extends BasePlatformWidget<AlertDialog, CupertinoAlertDialog> {

final Widget title;
final Widget content;
final List<Widget> actions;

PlatformAlertDialog({this.title, this.content, this.actions});

@override
AlertDialog createAndroidWidget(BuildContext context) {

return new AlertDialog(title: title, content: content, actions: actions,);

}

@override
CupertinoAlertDialog createIosWidget(BuildContext context) {

return new CupertinoAlertDialog(
  title: title, content: content, actions: actions,);

}
}

/**

class PlatformSwicth extends BasePlatformWidget<Switch, CupertinoSwitch> {

final bool value;
final ValueChanged<bool> onChanged;

PlatformSwicth({this.value, this.onChanged});

@override
Switch createAndroidWidget(BuildContext context) {

return new Switch(value: value, onChanged: onChanged);

}

@override
CupertinoSwitch createIosWidget(BuildContext context) {

return new CupertinoSwitch(value: value, onChanged: onChanged);

}
}

/**

class PlatformButton extends BasePlatformWidget<FlatButton, CupertinoButton> {
final VoidCallback onPressed;
final Widget child;

PlatformButton({this.onPressed, this.child});

@override
FlatButton createAndroidWidget(BuildContext context) {

return new FlatButton(onPressed: onPressed, child: child);

}

@override
CupertinoButton createIosWidget(BuildContext context) {

return new CupertinoButton(child: child, onPressed: onPressed);

}
}