引言
上一篇文章 深入理解Flutter的Listener组件
介绍了触控事件的监听原理,让我们对Flutter
中触摸事件的传递过程有了进一步的认识。
今天我们学习一下手势识别组件GestureDetector的原理。GestureDetector
的内部实现使用的是Listener
组件,如果对Listener
还不太熟悉,可以先了解一下Listener
的原理。
源码解析
一、GestureDetector是Listener的封装
GestureDector
是一个无状态组件,它的build
方法如下所示。
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
方法如下所示。
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
组件,这也证明了上面的结论:
GestureDetector
的内部实现使用的是Listener
组件。
二、GestureDetector的实现原理
相比于Listener
,GestureDetector
有自己的属性,如onDoubleTap
、onLongPress
、onHorizontalDragStart
、onVerticalDragStart
等。
其实说到底,这些属性也是由Listener
的onPointerDown
、onPointerMove
、onPointerUp
这三个属性封装而成的。
重新看一下RawGestureDetector
的State
的build
方法。
@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
构成如下图所示。
从之前对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
,并且对于每一种手势族都定义了一种类型。
1、TapGestureRecognizer
手势族里面就包含了onTapDown
、onTapUp
、onTap
、onTapCancel
、onSecondaryTapDown
、onSecondaryTapUp
、onSecondaryTapCancel
事件。
2、DoubleTapGestureRecognizer
手势族里面就包含了onDoubleTap
事件。
3、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);
}
1、第一步通过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
传递过去。
2、第二步调用了_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
对象的属性方法得到响应。
网友评论