在你點擊按鈕,滑動列表,縮放圖片等等交互過程當中,在背後卻有成千上百的事件觸發,如何處理這些事件?如何掌控事件的流動?不管在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作一些清理或者狀態重置的工做。