Flutter的原始事件是由window中 PointerDataPacketCallback(PointerDataPacket packet) 回调得到的,这个回调再GestureBinding初始化中就设置了window.onPointerDataPacket = _handlePointerDataPacket,咱们看一下_handlePointerDataPacket的代码ide
void _handlePointerDataPacket(ui.PointerDataPacket packet) {
//_pendingPointerEvents是一个PointerEvent的队列,这段代码的意思是将PointerDataPacket转换成PointerEvent而后存在队列中
_pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
if (!locked)
_flushPointerEventQueue();
}
//对队列中的PointerEvent进行出队并依次处理
void _flushPointerEventQueue() {
assert(!locked);
while (_pendingPointerEvents.isNotEmpty)
_handlePointerEvent(_pendingPointerEvents.removeFirst());//_handlePointerEvent对每个PointerEvent进行处理
}
复制代码
_handlePointerEvent方法才是对每一个PointerEvent进行处理的地方post
void _handlePointerEvent(PointerEvent event) {
HitTestResult hitTestResult;
if (event is PointerDownEvent) {
assert(!_hitTests.containsKey(event.pointer));
hitTestResult = HitTestResult();
hitTest(hitTestResult, event.position);//hitTest方法,来肯定命中测试的结果
_hitTests[event.pointer] = hitTestResult;//event.pointer是每次连续的PointEvent的惟一id,以id为key将hitTestResult存到_hitTests中
} else if (event is PointerUpEvent || event is PointerCancelEvent) {
hitTestResult = _hitTests.remove(event.pointer);//事件结束标记,将hitTestResult从_hitTests取出并移除
} else if (event.down) {
hitTestResult = _hitTests[event.pointer];//move事件直接重用down事件的hitTestResult,避免每次都进行命中测试
}
if (hitTestResult != null ||
event is PointerHoverEvent ||
event is PointerAddedEvent ||
event is PointerRemovedEvent) {
dispatchEvent(event, hitTestResult);//分发Event
}
}
复制代码
_handlePointerEvent中比较重要的两点:测试
上面的过程就是将原始事件转换成咱们须要的PointEvent,而后再肯定命中测试结果,最后再进行分发事件。ui
HitTestResult中有一个List _path的字段,由多个HitTestEntry来组成的path(事件进行冒泡的路径,为何是经过冒泡后面会有解释),HitTestEntry是每个命中的入口,它只有一个HitTestTarget target字段,而HitTestTarget又是由RenderObject来实现的,因此HitTestResult其实就是一系列经过命中测试的RenderObject的集合。咱们来看看是如何来肯定命中测试的结果的this
void hitTest(HitTestResult result, Offset position) {
assert(renderView != null);
renderView.hitTest(result, position: position);
super.hitTest(result, position);//这里是调用的GestureBinding中的hitTest方法,将WidgetsFlutterBinding加入到最后面
}
复制代码
调用renderView的hitTest方法,继续跟进spa
bool hitTest(HitTestResult result, { Offset position }) {
if (child != null)
child.hitTest(result, position: position);
result.add(HitTestEntry(this));
return true;
}
复制代码
能够看到renderView并无直接实现HitTestable中的hitTest方法,renderView的hitTest方法中的{ Offset position }是一个可选参数,而且带一个bool类型的返回值,renderView的hitTest方法显示对child进行命中测试,让后再将本身添加到命中测试结果。debug
RenderObject中并无发现hitTest方法,可是再其子类RenderBox中发现了名为hitTest的方法,也没有直接实现HitTestable中的hitTest方法,{ Offset position }也是一个可选参数,也有一个bool类型的返回值code
bool hitTest(HitTestResult result, { @required Offset position }) {
if (_size.contains(position)) {//肯定hit的位置再本身的size范围里面
if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
//先对children进行hitTest,而后再对本身进行hitTest,有一项返回true才能将本身添加到HitTestResult里面
result.add(BoxHitTestEntry(this, position));
return true;
}
}
return false;
}
复制代码
咱们看一个比较简单的例子,RenderPadding中是怎样对children进行命中测试的,RenderPadding的hitTestChildren实如今RenderShiftedBox中,hitTestSelf的实如今RenderBox中router
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
if (child != null) {
final BoxParentData childParentData = child.parentData;
return child.hitTest(result, position: position - childParentData.offset);//将点击点减去偏移应用到child的命中测试
}
return false;
}
@protected
bool hitTestSelf(Offset position) => false;//本身进行命中测试
复制代码
RenderPadding的命中测试结果就是若是child命中测试成功,则本身也会被添加的命中测试结果中,不然就不对本身进行命中测试对象
接下来就是对Event的分发了,咱们直接看GestureBinding中的dispatchEvent方法
@override // from HitTestDispatcher
void dispatchEvent(PointerEvent event, HitTestResult hitTestResult) {
assert(!locked);
//没有命中测试信息意味着PointerEvent是Hover,Added,Removed其中一种
if (hitTestResult == null) {
assert(event is PointerHoverEvent || event is PointerAddedEvent || event is PointerRemovedEvent);
try {
//将事件分发到注册了这次事件的路由,通常是由GestureRecognizer中void addPointer(PointerDownEvent event)方法进行注册
pointerRouter.route(event);
} catch (exception, stack) {}
return;//进行路由分发直接返回
}
//对命中测试的结果进行遍历,应为是先对child进行命中测试,因此事件的序列是冒泡向上传递的
for (HitTestEntry entry in hitTestResult.path) {
try {
//调用target的handleEvent方法处理事件
entry.target.handleEvent(event, entry);
} catch (exception, stack) {}
}
}
复制代码
当命中测试结果为空时进行路由分发,当命中测试结果不为空时,就进行命中结果分发,handleEvent方法的实现咱们来看一个比较典型的,RenderPointerListener中的handleEvent,RenderPointerListener时Listener(能够监听原始PointEvent的Widget)对应的RenderObject对象
@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
assert(debugHandleEvent(event, entry));
// onPointerEnter, onPointerHover, and onPointerExit events都在MouseTracker里面处理
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);
}
复制代码
RenderPointerListener直接把事件给到对应的回调,大多数RenderObject都没有实现handleEvent方法。
Flutter的官方文档推荐咱们使用GestureDetector来检测用户手势输入,GestureDetector帮咱们区别了各类类型的手势,咱们只须要设置须要监听的手势回调就能够了,使用很是方便。 从咱们上面的分析能够看到,事件的产生与分发,咱们来看一下GestureDetector是如何监听事件并进行区别手势的呢?
@override
Widget build(BuildContext context) {
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
...省略若干代码
return RawGestureDetector(
gestures: gestures,
behavior: behavior,
excludeFromSemantics: excludeFromSemantics,
child: child,
);
}
复制代码
能够看到GestureDetector是经过RawGestureDetector来实现的,咱们再看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;
}
复制代码
而RawGestureDetector又是经过Listener来实现的,上面咱们知道Listener是监听初始事件PointerEvent的,那他是如何被区别为各类各样的手势的呢?
看一下RawGestureDetector中的_handlePointerDown方法
void _handlePointerDown(PointerDownEvent event) {
assert(_recognizers != null);
for (GestureRecognizer recognizer in _recognizers.values)
recognizer.addPointer(event);
}
复制代码
在PointerDownEvent的时候,将全部recognizer进行addPointer(event),继续跟进addPointer方法,在GestureRecognizer中是空实现,咱们先看一个简单的实现TapGestureRecognizer
@override
void addPointer(PointerDownEvent event) {
startTrackingPointer(event.pointer);//开始追踪id为pointer的事件序列
if (state == GestureRecognizerState.ready) {
state = GestureRecognizerState.possible;
primaryPointer = event.pointer;
initialPosition = event.position;
if (deadline != null)
_timer = Timer(deadline, didExceedDeadline);
}
}
复制代码
addPointer中最主要的方法就是startTrackingPointer方法,这个方法是OneSequenceGestureRecognizer中的,可让OneSequenceGestureRecognizer去追踪这个事件序列,具体分析在下面手势竞技中再讲,Recognizer追踪了这个事件序列后,这个事件的后续事件都会被这个Recognizer处理,会触发handleEvent方法,经过一系列判断会走到handlePrimaryPointer方法,而后再PointerUpEvent时触发 相关回调
@override
void handlePrimaryPointer(PointerEvent event) {
if (event is PointerUpEvent) {
_finalPosition = event.position;
_checkUp();
} else if (event is PointerCancelEvent) {
_reset();
}
}
void _checkUp() {
if (_wonArenaForPrimaryPointer && _finalPosition != null) {
resolve(GestureDisposition.accepted);
if (!_wonArenaForPrimaryPointer || _finalPosition == null) {
return;
}
if (onTapUp != null)
invokeCallback<void>('onTapUp', () { onTapUp(TapUpDetails(globalPosition: _finalPosition)); });//触发onTapUp回调
if (onTap != null)
invokeCallback<void>('onTap', onTap);//触发onTap回调
_reset();
}
}
复制代码
Recognizer会对事件进行分析,而后会去区别不一样的状况去触发不一样的回调。
若是一个事件序列被多个Recognizer追踪,好比须要监听用户点击与滑动,那么怎么去区别用户究竟是点击仍是滑动呢?
咱们先看一下Recognizer是如何追踪事件序列的,先看startTrackingPointer方法
@protected
void startTrackingPointer(int pointer) {
GestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent);
_trackedPointers.add(pointer);
assert(!_entries.containsValue(pointer));
_entries[pointer] = _addPointerToArena(pointer);
}
复制代码
继续看一下_addPointerToArena方法
GestureArenaEntry _addPointerToArena(int pointer) {
if (_team != null)
return _team.add(pointer, this);
return GestureBinding.instance.gestureArena.add(pointer, this);
}
复制代码
能够看到GestureBinding(实例是WidgetsFlutterBinding)中的gestureArena字段(它是GestureArenaManager)将这个Recognizer添加进去。看看其add方法
final Map<int, _GestureArena> _arenas = <int, _GestureArena>{};
GestureArenaEntry add(int pointer, GestureArenaMember member) {
//_GestureArena中是一个GestureArenaMember的list,其实就是一个手势id,对应多个GestureArenaMember
final _GestureArena state = _arenas.putIfAbsent(pointer, () {
return _GestureArena();
});
state.add(member);//将GestureArenaMember添加到_GestureArena中
return GestureArenaEntry._(this, pointer, member);//再返回一个GestureArenaEntry对象给Recognizer中entries持有
}
复制代码
从以上的分析能够总结一下startTrackingPointer主要作的事情:
上面只是添加到路由以及竞技场,可是咱们还不知道是事件是怎样被发送到指定的路由,以及多个手势识别器是如何竞争处理手势事件的
咱们知道事件的传递是经过冒泡来进行传递的,HitTestResult的最上层是WidgetsFlutterBinding,最后处理事件的应该在GestureBinding中,咱们看一下GestureBinding的handleEvent方法
@override // from HitTestTarget
void handleEvent(PointerEvent event, HitTestEntry entry) {
pointerRouter.route(event);//调用PointerRouter的route方法将事件进行路由
...省略
}
复制代码
GestureBinding的dispatchEvent方法在HitTestResult为null的时候才路由,主要也就是Hover,Added,Removed这三种事件进行路由,而处理这三个的是一个global的MouseTracker。而此处路由会处理全部注册到routerMap中的Recognizer(实际上只是其handleEvent方法)。
仍是看GestureBinding的handleEvent方法
@override // from HitTestTarget
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路由以后,在PointerDownEvent时候就调用GestureArenaManager的close方法(防止其余Recognizer加入到手势竞技中),因此在就是为何addPoint注册路由的方法须要PointerDownEvent做为参数了,一旦在down的时候不注册,那么这个事件就与你的Recognizer无关了。看一下close方法
void close(int pointer) {
final _GestureArena state = _arenas[pointer];
if (state == null)
return;
state.isOpen = false;
_tryToResolveArena(pointer, state);
}
复制代码
先将isOpen变为false(在add的时候首先就会判断isOpen),而后调用_tryToResolveArena方法,继续跟进
void _tryToResolveArena(int pointer, _GestureArena state) {
assert(_arenas[pointer] == state);
assert(!state.isOpen);
if (state.members.length == 1) {//只有1个Recognizer,直接添加一个_resolveByDefault方法调用的task
scheduleMicrotask(() => _resolveByDefault(pointer, state));
} else if (state.members.isEmpty) {//没有Recognizer,直接移除该pointer对应的_GestureArena
_arenas.remove(pointer);
} else if (state.eagerWinner != null) {//渴望的胜利者,在_GestureArena关闭的时候若是不为空会直接做为胜利者
_resolveInFavorOf(pointer, state, state.eagerWinner);//肯定胜利者
}
}
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);//将不是胜利者的GestureArenaMember所有调用拒绝手势
}
member.acceptGesture(pointer);//调用胜利这的接受手势
}
复制代码
可是咱们在没有eagerWinner的时候是怎样来竞技的呢?咱们用两个具体的手势识别器点击(TapGestureRecognizer)、滑动(PanGestureRecognizer)来分析
首先咱们滑动一下
通过上面的分析,在down的时候是解析不出胜利者的,后续move事件会路由给TapGestureRecognizer,PanGestureRecognizer,咱们须要看一下TapGestureRecognizer的handleEvent方法
@override
void handleEvent(PointerEvent event) {
assert(state != GestureRecognizerState.ready);
if (event.pointer == primaryPointer) {
//接受手势前滑动距离是否溢出容忍值
final bool isPreAcceptSlopPastTolerance =
state == GestureRecognizerState.possible &&
preAcceptSlopTolerance != null &&
_getDistance(event) > preAcceptSlopTolerance;
//接受手势后滑动距离是否溢出容忍值
final bool isPostAcceptSlopPastTolerance =
state == GestureRecognizerState.accepted &&
postAcceptSlopTolerance != null &&
_getDistance(event) > postAcceptSlopTolerance;
//move事件下,超出容忍值
if (event is PointerMoveEvent && (isPreAcceptSlopPastTolerance || isPostAcceptSlopPastTolerance)) {
resolve(GestureDisposition.rejected);//拒绝后续事件
stopTrackingPointer(primaryPointer);//结束追踪事件
} else {
handlePrimaryPointer(event);
}
}
stopTrackingIfPointerNoLongerDown(event);
}
复制代码
因此但凡咱们滑动的距离超出了容忍值(这个值是根据经验事件来肯定的值),都会拒绝事件结束追踪。因此事件就会落到PanGestureRecognizer身上。
咱们再点一下
咱们须要看一下PanGestureRecognizer的handleEvent方法
@override
void handleEvent(PointerEvent event) {
assert(_state != _DragState.ready);
if (!event.synthesized
&& (event is PointerDownEvent || event is PointerMoveEvent)) {
final VelocityTracker tracker = _velocityTrackers[event.pointer];
assert(tracker != null);
tracker.addPosition(event.timeStamp, event.position);
}
if (event is PointerMoveEvent) {
final Offset delta = event.delta;
if (_state == _DragState.accepted) {
if (onUpdate != null) {
invokeCallback<void>('onUpdate', () => onUpdate(DragUpdateDetails(
sourceTimeStamp: event.timeStamp,
delta: _getDeltaForDetails(delta),
primaryDelta: _getPrimaryValueFromOffset(delta),
globalPosition: event.position,
)));
}
} else {//move事件,没有接受事件时
_pendingDragOffset += delta;
_lastPendingEventTimestamp = event.timeStamp;
//判断是否由足够的滑动距离来接受,也就是说滑动距离超过必定距离会主动接受
if (_hasSufficientPendingDragDeltaToAccept)
resolve(GestureDisposition.accepted);
}
}
stopTrackingIfPointerNoLongerDown(event);//up事件的时候会中止追踪
}
复制代码
因为咱们是点一下,那么距离不够是不会去主动接受的,等通过一系列move事件结束后PanGestureRecognizer仍是没有得到事件,最后再up的时候就中止追踪事件了,那么事件就会落到TapGestureRecognizer身上。
经过上面两种状况的分析,不一样的Recognizer都有本身的逻辑去接受、拒绝、中止追踪事件。
经过resolve方法传入一个GestureDisposition可让Recognizer来处置事件,咱们跟进resolve方法看一下具体操做,
void resolve(GestureDisposition disposition) {
final List<GestureArenaEntry> localEntries = List<GestureArenaEntry>.from(_entries.values);
_entries.clear();
for (GestureArenaEntry entry in localEntries)
entry.resolve(disposition);
}
void resolve(GestureDisposition disposition) {
_arena._resolve(_pointer, _member, disposition);
}
void _resolve(int pointer, GestureArenaMember member, GestureDisposition disposition) {
final _GestureArena state = _arenas[pointer];
if (state == null)
return;
assert(state.members.contains(member));
if (disposition == GestureDisposition.rejected) {
state.members.remove(member);//移除一个Recognizer
member.rejectGesture(pointer);//调用其rejectGesture方法
if (!state.isOpen)
_tryToResolveArena(pointer, state);//尝试肯定胜利者
} else {
if (state.isOpen) {
state.eagerWinner ??= member;
} else {
_resolveInFavorOf(pointer, state, member);//肯定胜利者
}
}
}
复制代码
能够看到若是是接受,就直接确认胜利者,若是是拒绝,就将其踢出并尝试确认胜利者。再看一下stopTrackingPointer的具体操做
void stopTrackingPointer(int pointer) {
if (_trackedPointers.contains(pointer)) {
GestureBinding.instance.pointerRouter.removeRoute(pointer, handleEvent);//移除路由,不处理余下事件
_trackedPointers.remove(pointer);//从_trackedPointers中移除
if (_trackedPointers.isEmpty)
didStopTrackingLastPointer(pointer);
}
}
复制代码
主要就是移除路由,didStopTrackingLastPointer是在没有追踪的PointEvent时,作一些收尾工做,具体都有不一样实现。