上一篇文章 深刻理解Flutter的Listener組件 介紹了觸控事件的監聽原理,讓咱們對Flutter
中觸摸事件的傳遞過程有了進一步的認識。bash
今天咱們學習一下手勢識別組件GestureDetector的原理。GestureDetector
的內部實現使用的是Listener
組件,若是對Listener
還不太熟悉,能夠先了解一下Listener
的原理。markdown
GestureDector
是一個無狀態組件,它的build
方法以下所示。less
class GestureDetector extends StatelessWidget { ...省略 Widget build(BuildContext context) { ...省略 return RawGestureDetector( gestures: gestures, behavior: behavior, excludeFromSemantics: excludeFromSemantics, child: child, ); } } 複製代碼
build
方法直接返回了RawGestureDetector
組件,說明GestureDetector
是由子組件RawGestureDetector
構成的。而RawGestureDetector
是一個有狀態組件,它的State
的build
方法以下所示。ide
class RawGestureDetector extends StatefulWidget { @override RawGestureDetectorState createState() => RawGestureDetectorState(); } class RawGestureDetectorState extends State<RawGestureDetector> { ...省略 @override Widget build(BuildContext context) { Widget result = Listener( onPointerDown: _handlePointerDown, behavior: widget.behavior ?? _defaultBehavior, child: widget.child, ); if (!widget.excludeFromSemantics) result = _GestureSemantics(owner: this, child: result); return result; } } 複製代碼
build
方法裏面返回了Listener
組件,這也證實了上面的結論:post
GestureDetector
的內部實現使用的是Listener
組件。學習
相比於Listener
,GestureDetector
有本身的屬性,如onDoubleTap
、onLongPress
、onHorizontalDragStart
、onVerticalDragStart
等。測試
其實說到底,這些屬性也是由Listener
的onPointerDown
、onPointerMove
、onPointerUp
這三個屬性封裝而成的。ui
從新看一下RawGestureDetector
的State
的build
方法。this
@override Widget build(BuildContext context) { Widget result = Listener( onPointerDown: _handlePointerDown, behavior: widget.behavior ?? _defaultBehavior, child: widget.child, ); if (!widget.excludeFromSemantics) result = _GestureSemantics(owner: this, child: result); return result; } 複製代碼
Listener
組件的child
屬性是由GestureDector
傳遞進來的,也就是說GestureDector
自上而下的Widget
構成以下圖所示。 spa
從以前對Listener
組件的分析可知,Listener
中的Child
的觸摸事件由onPointerDown
、onPointerMove
、onPointerUp
等屬性值決定。
這裏Listener
屬性值爲_handlePointerDown
,它是一個方法。
void _handlePointerDown(PointerDownEvent event) { assert(_recognizers != null); for (GestureRecognizer recognizer in _recognizers.values) recognizer.addPointer(event); } 複製代碼
該方法遍歷了_recognizers
裏面的值(值類型爲GestureRecognizer
),_recognizers
又是在_syncAll
方法中賦值的。
void _syncAll(Map<Type, GestureRecognizerFactory> gestures) { final Map<Type, GestureRecognizer> oldRecognizers = _recognizers; _recognizers = <Type, GestureRecognizer>{}; for (Type type in gestures.keys) { _recognizers[type] = oldRecognizers[type] ?? gestures[type].constructor(); //重要方法 gestures[type].initializer(_recognizers[type]); //重要方法 } for (Type type in oldRecognizers.keys) { if (!_recognizers.containsKey(type)) oldRecognizers[type].dispose(); } } 複製代碼
_syncAll
方法會將原有的_recognizers
保存下來,而後遍歷參數中的gestures
,若原有的_recognizers
有該手勢類型對象,則使用,不然調用gestures[type]
的constructor
方法。而後繼續調用gestures[type]
的initializer
方法。記住constructor
和initializer
這兩個方法,後面的分析須要用到。
_syncAll
方法在兩處地方被調用,分別是initState
和didUpdateWidget
方法。
@override void initState() { super.initState(); _syncAll(widget.gestures); } @override void didUpdateWidget(RawGestureDetector oldWidget) { super.didUpdateWidget(oldWidget); _syncAll(widget.gestures); } 複製代碼
組件初始化會調用initState
方法,並傳遞了widget
的gestures
屬性,這裏的widget
是指RawGestureDetector
組件。
讓咱們再回過頭來看GestureDector
的build
方法,以下所示。
@override Widget build(BuildContext context) { final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{}; if ( onTapDown != null || onTapUp != null || onTap != null || onTapCancel != null || onSecondaryTapDown != null || onSecondaryTapUp != null || onSecondaryTapCancel != null ) { gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>( () => TapGestureRecognizer(debugOwner: this), (TapGestureRecognizer instance) { instance ..onTapDown = onTapDown ..onTapUp = onTapUp ..onTap = onTap ..onTapCancel = onTapCancel ..onSecondaryTapDown = onSecondaryTapDown ..onSecondaryTapUp = onSecondaryTapUp ..onSecondaryTapCancel = onSecondaryTapCancel; }, ); } if (onDoubleTap != null) { gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>( () => DoubleTapGestureRecognizer(debugOwner: this), (DoubleTapGestureRecognizer instance) { instance ..onDoubleTap = onDoubleTap; }, ); } if (onLongPress != null || onLongPressUp != null || onLongPressStart != null || onLongPressMoveUpdate != null || onLongPressEnd != null) { gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>( () => LongPressGestureRecognizer(debugOwner: this), (LongPressGestureRecognizer instance) { instance ..onLongPress = onLongPress ..onLongPressStart = onLongPressStart ..onLongPressMoveUpdate = onLongPressMoveUpdate ..onLongPressEnd =onLongPressEnd ..onLongPressUp = onLongPressUp; }, ); } ...省略 return RawGestureDetector( gestures: gestures, behavior: behavior, excludeFromSemantics: excludeFromSemantics, child: child, ); } 複製代碼
首先初始化了gestures
,而且對於每一種手勢族都定義了一種類型。
一、TapGestureRecognizer
手勢族裏面就包含了onTapDown
、onTapUp
、onTap
、onTapCancel
、onSecondaryTapDown
、onSecondaryTapUp
、onSecondaryTapCancel
事件。
二、DoubleTapGestureRecognizer
手勢族裏面就包含了onDoubleTap
事件。
三、LongPressGestureRecognizer
手勢族裏面就包含了onLongPress
、onLongPressStart
、onLongPressMoveUpdate
、onLongPressEnd
、onLongPressUp
事件。
gestures
中的每個值都是GestureRecognizerFactory
類型。經過GestureRecognizerFactoryWithHandlers
的構造方法,分別給GestureRecognizerFactory
的constructor、initializer
方法進行初始化。
還記得RawGestureDetector
組件的_syncAll
中提到的constructor、initializer
方法麼?因此結合起來看,咱們得出了以下結論:
GestureDetector
的多種手勢屬性,都有其所屬的手勢族(GestureRecognizerFactory
對象)。這些屬性會經過手勢族的initializer
方法保存起來。
GestureDetector( child: ConstrainedBox( constraints: BoxConstraints.tight(Size(300, 150)), child: Container( color: Colors.blue, child: Center( child: Text('click me'), ), ), ), onTapDown: (TapDownDetails details) { print("onTap down"); }, onTapUp: (TapUpDetails details) { print("onTap up"); }, ), 複製代碼
運行上面的代碼後,展現以下。
GestureDector
本質上也是
Listener
,因此當咱們點擊了
click me文案後,須要執行命中測試,命中測試列表以下所示:
RenderParagraph
->
RenderPositionedBox
->
RenderDecoratedBox
->
RenderConstrainedBox
->
RenderPointerListener
。
根據命中測試列表,從上而下執行每個對象的handleEvent
方法。Listener
對應的RenderObject
就是RenderPointerListener
,而RenderPointerListener
的handleEvent
方法以下。
@override void handleEvent(PointerEvent event, HitTestEntry entry) { assert(debugHandleEvent(event, entry)); if (onPointerDown != null && event is PointerDownEvent) return onPointerDown(event); if (onPointerMove != null && event is PointerMoveEvent) return onPointerMove(event); if (onPointerUp != null && event is PointerUpEvent) return onPointerUp(event); if (onPointerCancel != null && event is PointerCancelEvent) return onPointerCancel(event); if (onPointerSignal != null && event is PointerSignalEvent) return onPointerSignal(event); } 複製代碼
這裏的onPointerDown
、onPointerMove
、onPointerUp
、onPointerCancel
、onPointerSignal
屬性和Listener
中是一一對應的。
當點擊click me文案時,因爲onPointerDown!=null && event is PointerDownEvent
爲true,從而執行了Listener
中的onPointerDown
方法,也就是RawGestureDetector
組件的_handlePointerDown
方法。
void _handlePointerDown(PointerDownEvent event) { assert(_recognizers != null); for (GestureRecognizer recognizer in _recognizers.values) recognizer.addPointer(event); } 複製代碼
_recognizers.values
值遍歷的結果咱們上面分析過了,這裏遍歷的結果是每次都會去執行GestureRecognizer
對象的addPointer
方法。
void addPointer(PointerDownEvent event) { _pointerToKind[event.pointer] = event.kind; if (isPointerAllowed(event)) { addAllowedPointer(event); } else { handleNonAllowedPointer(event); } } 複製代碼
首先經過了isPointerAllowed
方法判斷PointerDownEvent
手勢事件是否被GestureRecognizer
對象所接受,通常每個GestureRecognizer
對象都會重寫isPointerAllowed
方法。
對於上面的例子,這裏的GestureRecognizer
對象就是TapGestureRecognizer
,它的addAllowedPointer
方法以下所示。
@override
void addAllowedPointer(PointerDownEvent event) {
super.addAllowedPointer(event);
_initialButtons = event.buttons;
}
複製代碼
這裏直接調用了父類的addAllowedPointer
方法。
@override void addAllowedPointer(PointerDownEvent event) { startTrackingPointer(event.pointer, event.transform); if (state == GestureRecognizerState.ready) { state = GestureRecognizerState.possible; primaryPointer = event.pointer; initialPosition = OffsetPair(local: event.localPosition, global: event.position); if (deadline != null) _timer = Timer(deadline, () => didExceedDeadlineWithEvent(event)); } } 複製代碼
而後是startTrackingPointer
方法。
@protected
void startTrackingPointer(int pointer, [Matrix4 transform]) {
GestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent, transform);
_trackedPointers.add(pointer);
assert(!_entries.containsValue(pointer));
_entries[pointer] = _addPointerToArena(pointer);
}
複製代碼
一、第一步經過GestureBinding.instance.pointerRouter
調用了addRoute
方法,參數爲PointerDownEvent
事件的惟一值(pointer
)、handleEvent
對象(由GestureRecognizer
的子類實現)、PointerDownEvent
事件座標系(transform
)。
注意:這裏的
GestureBinding.instance
返回的是GestureBinding
的對象,它是一個單例類,做用是管理手勢事件生命週期與手勢衝突。
void addRoute(int pointer, PointerRoute route, [Matrix4 transform]) {
final LinkedHashSet<_RouteEntry> routes = _routeMap.putIfAbsent(pointer, () => LinkedHashSet<_RouteEntry>());
assert(!routes.any(_RouteEntry.isRoutePredicate(route)));
routes.add(_RouteEntry(route: route, transform: transform));
}
複製代碼
addRoute
方法將在_routeMap
中尋找pointer
對應的LinkedHashSet
,不存在則新建一個,而後建立一個_RouteEntry
對象,並將route
和transform
傳遞過去。
二、第二步調用了_addPointerToArena
方法。
GestureArenaEntry _addPointerToArena(int pointer) { if (_team != null) return _team.add(pointer, this); return GestureBinding.instance.gestureArena.add(pointer, this); } 複製代碼
在_addPointerToArena
方法中,也經過GestureBinding.instance.gestureArena
調用了add
方法,參數爲PointerDownEvent
事件的惟一值(pointer
)、GestureRecognizer
對象(具體子類)。
還記得咱們點擊click me文案時,上面提到的命中測試列表麼?其實上面只是列出了一部分,在RenderPointerListener
的最後還有WidgetsFlutterBinding
。因此應該是這樣的: RenderParagraph
->RenderPositionedBox
->RenderDecoratedBox
->RenderConstrainedBox
->RenderPointerListener
->...->WidgetsFlutterBinding
因此在命中測試列表最後一步,執行的是WidgetsFlutterBinding
的handleEvent
方法,這一步很重要,咱們來看一下。
@override 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); } else if (event is PointerSignalEvent) { pointerSignalResolver.resolve(event); } } 複製代碼
方法中參數event
是一個PointerEvent
對象,由PointerDownEvent
、一系列PointerMoveEvent
、PointerUpEvent
事件組成,對於每個PointerEvent
事件,都會執行pointerRouter
的route
方法。這裏的pointerRouter
對象就是GestureBinding.instance.pointerRouter
對象。
void route(PointerEvent event) { final LinkedHashSet<_RouteEntry> routes = _routeMap[event.pointer]; final List<_RouteEntry> globalRoutes = List<_RouteEntry>.from(_globalRoutes); if (routes != null) { for (_RouteEntry entry in List<_RouteEntry>.from(routes)) { if (routes.any(_RouteEntry.isRoutePredicate(entry.route))) _dispatch(event, entry); } } for (_RouteEntry entry in globalRoutes) { if (_globalRoutes.any(_RouteEntry.isRoutePredicate(entry.route))) _dispatch(event, entry); } } 複製代碼
route
方法會從_routeMap
中取出該觸摸事件,並執行_dispatch
將該事件分發下去。這裏_routeMap
對應的數據,在上面的startTrackingPointer
方法中已分析過。
void _dispatch(PointerEvent event, _RouteEntry entry) {
try {
event = event.transformed(entry.transform);
entry.route(event);
} catch (exception, stack) {
...省略
}
}
複製代碼
event.transfromed
方法會對當前觸摸事件對象進行座標系轉換。通常來講,非特殊狀況下,這裏轉換前和轉換後是同一個觸摸事件對象。而後調用了_RouteEntry
的route
方法,將該觸摸事件對象傳遞過去。
這裏_RouteEntry
的route
方法就是上面的startTrackingPointer
方法中初始化的,而且它指向的是每個GestureRecognizer
子類的handleEvent
方法。
拿上面的例子來講,就是TapGestureRecognizer
的handleEvent
方法,因爲TapGestureRecognizer
沒有該方法,咱們從它父類PrimaryPointerGestureRecognizer
能夠找到。
@override void handleEvent(PointerEvent event) { assert(state != GestureRecognizerState.ready); if (state == GestureRecognizerState.possible && event.pointer == primaryPointer) { final bool isPreAcceptSlopPastTolerance = !_gestureAccepted && preAcceptSlopTolerance != null && _getGlobalDistance(event) > preAcceptSlopTolerance; final bool isPostAcceptSlopPastTolerance = _gestureAccepted && postAcceptSlopTolerance != null && _getGlobalDistance(event) > postAcceptSlopTolerance; if (event is PointerMoveEvent && (isPreAcceptSlopPastTolerance || isPostAcceptSlopPastTolerance)) { resolve(GestureDisposition.rejected); stopTrackingPointer(primaryPointer); } else { handlePrimaryPointer(event); } } stopTrackingIfPointerNoLongerDown(event); } 複製代碼
這裏最重要的方法是執行了resolve
方法。
void resolve(GestureDisposition disposition) { final List<GestureArenaEntry> localEntries = List<GestureArenaEntry>.from(_entries.values); _entries.clear(); for (GestureArenaEntry entry in localEntries) entry.resolve(disposition); } 複製代碼
這裏的_entries
也是在上面的startTrackingPointer
方法分析過的,因此上述方法會遍歷_entries
的每個GestureArenaEntry
對象(對應着每個GestureRecognizer
對象),並執行它的resolved
方法,而後再調用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); } } } 複製代碼
該方法主要的做用是處理手勢衝突,經過手勢衝突處理後,能成功執行的手勢事件會調用_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); } 複製代碼
而後再執行GestureArenaMember
的acceptGesture
方法。該方法是抽象方法,具體的實現是在其子類中。咱們看一下TapGestureRecognizer
的實現。
@override void acceptGesture(int pointer) { super.acceptGesture(pointer); if (pointer == primaryPointer) { _checkDown(pointer); _wonArenaForPrimaryPointer = true; _checkUp(); } } 複製代碼
這裏_checkDown
方法主要處理按下事件,_checkUp
主要處理擡起事件。這也說明了TapGestureRecognizer
手勢族只處理手勢的按下與擡起。其餘事件由其餘手勢族進行處理。
_checkDown
與_checkUp
方法後面還會調用諸多方法,最終會調用onTapDown
和onTapUp
方法,這裏的方法鏈路就再也不分析了,有興趣的同窗能夠去看看源碼。
本文以TapGestureRecognizer
做爲例子,分析了GestureDector
組件的觸摸事件的原理。GestureDector
組件的底層是經過Listener
實現的,而且與Listener
同樣也須要對觸摸事件進行命中測試。GestureDector
組件的各個屬性方法在獲得響應以前,會經過WidgetsFlutterBinding
作事件分發,並經過手勢衝突競技場作手勢衝突管理,最終經過的手勢事件纔會分發到各個GestureRecognizer
對象的handleEvent
方法進行處理,結果纔會是各個GestureRecognizer
對象的屬性方法獲得響應。