在你点击按钮,滑动列表,缩放图片等等交互过程当中,在背后却有成千上百的事件触发,如何处理这些事件?如何掌控事件的流动?不管在web, android或者ios,都是学习的一个难点,在Flutter同理也是同样,究竟Flutter的事件流有啥特别之处,接下来就慢慢展现给你们。android
事件从哪里来?通常来讲都不须要应用开发者去担忧事件是如何从硬件收集起来的,可是事件的传递总须要有个源头。
在Flutter里面主要处理事件和手势相关的就在gestures文件夹下。
而Flutter框架事件的源头就在gestures/binding.dart里的GestureBinding类开始:ios
void initInstances() { super.initInstances(); _instance = this; ui.window.onPointerDataPacket = _handlePointerDataPacket; }
可见事件是由ui.window.onPointerDataPacket产生,把事件传给GestureBinding._handlePointerDataPacket方法,而ui.window这个就是sky引擎的实现,之后有机会再去深刻,如今只需关注上层。
纵观整个代码,会发现有不少binding,SchedulerBinding,GestureBinding,ServicesBinding,RendererBinding和WidgetsBinding等都跟引擎相关的,之后再慢慢逐个分析。
接着继续跟踪方法的调用过程:web
先看_handlePointerEvent方法:浏览器
void _handlePointerEvent(PointerEvent event) { assert(!locked); HitTestResult result; if (event is PointerDownEvent) { assert(!_hitTests.containsKey(event.pointer)); result = new HitTestResult(); hitTest(result, event.position); _hitTests[event.pointer] = result; assert(() { if (debugPrintHitTestResults) debugPrint('$event: $result'); return true; }()); } else if (event is PointerUpEvent || event is PointerCancelEvent) { result = _hitTests.remove(event.pointer); } else if (event.down) { result = _hitTests[event.pointer]; } else { return; // We currently ignore add, remove, and hover move events. } if (result != null) dispatchEvent(event, result); }
当是PointerDownEvent事件的时候,会新建一个HitTestResult对象,而这个HitTestResult对象里面有一个path的属性,能够推测这个属性就是用来记录事件传递所通过的的节点。
新建HitTestResult对象后,接下来重点就是调用GestureBinding.histTest方法。
在看看hitTest方法:app
void hitTest(HitTestResult result, Offset position) { result.add(new HitTestEntry(this)); }
这里把自身添加到HitTestResult上,意味着之后dispatchEvent时候会遍历path上的HitTestEntry,也会调起GestureBinding.handleEvent方法。
接着再看handleEvent方法:框架
void handleEvent(PointerEvent event, HitTestEntry entry) { pointerRouter.route(event); if (event is PointerDownEvent) { gestureArena.close(event.pointer); } else if (event is PointerUpEvent) { gestureArena.sweep(event.pointer); } }
这里就看到pointerRouter路由事件,以及手势相关的一些处理,手势等会再说。
可是看完整个方法调用都没看到事件是如何传递到节点树上,而pointerRouter仅仅是一个观察者模式的实现,找遍了代码也没找到对应的listener,事件是如何传递?咱们的点击事件是如何响应?依然不清楚。ide
既然GestureBinding上并无事件如何传递节点树的实现,再看哪里用到这个类,总有地方须要依赖它的。
很快就注意到WidgetsFlutterBinding这个类了。学习
class WidgetsFlutterBinding extends BindingBase with SchedulerBinding, GestureBinding, ServicesBinding, RendererBinding, WidgetsBinding { static WidgetsBinding ensureInitialized() { if (WidgetsBinding.instance == null) new WidgetsFlutterBinding(); return WidgetsBinding.instance; } }
WidgetsFlutterBinding这个类mixin了好几个Binding,同时这个类也是框架的初始化入口,当咱们跑起整个Flutter应用时:ui
void main() { runApp(new MyApp()); }
runApp其实就会执行WidgetsFlutterBinding.ensureInitialized方法初始化各个Binding:this
void runApp(Widget app) { WidgetsFlutterBinding.ensureInitialized() ..attachRootWidget(app) ..scheduleWarmUpFrame(); }
而后attachRootWidget方法,就去设置根节点了:
void attachRootWidget(Widget rootWidget) { _renderViewElement = new RenderObjectToWidgetAdapter<RenderBox>( container: renderView, debugShortDescription: '[root]', child: rootWidget ).attachToRenderTree(buildOwner, renderViewElement); }
这里看到了真正的根节点renderView,事件怎样也应该从根节点开始传递吧。
沿着renderView就找到RndererBinding.hitTest方法:
void hitTest(HitTestResult result, Offset position) { assert(renderView != null); renderView.hitTest(result, position: position); // This super call is safe since it will be bound to a mixed-in declaration. super.hitTest(result, position); // ignore: abstract_super_member_reference }
到这里基本能够肯定先调用RendererBinding.histTest方法接着调用GestureBinding.histTest方法。
再回头GestureBinding的实现也就是先让renderView.hitTest方法去肯定事件传递路径,都添加到HitTestResult的path上,最后再添加GestureBinding自身做为最后的一个HitTestEntry。
而GestureBindg.dispatchEvent会遍历这些HitTestEntry调用他们的handleEvent方法:
void dispatchEvent(PointerEvent event, HitTestResult result) { assert(!locked); assert(result != null); for (HitTestEntry entry in result.path) { try { entry.target.handleEvent(event, entry); } catch (exception, stack) { FlutterError.reportError(new FlutterErrorDetailsForPointerEventDispatcher( exception: exception, stack: stack, library: 'gesture library', context: 'while dispatching a pointer event', event: event, hitTestEntry: entry, informationCollector: (StringBuffer information) { information.writeln('Event:'); information.writeln(' $event'); information.writeln('Target:'); information.write(' ${entry.target}'); } )); } } }
还有一个重点就是节点上hitTest方法实现,而节点通常都是继承自RenderBox的实现:
bool hitTest(HitTestResult result, { @required Offset position }) { if (_size.contains(position)) { if (hitTestChildren(result, position: position) || hitTestSelf(position)) { result.add(new BoxHitTestEntry(this, position)); return true; } } return false; }
固然首先判断点击是否在节点位置上,而后再交给children处理,接着自身处理,若是hitTestChildren或者hitTestSelf返回true,就把当前节点加入到HitTestResult上。
这个时候HitTestResult中的路径顺序通常就是:
目标节点-->父节点-->根节点-->GestureBinding
接着PointerDown,PointerMove,PointerUp,PointerCancel等事件分发,都根据这个顺序来遍历调用它们的handleEvent方法,就像浏览器事件的冒泡过程同样,既然像冒泡同样,搞过web开发的同窗都知道,浏览器是能够用代码阻止冒泡的,那Flutter行不行尼?答案,暂时尚未发现有方法能够阻止这个冒泡过程。
如今已经清楚框架的事件流,如今开始深刻框架的手势系统。
The GestureDetector widget decides which gestures to attempt to recognize based on which of its callbacks are non-null.
根据文档所说GestureDetector控件能够检测手势,而且根据手势调起相应回调。
GestureDector真的支持了至关多的手势,基本上经常使用都有了,框架实在太给力!
那GestureDector控件为何有这么大本领,而手势是如何检测的尼?
先对这个控件层层剥皮,看它的build方法:
Widget build(BuildContext context) { final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{}; ... return new RawGestureDetector( gestures: gestures, behavior: behavior, excludeFromSemantics: excludeFromSemantics, child: child, ); } }
能够看到GestureDector其实就是根据注册的回调,添加对应的GestureRecognizer(手势识别器),并全传递到RawGestureDetector。
而RawGestureDetector的build方法:
Widget build(BuildContext context) { Widget result = new Listener( onPointerDown: _handlePointerDown, behavior: widget.behavior ?? _defaultBehavior, child: widget.child ); if (!widget.excludeFromSemantics) result = new _GestureSemantics(owner: this, child: result); return result; }
关键在于_handlePointerDown方法:
void _handlePointerDown(PointerDownEvent event) { assert(_recognizers != null); for (GestureRecognizer recognizer in _recognizers.values) recognizer.addPointer(event); }
遍历_recognizers(手势识别器)调用addPointer方法,通常来讲recognizer都是继承自PrimaryPointerGestureRecognizer的实现:
void addPointer(PointerDownEvent event) { startTrackingPointer(event.pointer); if (state == GestureRecognizerState.ready) { state = GestureRecognizerState.possible; primaryPointer = event.pointer; initialPosition = event.position; if (deadline != null) _timer = new Timer(deadline, didExceedDeadline); } }
到这里先理一下流程,当肯定PointerDown事件落在GestureDector控件下的子组件时,在GestureDector上注册的GesutreRecognizer就会追踪这个pointer(就是咱们的手指),注意了这里仍是设置一个Timer后面再说有什么做用,先看startTrackingPointer方法:
void startTrackingPointer(int pointer) { GestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent); _trackedPointers.add(pointer); assert(!_entries.containsValue(pointer)); _entries[pointer] = _addPointerToArena(pointer); }
啊哈,这里用到了GestureBinding.instance.pointerRouter,还记得上面提到的吗,事件传递的最后一站其实就是GestureBinding,而后调用它的handleEvent方法,到最后就是调用pointer.route方法路由事件,因此还要调用GestureRecognizer的handleEvent方法。
接着再看GestureRcognizer._addPointerToArea方法
GestureArenaEntry _addPointerToArena(int pointer) { if (_team != null) return _team.add(pointer, this); return GestureBinding.instance.gestureArena.add(pointer, this); }
这里又用到GestureBinding.instance.gestureArena,其实就是GestureArenaManager,再看add方法:
GestureArenaEntry add(int pointer, GestureArenaMember member) { final _GestureArena state = _arenas.putIfAbsent(pointer, () { assert(_debugLogDiagnostic(pointer, '★ Opening new gesture arena.')); return new _GestureArena(); }); state.add(member); assert(_debugLogDiagnostic(pointer, 'Adding: $member')); return new GestureArenaEntry._(this, pointer, member); }
这里就是新建了一个GestureArenaEntry对象,好吧,咱们得整理一下他们的关系:
class GestureArenaManager { final Map<int, _GestureArena> _arenas = <int, _GestureArena>{}; } class _GestureArena { final List<GestureArenaMember> members = <GestureArenaMember>[]; } class OneSequenceGestureRecognizer extends GestureArenaMember { final Map<int, GestureArenaEntry> _entries = <int, GestureArenaEntry>{}; }
下面用个形象一点的方法描述它们的关系:
首先咱们有一批竞技选手(各类Recognizer),咱们也可能会有好几个竞技场地(_GestureArena),咱们的场地管理员(GestureArenaManager)会根据Pointer的多少来构建场地,可是各个选手也要拿到每一个竞技场的入场券(GestureArenaEntry)才能入场与其余选手一较高下。
当咱们的选手拿着对应的入场券进场后,如今各个场地都汇集了一批选手,叮的一声(PointerDown事件),各个场地入口关闭,过了一会激烈的竞技,又叮的一声(PointerUp事件)竞技结束,咱们就要打扫竞技场看一下哪一位选手胜利了。
这里PointerDown事件和PointerUp事件控制场地关闭和打扫,主要代码在GestureBinding.handleEvent方法上,上面就有提到,这里就不贴了。
那么怎么判断哪一个手势是最后赢得胜利留下来的呢,不像现实竞技场那么残酷,这里是很斯文优雅的,对手本身会判断是否要退出竞争,判断条件固然是PointerDown,PointerMove,PointerUp事件传递的信息是否符合当前手势的定义,若是不符合就自动退出,若是符合就向竞技场(_GestureArena)申请我符合条件,请判我获取胜利,其余手势只能判断为失败了。
可是这里也会有一些状况须要特别处理:
因此整个竞技场的核心,只是仅仅让当前手势知道已经没有别的手势竞争,能够本身判断是否符合当前手势的定义而触发相应的事件,因此竞技场胜利的一方并非百分百触发手势的,得到竞技场胜利只是触发手势的必要非充分条件。
固然整个机制仍是有点出入的,下面还会继续分析。
例如TapGestureRecognizer,在不存在竞争的状况时,当GestureAreaManager.close调起时:
void close(int pointer) { final _GestureArena state = _arenas[pointer]; if (state == null) return; // This arena either never existed or has been resolved. state.isOpen = false; assert(_debugLogDiagnostic(pointer, 'Closing', state)); _tryToResolveArena(pointer, state); }
就会接着调起_tryToResolveArena方法:
void _tryToResolveArena(int pointer, _GestureArena state) { assert(_arenas[pointer] == state); assert(!state.isOpen); if (state.members.length == 1) { //没有竞争的状况 scheduleMicrotask(() => _resolveByDefault(pointer, state)); } else if (state.members.isEmpty) { _arenas.remove(pointer); assert(_debugLogDiagnostic(pointer, 'Arena empty.')); } else if (state.eagerWinner != null) { assert(_debugLogDiagnostic(pointer, 'Eager winner: ${state.eagerWinner}')); _resolveInFavorOf(pointer, state, state.eagerWinner); } }
由于是没有竞争者,因此就会跳进_resolveByDefault方法:
void _resolveByDefault(int pointer, _GestureArena state) { if (!_arenas.containsKey(pointer)) return; // Already resolved earlier. assert(_arenas[pointer] == state); assert(!state.isOpen); final List<GestureArenaMember> members = state.members; assert(members.length == 1); _arenas.remove(pointer); assert(_debugLogDiagnostic(pointer, 'Default winner: ${state.members.first}')); state.members.first.acceptGesture(pointer); }
这里最后就调起TapGestureRecognizer.acceptGesture方法:
void acceptGesture(int pointer) { super.acceptGesture(pointer); if (pointer == primaryPointer) { _checkDown(); _wonArenaForPrimaryPointer = true; _checkUp(); } }
_checkDown会尝试调起onTapDown,这里能够说onTapDown会当即调用,_checkUp会尝试调起onTapUp,onTap的回调(至少等onPointerUp事件触发才会成功)。
接下来咱们考虑若是父节点监听了Tap手势,也就是出现竞争状况,两个都是TapGestureRecognizer,状况会怎样的尼?
很明显GestureAreaManager.close方法中的_tryToResolveArena方法并无起到啥做用,这个时候你们还记得deadline这个超时时间吗,TapGestureRecognizer设置的超时时间为100毫秒,当咱们按下的时间超过100毫秒
TapGestureRecognizer.didExceedDeadline就会调用接着调起_checkDown方法(意味着onTapDown触发有可能延迟100毫秒,并不彻底是你点下的一瞬间就触发),可是咱们点击的时间很快(低于100毫秒)的时候又怎样尼?
别忘了在GestureAreaManager的方法处理以前,pointerRouter先会路由事件,直接调起 TapGestureRecognizer.handleEvent
-->TapGestureRecognizer.handlePrimaryPointer
-->TapGestureRecognizer._checkUp
-->TapGestureRecognizer.stopTrackingIfPointerNoLongerDown
-->TapGestureRecognizer.didStopTrackingLastPointer
既然咱们在上面事件流的分析知道,事件流就相似浏览器事件冒泡的方式,因此注册在pointerRouter的监听器也是子组件优先调用接着是父组件。接着stopTrackingIfPointerNoLongerDown方法将注册的监听器从pointerRouter移除,didStopTrackingLastPointer方法把TapGestureRecognizer的状态设置成ready,准备好下次手势处理。
这里再简单介绍一下GestureRecognizer的几个状态:
最后就是打扫竞技场了:
void sweep(int pointer) { final _GestureArena state = _arenas[pointer]; if (state == null) return; // This arena either never existed or has been resolved. assert(!state.isOpen); if (state.isHeld) { state.hasPendingSweep = true; assert(_debugLogDiagnostic(pointer, 'Delaying sweep', state)); return; // This arena is being held for a long-lived member. } assert(_debugLogDiagnostic(pointer, 'Sweeping', state)); _arenas.remove(pointer); if (state.members.isNotEmpty) { // First member wins. assert(_debugLogDiagnostic(pointer, 'Winner: ${state.members.first}')); state.members.first.acceptGesture(pointer); // Give all the other members the bad news. for (int i = 1; i < state.members.length; i++) state.members[i].rejectGesture(pointer); } }
默认会让第一个手势胜出,其余都会调起rejectGesture方法。可是在咱们刚才的举的例子已经不起做用了,由于手势都处理完毕,都回到ready状态了。
在看看若是是两个不一样类型的手势竞争的状况下会怎样,例如:TapGestureRecognizer 和 LongPressGestureRecognizer。
假设在GestureDector上同时注册了onTap和onLongPress。
这个时候GestureRecognizer注册的顺序就很重要了,在GestureDector里面框架已经设置好各自顺序,这里TapGestureRecognizer先于LongPressGestureRecognizer处理事件,由于最后处理手势的时候默认是第一个胜出的。
LongPressGestureRecognizer设置的超时时间为500毫秒,若是点击时间低于500毫秒时,就好像没有竞争状况同样,onTap回调正常调起,可是点击时间超过500毫秒,又会怎样尼?
这时就会调起LongPressGestureRecognizer.didExceedDeadline方法:
void didExceedDeadline() { resolve(GestureDisposition.accepted); if (onLongPress != null) invokeCallback<Null>('onLongPress', onLongPress); }
而接着调起的就是GestureArenaManager._resolve方法:
void _resolve(int pointer, GestureArenaMember member, GestureDisposition disposition) { final _GestureArena state = _arenas[pointer]; if (state == null) return; // This arena has already resolved. assert(_debugLogDiagnostic(pointer, '${ disposition == GestureDisposition.accepted ? "Accepting" : "Rejecting" }: $member')); assert(state.members.contains(member)); if (disposition == GestureDisposition.rejected) { state.members.remove(member); member.rejectGesture(pointer); if (!state.isOpen) _tryToResolveArena(pointer, state); } else { assert(disposition == GestureDisposition.accepted); if (state.isOpen) { state.eagerWinner ??= member; } else { assert(_debugLogDiagnostic(pointer, 'Self-declared winner: $member')); _resolveInFavorOf(pointer, state, member); } } }
由于被决议为accepted,最后调起_resolveInFavorOf方法,至于eagerWinner的设置是在hitTest时候resolve才会起效。
再看_resolveInFavorOf方法:
void _resolveInFavorOf(int pointer, _GestureArena state, GestureArenaMember member) { assert(state == _arenas[pointer]); assert(state != null); assert(state.eagerWinner == null || state.eagerWinner == member); assert(!state.isOpen); _arenas.remove(pointer); for (GestureArenaMember rejectedMember in state.members) { if (rejectedMember != member) rejectedMember.rejectGesture(pointer); } member.acceptGesture(pointer); }
直接reject了TapGestureRecognizer,TapGestureRecognizer的状态被设置为defunt,LongPressGestureRecognizer成为最后的优胜者。
咱们能够在GestureRecognizer.handleEvent判断手势是否符合本身定义,例如滑动多少距离范围;设置deadline超时时间规定手势需在多少时间内完成,或者超出多少时间才符合定义;当检测到手势符合咱们定义或者不符合时,能够调起resolve决议,让其余手势识别放弃监听手势并重置状态;咱们自定义手势识别器应在rejectGesture作一些清理或者状态重置的工做。