上一篇文章 深刻理解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
对象的属性方法获得响应。