从源码分析Flutter中的事件分发
事件的传递与竞技响应
通过如下这段代码,一起来探究一下flutter中的事件分发吧
GestureDetector(
onTap: () {
print("tap_red");
},
onTapDown: (detail){
print("tap_red_down");
},
onTapUp: (detail){
print("tap_red_up");
},
child: Container(
color: Colors.red,
width: 200,
height: 200,
),
)
首先,我们知道,用户的触摸行为,一定是在原生设备进行(如上图所示),我们的事件分发肯定是从Java层传递到了C++,最终传递至Dart这个过程。在Dart部分,我们注意到经过
zone.runUnaryGuarded
方法之后会调用到window.onPointDataPacket
方法处,查看GestureBinding初始化的过程得知这个方法会执行_handlePointerDataPacket
.
通过断点我们可以也可以看到,执行一次点击事件会执行GestureBinding中的
_handlePointerDataPacket
->_flushPointerEventQueue
->handlePointerEvent
->_handlePointerEventImmediately
下面我们具体来看一下_handlePointerEventImmediately中的具体内容
void _handlePointerEventImmediately(PointerEvent event) {
HitTestResult? hitTestResult;
if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent) {
assert(!_hitTests.containsKey(event.pointer));
hitTestResult = HitTestResult();
hitTest(hitTestResult, event.position);
if (event is PointerDownEvent) {
_hitTests[event.pointer] = hitTestResult;
}
assert(() {
if (debugPrintHitTestResults)
debugPrint('$event: $hitTestResult');
return true;
}());
} else if (event is PointerUpEvent || event is PointerCancelEvent) {
hitTestResult = _hitTests.remove(event.pointer);
} else if (event.down) {
// Because events that occur with the pointer down (like
// [PointerMoveEvent]s) should be dispatched to the same place that their
// initial PointerDownEvent was, we want to re-use the path we found when
// the pointer went down, rather than do hit detection each time we get
// such an event.
hitTestResult = _hitTests[event.pointer];
}
assert(() {
if (debugPrintMouseHoverEvents && event is PointerHoverEvent)
debugPrint('$event');
return true;
}());
if (hitTestResult != null ||
event is PointerAddedEvent ||
event is PointerRemovedEvent) {
assert(event.position != null);
dispatchEvent(event, hitTestResult);
}
}
我们知道,一次点击响应可以看做是Down+Up事件,这里首先走的应该是PointerDownEvent,在这里初始化了hitTestResult,然后对执行hitTest方法,来初始化hitTestResult内的target
- HitTestResult HitTestResult内有一个List< HitTestEntry > _path, HitTestEntry中有一个HitTestTarget target,这个target对象拥有handleEvent的能力.
abstract class HitTestTarget {
// This class is intended to be used as an interface, and should not be
// extended directly; this constructor prevents instantiation and extension.
HitTestTarget._();
/// Override this method to receive events.
void handleEvent(PointerEvent event, HitTestEntry entry);
}
hitTest方法
查看源码,我们看到这个hitTest方法在源码中有这样的关系,按照执行的先后顺序我们来看一下具体的代码
RendererBinding.hitTest
@override
void hitTest(HitTestResult result, Offset position) {
assert(renderView != null);
assert(result != null);
assert(position != null);
renderView.hitTest(result, position: position);
super.hitTest(result, position);
}
主要是调用了renderView.hitTest和super.hitTest(GestureBinding.hitTest)
RenderView.hitTest
(RenderView是绘制树的根节点,是所有Widget的祖先)
bool hitTest(HitTestResult result, { required Offset position }) {
if (child != null)
child!.hitTest(BoxHitTestResult.wrap(result), position: position);
result.add(HitTestEntry(this));
return true;
}
先是执行,child!.hitTest(RenderBox.hitTest),之后将自己加入result
RenderBox.hitTest
bool hitTest(BoxHitTestResult result, { required Offset position }) {
...
if (_size!.contains(position)) {
if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
result.add(BoxHitTestEntry(this, position));
return true;
}
}
return false;
}
先是判断触摸点是否属于widget的size范围,在范围内执行hitTestChildren
和hitTestSelf
是两个抽象方法(因为Widget可能有一个child或者多个child),查看具体实现其实逻辑和RenderBox.hitTest一样,也是先判断自己是否在这次点击的postion范围内,然后递归调用子Widget的hitTest。观察这个方法结构,我们知道,如果一个Widget越深,则越先被添加进HitTestResult中。这样整个流程执行下来,HitTestResult就得到了这次点击事件坐标上所有能响应的控件集合。RendererBinding.hitTest执行super.hitTest(result, position);,即GestureBinding.hitTest
GestureBinding.hitTest
@override // from HitTestable
void hitTest(HitTestResult result, Offset position) {
result.add(HitTestEntry(this));
}
最后把自己添加到HitTestResult的结尾;下一步关键就是对事件进行分发了
dispatchEvent
按照上面的逻辑,首先也是先走RendererBinding.dispathchEvent,event.kind是PointerDeviceKind.touch,就直接调用了GestureBinding.dispatchEvent,我们直接来看
GestureBinding.dispatchEvent
@override // from HitTestDispatcher
@pragma('vm:notify-debugger-on-exception')
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
...
for (final HitTestEntry entry in hitTestResult.path) {
entry.target.handleEvent(event.transformed(entry.transform), entry);
}
}
其中最重要的一句是循环调用hitTestResult集合中每一个对象的
handleEvent(event.transformed(entry.transform), entry)
方法,大部分时候只有RenderPointerListener会处理这个方法,RenderPointerListener被嵌套在RawGestureDetector中。
RenderPointerListener.handleEvent
@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
assert(debugHandleEvent(event, entry));
if (event is PointerDownEvent)
return onPointerDown?.call(event);
if (event is PointerMoveEvent)
return onPointerMove?.call(event);
if (event is PointerUpEvent)
return onPointerUp?.call(event);
if (event is PointerHoverEvent)
return onPointerHover?.call(event);
if (event is PointerCancelEvent)
return onPointerCancel?.call(event);
if (event is PointerSignalEvent)
return onPointerSignal?.call(event);
}
这个方法就是将event按照类型,触发RawGestureDetector中的不同处理。Flutter中几乎所有的手势处理都是这个类的包装(InkWell的结构,在最里层返回的是一个RenderPointerListener
),handleEvent
会根据不同的事件类型,回调到RawGestureDetector的相关手势处理中。
对照我们例子的执行过程:
执行recognizer.addPointer内部方法的是BaseTapGestureRecognizer类,最终会将GestureRecognizer加入到竞技场内参与竞技.HitTestResult中的每一个RawGestureDetector会将自己添加到GestureArenaManager中.
不要忘了GestureBinding被添加在HitTestResult的最后,GestureBinding也要执行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);
} else if (event is PointerSignalEvent) {
pointerSignalResolver.resolve(event);
}
}
小结
竞技响应
一个点击事件肯定是从down事件开始,GestureBinding.handleEvent方法时必定会先走
gestureArena.close(event.pointer);
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);
}
在本例中,竞技场只有一个竞争者,会直接执行_resolveByDefault方法
void _tryToResolveArena(int pointer, _GestureArena state) {
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!);
}
}
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);
}
可以看出,最终执行的是
BaseTapGestureRecognizer.acceptGesture
@override
void acceptGesture(int pointer) {
super.acceptGesture(pointer);
if (pointer == primaryPointer) {
_checkDown();
_wonArenaForPrimaryPointer = true;
_checkUp();
}
}
@override
void rejectGesture(int pointer) {
super.rejectGesture(pointer);
if (pointer == primaryPointer) {
// Another gesture won the arena.
assert(state != GestureRecognizerState.possible);
if (_sentTapDown)
_checkCancel(null, 'forced');
_reset();
}
}
在本例中的只有一个竞技者,响应点击为BaseTapGestureRecognizer.acceptGesture(int pointer),会先进行down事件的消费;_checkUp()此时的_up为null,这个_up的会在PrimaryPointerGestureRecognizer.handleEvent
的时候调用handlePrimaryPointer
赋值,所以即使当竞技场,只有一个竞技者的时候在Down事件也不会被识别为一个完整的点击动作。
void _checkDown() {
if (_sentTapDown) {
return;
}
handleTapDown(down: _down!);
_sentTapDown = true;
}
void _checkUp() {
if (!_wonArenaForPrimaryPointer || _up == null) {
return;
}
assert(_up!.pointer == _down!.pointer);
handleTapUp(down: _down!, up: _up!);
_reset();
}
以上,是区域内只有一个竞争者的响应流程,在down事件中就已经决出胜负;up的时候,竞技场已经打扫干净了,不会做任何操作.
如果有多个响应者呢?
GestureDetector(
onTap: () {
print("tap_red");
},
onTapDown: (detail){
print("tap_red_down");
},
onTapUp: (detail){
print("tap_red_up");
},
child: Container(
color: Colors.red,
width: 200,
height: 200,
child: Center(
child: GestureDetector(
onTap: () {
print("tap_green");
},
onTapDown: (detail){
print("tap_green_down");
},
onTapUp: (detail){
print("tap_green_up");
},
child: Container(
color: Colors.green,
width: 100,
height: 100,
),
),
),
),
)
当我点击内部绿色区域的时候,在竞技场内就不只一个响应者了,在down事件中,竞技场就无法直接决出胜负;需要在up事件中sweep竞技场的时候,选择第一个(嵌套结构最内层)member作为响应者acceptGesture,其他memeber.rejectGesture
GestureArenaManager.sweep
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);
}
}
滑动事件
根据上述流程,我们分析一下滑动事件的传递
ListView.builder(itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
print("tap_red");
},
onTapDown: (detail) {
print("tap_red_down");
},
onTapUp: (detail) {
print("tap_red_up");
},
child: Container(
height: 100,
child: Text("$index"),
));
})
首先,滑动过程是down+move事件
down的执行流程如下
此时在down的流程中,竞技场内不只有一个竞技者,无法直接决出胜负;
第一次move流程
DragGestureRecognizer.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.localPosition);
}
if (event is PointerMoveEvent) {
if (event.buttons != _initialButtons) {
_giveUpPointer(event.pointer);
return;
}
if (_state == _DragState.accepted) {
_checkUpdate(
sourceTimeStamp: event.timeStamp,
delta: _getDeltaForDetails(event.localDelta),
primaryDelta: _getPrimaryValueFromOffset(event.localDelta),
globalPosition: event.position,
localPosition: event.localPosition,
);
} else {
_pendingDragOffset += OffsetPair(local: event.localDelta, global: event.delta);
_lastPendingEventTimestamp = event.timeStamp;
_lastTransform = event.transform;
final Offset movedLocally = _getDeltaForDetails(event.localDelta);
final Matrix4? localToGlobalTransform = event.transform == null ? null : Matrix4.tryInvert(event.transform!);
_globalDistanceMoved += PointerEvent.transformDeltaViaPositions(
transform: localToGlobalTransform,
untransformedDelta: movedLocally,
untransformedEndPosition: event.localPosition,
).distance * (_getPrimaryValueFromOffset(movedLocally) ?? 1).sign;
if (_hasSufficientGlobalDistanceToAccept(event.kind))
resolve(GestureDisposition.accepted);
}
}
if (event is PointerUpEvent || event is PointerCancelEvent) {
_giveUpPointer(event.pointer);
}
}
在第一次Move事件阶段,_state == _DragState.accepted
肯定为false,走下面的流程,最下方有一个判断_hasSufficientGlobalDistanceToAccept,这个判断里对比了手指在屏幕上的滑动距离,如果大于18逻辑像素则认为是一次滑动,调用resolve(GestureDisposition.accepted)
,这个方法,最终会调用到GestureArenaManager._resolve中,决出胜利者,执行acceptGesture
VerticalDragGestureRecognizer._hasSufficientGlobalDistanceToAccept
@override
bool _hasSufficientGlobalDistanceToAccept(PointerDeviceKind pointerDeviceKind) {
return _globalDistanceMoved.abs() > computeHitSlop(pointerDeviceKind);//> 18
}
GestureArenaManager._resolve
/// Reject or accept a gesture recognizer.
///
/// This is called by calling [GestureArenaEntry.resolve] on the object returned from [add].
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);
}
}
}
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 (final GestureArenaMember rejectedMember in state.members) {
if (rejectedMember != member)
rejectedMember.rejectGesture(pointer);
}
member.acceptGesture(pointer);
}
DragGestureRecognizer.acceptGesture
@override
void acceptGesture(int pointer) {
assert(!_acceptedActivePointers.contains(pointer));
_acceptedActivePointers.add(pointer);
if (_state != _DragState.accepted) {
_state = _DragState.accepted;
final OffsetPair delta = _pendingDragOffset;
final Duration timestamp = _lastPendingEventTimestamp!;
final Matrix4? transform = _lastTransform;
final Offset localUpdateDelta;
switch (dragStartBehavior) {
case DragStartBehavior.start:
_initialPosition = _initialPosition + delta;
localUpdateDelta = Offset.zero;
break;
case DragStartBehavior.down:
localUpdateDelta = _getDeltaForDetails(delta.local);
break;
}
_pendingDragOffset = OffsetPair.zero;
_lastPendingEventTimestamp = null;
_lastTransform = null;
_checkStart(timestamp, pointer);
if (localUpdateDelta != Offset.zero && onUpdate != null) {
final Matrix4? localToGlobal = transform != null ? Matrix4.tryInvert(transform) : null;
final Offset correctedLocalPosition = _initialPosition.local + localUpdateDelta;
final Offset globalUpdateDelta = PointerEvent.transformDeltaViaPositions(
untransformedEndPosition: correctedLocalPosition,
untransformedDelta: localUpdateDelta,
transform: localToGlobal,
);
final OffsetPair updateDelta = OffsetPair(local: localUpdateDelta, global: globalUpdateDelta);
final OffsetPair correctedPosition = _initialPosition + updateDelta; // Only adds delta for down behaviour
_checkUpdate(
sourceTimeStamp: timestamp,
delta: localUpdateDelta,
primaryDelta: _getPrimaryValueFromOffset(localUpdateDelta),
globalPosition: correctedPosition.global,
localPosition: correctedPosition.local,
);
}
// This acceptGesture might have been called only for one pointer, instead
// of all pointers. Resolve all pointers to `accepted`. This won't cause
// infinite recursion because an accepted pointer won't be accepted again.
resolve(GestureDisposition.accepted);
}
}
其中调用 _checkStart(timestamp)
DragGestureRecognizer._checkStart
void _checkStart(Duration timestamp, int pointer) {
assert(_initialButtons == kPrimaryButton);
if (onStart != null) {
final DragStartDetails details = DragStartDetails(
sourceTimeStamp: timestamp,
globalPosition: _initialPosition.global,
localPosition: _initialPosition.local,
kind: getKindForPointer(pointer),
);
invokeCallback<void>('onStart', () => onStart!(details));
}
}
这里先封装了一个DragStartDetails
对象,之后调用的onStart(details)
是外界传入的一个变量。我们返回看Scrollable的 setCanDrag(bool canDrag)
方法
Scrollable.setCanDrag
@override
@protected
void setCanDrag(bool canDrag) {
if (canDrag == _lastCanDrag && (!canDrag || widget.axis == _lastAxisDirection))
return;
if (!canDrag) {
_gestureRecognizers = const <Type, GestureRecognizerFactory>{};
// Cancel the active hold/drag (if any) because the gesture recognizers
// will soon be disposed by our RawGestureDetector, and we won't be
// receiving pointer up events to cancel the hold/drag.
_handleDragCancel();
} else {
switch (widget.axis) {
case Axis.vertical:
_gestureRecognizers = <Type, GestureRecognizerFactory>{
VerticalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
() => VerticalDragGestureRecognizer(supportedDevices: _configuration.dragDevices),
(VerticalDragGestureRecognizer instance) {
instance
..onDown = _handleDragDown
..onStart = _handleDragStart
..onUpdate = _handleDragUpdate
..onEnd = _handleDragEnd
..onCancel = _handleDragCancel
..minFlingDistance = _physics?.minFlingDistance
..minFlingVelocity = _physics?.minFlingVelocity
..maxFlingVelocity = _physics?.maxFlingVelocity
..velocityTrackerBuilder = _configuration.velocityTrackerBuilder(context)
..dragStartBehavior = widget.dragStartBehavior;
},
),
};
break;
case Axis.horizontal:
_gestureRecognizers = <Type, GestureRecognizerFactory>{
HorizontalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
() => HorizontalDragGestureRecognizer(supportedDevices: _configuration.dragDevices),
(HorizontalDragGestureRecognizer instance) {
instance
..onDown = _handleDragDown
..onStart = _handleDragStart
..onUpdate = _handleDragUpdate
..onEnd = _handleDragEnd
..onCancel = _handleDragCancel
..minFlingDistance = _physics?.minFlingDistance
..minFlingVelocity = _physics?.minFlingVelocity
..maxFlingVelocity = _physics?.maxFlingVelocity
..velocityTrackerBuilder = _configuration.velocityTrackerBuilder(context)
..dragStartBehavior = widget.dragStartBehavior;
},
),
};
break;
}
}
_lastCanDrag = canDrag;
_lastAxisDirection = widget.axis;
if (_gestureDetectorKey.currentState != null)
_gestureDetectorKey.currentState!.replaceGestureRecognizers(_gestureRecognizers);
}
ScrollableState._handleDragStart
void _handleDragStart(DragStartDetails details) {
// It's possible for _hold to become null between _handleDragDown and
// _handleDragStart, for example if some user code calls jumpTo or otherwise
// triggers a new activity to begin.
assert(_drag == null);
_drag = position.drag(details, _disposeDrag);
assert(_drag != null);
assert(_hold == null);
}
这里的position就是ScrollPositionWithSingleContext
ScrollPositionWithSingleContext.drag
@override
Drag drag(DragStartDetails details, VoidCallback dragCancelCallback) {
final ScrollDragController drag = ScrollDragController(
delegate: this,
details: details,
onDragCanceled: dragCancelCallback,
carriedVelocity: physics.carriedMomentum(_heldPreviousVelocity),
motionStartDistanceThreshold: physics.dragStartDistanceMotionThreshold,
);
beginActivity(DragScrollActivity(this, drag));
assert(_currentDrag == null);
_currentDrag = drag;
return drag;
}
beginActivity里面主要就是发起滚动的通知UserScrollNotification
后续move
ScrollableState._handleDragUpdate
void _handleDragUpdate(DragUpdateDetails details) {
// _drag might be null if the drag activity ended and called _disposeDrag.
assert(_hold == null || _drag == null);
_drag?.update(details);
}
这里的_drag是ScrollDragController
ScrollDragController.update
@override
void update(DragUpdateDetails details) {
assert(details.primaryDelta != null);
_lastDetails = details;
double offset = details.primaryDelta!;
if (offset != 0.0) {
_lastNonStationaryTimestamp = details.sourceTimeStamp;
}
// By default, iOS platforms carries momentum and has a start threshold
// (configured in [BouncingScrollPhysics]). The 2 operations below are
// no-ops on Android.
_maybeLoseMomentum(offset, details.sourceTimeStamp);
offset = _adjustForScrollStartThreshold(offset, details.sourceTimeStamp);
if (offset == 0.0) {
return;
}
if (_reversed) // e.g. an AxisDirection.up scrollable
offset = -offset;
delegate.applyUserOffset(offset);
}
这里会调用 delegate.applyUserOffset(offset)
,这个delegate
是个ScrollActivityDelegate
接口,主要实现类是ScrollPositionWithSingleContext
.
ScrollPositionWithSingleContext.applyUserOffset
@override
void applyUserOffset(double delta) {
updateUserScrollDirection(delta > 0.0 ? ScrollDirection.forward : ScrollDirection.reverse);
setPixels(pixels - physics.applyPhysicsToUserOffset(this, delta));
}
updateUserScrollDirection
会发起一个滑动方向的通知UserScrollNotification
;
setPixels
会发起一个ScrollUpdateNotification
通知并且调用notifyListeners()
,通知Viewport
更新偏移量已经我们自己添加到ScrollController
中的方法
至此,滑动的整个流程,我们也都了解了
总结
本文仅以实例直观的分析了整个流程,作为自己的学习记录,对您有帮助的话,欢迎点点小心心~
转载自:https://juejin.cn/post/7021442906246348813