当前位置:网站首页>How does the Flutter TapGestureRecognizer work
How does the Flutter TapGestureRecognizer work
2022-08-05 04:52:00 【bawomingtian123】
when using click event,我们使用GestureDetectorHow to nest subcomponents.Then behind the click eventTapGestureRecognizerHow to identify users in“点击”.
从GestureDetector源码build方法中可以看到,在我们给GestureDetector组件添加了onTap方法后,GestureDetector会添加TapGestureRecognizer 手势识别器,The main function of this recognizer is to recognize click events.
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
@override
Widget build(BuildContext context) {
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
if (onTapDown != null ||
onTapUp != null ||
onTap != null ||
onTapCancel != null ||
onSecondaryTap != null ||
onSecondaryTapDown != null ||
onSecondaryTapUp != null ||
onSecondaryTapCancel != null||
onTertiaryTapDown != null ||
onTertiaryTapUp != null ||
onTertiaryTapCancel != null
) {
gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
() => TapGestureRecognizer(debugOwner: this),
(TapGestureRecognizer instance) {
instance
..onTapDown = onTapDown
..onTapUp = onTapUp
..onTap = onTap
..onTapCancel = onTapCancel
..onSecondaryTap = onSecondaryTap
..onSecondaryTapDown = onSecondaryTapDown
..onSecondaryTapUp = onSecondaryTapUp
..onSecondaryTapCancel = onSecondaryTapCancel
..onTertiaryTapDown = onTertiaryTapDown
..onTertiaryTapUp = onTertiaryTapUp
..onTertiaryTapCancel = onTertiaryTapCancel;
},
);
}
In different gesture callbacks according to the settings,Add different gesture recognizers at the bottom,例如点击,长按,和双击.在最后使用
RawGestureDetector Component encapsulation gesture recognizerMap
return RawGestureDetector(
gestures: gestures,
behavior: behavior,
excludeFromSemantics: excludeFromSemantics,
child: child,
);
继续查看RawGestureDetector源码,在其build方法中我们找到Listener
@override
Widget build(BuildContext context) {
Widget result = Listener(
onPointerDown: _handlePointerDown,
behavior: widget.behavior ?? _defaultBehavior,
child: widget.child,
);
if (!widget.excludeFromSemantics) {
result = _GestureSemantics(
behavior: widget.behavior ?? _defaultBehavior,
assignSemantics: _updateSemanticsForRenderObject,
child: result,
);
}
return result;
}
Listenerthere is a keyonPointerDown回调,This method is the component processingdown事件的入口.After the interface receives the touch event reported by the bottom layer,组件会通过_handlePointerDown方法进行处理.
void _handlePointerDown(PointerDownEvent event) {
assert(_recognizers != null);
for (final GestureRecognizer recognizer in _recognizers!.values)
recognizer.addPointer(event);
}
这个方法主要作用是将downEvents added to various gesture recognizers.这里我们以TapGestureRecognizerTake an example to see the specific implementation logic.
/// * [GestureDetector.onTap], which uses this recognizer.
/// * [MultiTapGestureRecognizer]
class TapGestureRecognizer extends BaseTapGestureRecognizer {
......
}
由于TapGestureRecognizer继承BaseTapGestureRecognizer,我们继续向下找
/// * [TapGestureRecognizer], a ready-to-use tap recognizer that recognizes
/// taps of the primary button and taps of the secondary button.
/// * [ModalBarrier], a widget that uses a custom tap recognizer that accepts
/// any buttons.
abstract class BaseTapGestureRecognizer extends PrimaryPointerGestureRecognizer {
......
}
由于BaseTapGestureRecognizer继承PrimaryPointerGestureRecognizer,Let's keep looking down
Here, because there are many inheritance levels,我就不一一列出来了,Interested partners can see the source code.
/// * [GestureDetector], the widget that is used to detect built-in gestures.
/// * [RawGestureDetector], the widget that is used to detect custom gestures.
/// * [debugPrintRecognizerCallbacksTrace], a flag that can be set to help
/// debug issues with gesture recognizers.
abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableTreeMixin {
/// This method is called for each and all pointers being added. In
/// most cases, you want to override [addAllowedPointer] instead.
void addPointer(PointerDownEvent event) {
_pointerToKind[event.pointer] = event.kind;
if (isPointerAllowed(event)) {
addAllowedPointer(event);
} else {
handleNonAllowedPointer(event);
}
}
}
we are in abstract classGestureRecognizer找到了addPointer方法.The logic here is simple,is to determine whether the current touch event is legal,The specific gesture recognizer will override this method,Implement different logic according to the requirements of the recognizer.
/// Checks whether or not a pointer is allowed to be tracked by this recognizer.
@protected
bool isPointerAllowed(PointerDownEvent event) {
// Currently, it only checks for device kind. But in the future we could check
// for other things e.g. mouse button.
return _supportedDevices == null || _supportedDevices!.contains(event.kind);
}
我们查看TapGestureRecognizerRecognizer method
@override
bool isPointerAllowed(PointerDownEvent event) {
switch (event.buttons) {
case kPrimaryButton:
if (onTapDown == null &&
onTap == null &&
onTapUp == null &&
onTapCancel == null)
return false;
break;
case kSecondaryButton:
if (onSecondaryTap == null &&
onSecondaryTapDown == null &&
onSecondaryTapUp == null &&
onSecondaryTapCancel == null)
return false;
break;
case kTertiaryButton:
if (onTertiaryTapDown == null &&
onTertiaryTapUp == null &&
onTertiaryTapCancel == null)
return false;
break;
default:
return false;
}
return super.isPointerAllowed(event);
}
从上面看,对于点击事件来说,只要onTapDown、 onTap、onTapUp和 onTapCancelAnything that is not empty is considered legal(Strictly speaking, it depends on the parent class.).
We then look at when the gesture event is legal,具体逻辑
/// Registers a new pointer that's been checked to be allowed by this gesture
/// recognizer.
///
/// Subclasses of [GestureRecognizer] are supposed to override this method
/// instead of [addPointer] because [addPointer] will be called for each
/// pointer being added while [addAllowedPointer] is only called for pointers
/// that are allowed by this recognizer.
@protected
void addAllowedPointer(PointerDownEvent event) { }
This logic is implemented in several layers,我们一层一层看
第一层BaseTapGestureRecognizer
@override
void addAllowedPointer(PointerDownEvent event) {
assert(event != null);
if (state == GestureRecognizerState.ready) {
// If there is no result in the previous gesture arena,
// we ignore them and prepare to accept a new pointer.
if (_down != null && _up != null) {
assert(_down!.pointer == _up!.pointer);
_reset();
}
assert(_down == null && _up == null);
// `_down` must be assigned in this method instead of `handlePrimaryPointer`,
// because `acceptGesture` might be called before `handlePrimaryPointer`,
// which relies on `_down` to call `handleTapDown`.
_down = event;
}
if (_down != null) {
// This happens when this tap gesture has been rejected while the pointer
// is down (i.e. due to movement), when another allowed pointer is added,
// in which case all pointers are simply ignored. The `_down` being null
// means that _reset() has been called, since it is always set at the
// first allowed down event and will not be cleared except for reset(),
super.addAllowedPointer(event);
}
}
from the bottom of the methodsuper.addAllowedPointer(event);进入到下一层PrimaryPointerGestureRecognizer
第二层PrimaryPointerGestureRecognizer
@override
void addAllowedPointer(PointerDownEvent event) {
super.addAllowedPointer(event);
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));
}
}
An event is added to this layer asdeadline的定时器,Mainly used to restore the initial state,这里不是我们的重点.
继续沿着super.addAllowedPointer(event);进入第三层
第三层OneSequenceGestureRecognizer
@override
@protected
void addAllowedPointer(PointerDownEvent event) {
startTrackingPointer(event.pointer, event.transform);
}
This is the key entry point of our gesture recognition.
/// This method also adds this recognizer (or its [team] if it's non-null) to
/// the gesture arena for the specified pointer.
///
/// This is called by [OneSequenceGestureRecognizer.addAllowedPointer].
@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(pointer, handleEvent, transform);
This line converts our gesture recognizer'shandleEventThe method is registered to the global singletonpointerRouter里,Subsequent events passpointerRouter直接回调handleEvent方法,will not continue the whole process above.这里也就是说Widget入口RawGestureDetector的Listener只添加了onPointerDownCallback's sake.
_entries[pointer] = _addPointerToArena(pointer);
This line we add that gesture recognizer to the gesture recognition arena,Compete with other recognizers.
具体逻辑如下
GestureArenaEntry _addPointerToArena(int pointer) {
if (_team != null)
return _team!.add(pointer, this);
return GestureBinding.instance!.gestureArena.add(pointer, this);
}
Here to add that not all recognizers are victories in the arena,For example, the click event of today will be,Relatively simple and direct arena distribution of victory,For example, the double-click event that will be discussed later,It is to implement the specific logic inside the own identifier.
There's a lot of logic above,This process is justmixin GestureBindingA small step in dispatching events,That is to say, the component that passes the hit test will only be called in the following loop,so that the above will be executedRawGestureDetector的Listener中的onPointerDown回调.
@override // from HitTestDispatcher
@pragma('vm:notify-debugger-on-exception')
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
......
for (final HitTestEntry entry in hitTestResult.path) {
try {
entry.target.handleEvent(event.transformed(entry.transform), entry);
} catch (exception, stack) {
......
},
));
}
}
}
Very clever here,in hit test,GestureBindingAdds himself to the hit list,Useful in subsequent logic.
each component in the above loop,Added its own gesture recognizers to the arena,So how did this arena close?,That is, other players are not allowed to continue to enter the field..答案就在GestureBinding 的void handleEvent(PointerEvent event, HitTestEntry entry)方法中.
@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);
}
}
这里可以看到pointerRouter,这个就是上面解释过的 GestureBinding.instance!.pointerRouter.addRoute(pointer, handleEvent, transform);
这里调用pointerRouter.route(event); handleEvent方法会执行.
会执行到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);
}
The function of this method is to judge the event asMoveevent and move offset distance is large,then exit the gesture competition,Leave the opportunity to another identifier.
Tap点击事件是在Upevent wins.
@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);
}
}
在Up事件时,Arena needs to be cleared,Need to choose a winner.
/// * [hold]
/// * [release]
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);
}
}
很多Flutter beginner in parentWidget和子Widget 都使用GestureDetector When configuring click events,Only child components can receive callbacks,比较困扰,The above paragraph is the key point.
during clearing,Judging that the competitive player is not empty,Directly declare the first player to win,Notify other players of failure.
Here, due to the principle of depth first,Subcomponent is the first entry,the first player,So it was judged victory.
边栏推荐
猜你喜欢
基于Web的商城后台管理系统的设计与实现
bytebuffer 使用demo
MySQL基础(一)---基础认知及操作
【 8.4 】 source code - [math] [calendar] [delete library 】 【 is not a simple sequence (Bonus) 】
Qixi Festival code confession
虚证、实证如何鉴别?
【转】什么是etcd
Day019 Method overriding and introduction of related classes
Homework 8.4 Interprocess Communication Pipes and Signals
University Physics---Particle Kinematics
随机推荐
Flutter学习5-集成-打包-发布
延迟加载js方式async与defer区别
四位数显表头设计
Mysql的undo log详解
ansible各个模块详解
dedecms后台生成提示读取频道信息失败的解决方法
【转】什么是etcd
说说数据治理中常见的20个问题
Develop your own node package
【8.3】代码源 - 【喵 ~ 喵 ~ 喵~】【树】【与】
人性的弱点
Redis - 13、开发规范
NPDP证书含金量高吗?跟PMP相比?
Why did you start preparing for the soft exam just after the PMP exam?
bytebuffer internal structure
狗仔队:表面编辑多视点图像处理
[BSidesCF 2019]Kookie
Talk about 20 common problems in data governance
什么是ASEMI光伏二极管,光伏二极管的作用
[BJDCTF2020] EasySearch