当前位置:网站首页>Flutter TapGestureRecognizer 如何工作
Flutter TapGestureRecognizer 如何工作
2022-08-05 04:50:00 【bawomingtian123】
在使用点击事件时,我们使用GestureDetector嵌套子组件的方式。那么点击事件背后的TapGestureRecognizer如何识别出用户在“点击”。
从GestureDetector源码build方法中可以看到,在我们给GestureDetector组件添加了onTap方法后,GestureDetector会添加TapGestureRecognizer 手势识别器,这个识别器主要功能就是识别点击事件。
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;
},
);
}在根据设置的不同手势回调,底层添加不同的手势识别器,例如点击,长按,和双击。在最后使用
RawGestureDetector 组件封装手势识别器Map
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;
}Listener里有个关键的onPointerDown回调,这个方法就是组件处理down事件的入口。界面在收到底层上报触摸事件后,组件会通过_handlePointerDown方法进行处理。
void _handlePointerDown(PointerDownEvent event) {
assert(_recognizers != null);
for (final GestureRecognizer recognizer in _recognizers!.values)
recognizer.addPointer(event);
}这个方法主要作用是将down事件添加到各种手势识别器中。这里我们以TapGestureRecognizer为例查看具体实现逻辑。
/// * [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,我们还的继续向下找
这里由于继承层级比较多,我就不一一列出来了,有兴趣的小伙伴查看源码就可看到。
/// * [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);
}
}
}我们在抽象类GestureRecognizer找到了addPointer方法。这里面逻辑也简单,就是判断当前触摸事件是否合法,具体的手势识别器会复写该方法,按照识别器需求实现不同的逻辑。
/// 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);
}我们查看TapGestureRecognizer识别器的方法
@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和 onTapCancel任一不为空就认为合法(严谨说具体还的看父类)。
我们接着看在手势事件合法时,具体逻辑
/// 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) { }这个逻辑分好几层实现,我们一层一层看
第一层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);
}
}从方法底部super.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));
}
}这一层里面添加了一个事件为deadline的定时器,主要用来恢复初始状态,这里不是我们的重点。
继续沿着super.addAllowedPointer(event);进入第三层
第三层OneSequenceGestureRecognizer
@override
@protected
void addAllowedPointer(PointerDownEvent event) {
startTrackingPointer(event.pointer, event.transform);
}这里就是我们手势识别的重点入口。
/// 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);
这行将我们手势识别器的handleEvent方法注册到全局单例pointerRouter里,后续事件通过pointerRouter直接回调handleEvent方法,不会继续走上面整个流程。这里也就是说Widget入口RawGestureDetector的Listener只添加了onPointerDown回调的缘故。
_entries[pointer] = _addPointerToArena(pointer);
这行我们将该手势识别器添加到手势识别竞技场中,同其他识别器进行角逐。
具体逻辑如下
GestureArenaEntry _addPointerToArena(int pointer) {
if (_team != null)
return _team!.add(pointer, this);
return GestureBinding.instance!.gestureArena.add(pointer, this);
}这里补充说一下不是所有识别器都是在竞技场角逐出的胜利,例如今天将的点击事件,比较简单直接有竞技场分配胜利,例如后续会讲到的双击事件,则是由自己识别器内部实现具体逻辑。
上面讲了一大堆逻辑,这个流程只是mixin GestureBinding派发事件的一小步,也就是说在命中测试通过的组件在下面循环里面才会被调用,从而才会执行上面RawGestureDetector的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) {
......
},
));
}
}
}这里非常巧妙,在命中测试中,GestureBinding会讲自己也添加到命中列表中,在后续逻辑中有大用处。
每一个组件在上面的循环中,将自己的手势识别器都加入到了竞技场,那么这个竞技场是如何关闭呢,也就是不允许其他选手继续入场。答案就在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);
}这个方法作用是判断事件为Move事件且移动偏移距离较大,则退出手势竞争,把机会留个其他识别器。
Tap点击事件是在Up事件决胜出来。
@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事件时,竞技场需要清场,需要选择出一个胜利者。
/// * [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 初学者在父Widget和子Widget 都使用GestureDetector 配置点击事件时,只有子组件能收到回调,比较困扰,上面这段就是关键点。
在清场时,判断竞技选手不为空,直接宣判第一个选手胜利,通知其他选手失败。
这里由于采用深度优先的原则,子组件是第一个入场,也就是第一个选手,所以被判定胜利。
边栏推荐
- 8.04 Day35-----MVC three-tier architecture
- How to wrap markdown - md file
- 基于Web的商城后台管理系统的设计与实现
- How do newcomers get started and learn software testing?
- UI自动化测试 App的WebView页面中,当搜索栏无搜索按钮时处理方法
- u-boot中的u-boot,dm-pre-reloc
- 雷克萨斯lm的安全性到底体现在哪里?一起来看看吧
- Mysql's redo log detailed explanation
- 【 8.4 】 source code - [math] [calendar] [delete library 】 【 is not a simple sequence (Bonus) 】
- SkiaSharp 之 WPF 自绘 粒子花园(案例版)
猜你喜欢
![[Surveying] Quick Summary - Excerpt from Gaoshu Gang](/img/35/e5c5349b8d4ccf9203c432a9aaee7b.png)
[Surveying] Quick Summary - Excerpt from Gaoshu Gang

dedecms后台生成提示读取频道信息失败的解决方法

【8.4】代码源 - 【数学】【历法】【删库】【不朴素的数列(Bonus)】

Analyses the mainstream across technology solutions

AUTOCAD - dimension association

Some conventional routines of program development (1)

数字孪生技术在电力系统中的应用现状

Visibility of multi-column attribute column elements: display, visibility, opacity, vertical alignment: vertical-align, z-index The larger it is, the more it will be displayed on the upper layer

C++ core programming
虚证、实证如何鉴别?
随机推荐
dedecms dream weaving tag tag does not support capital letters fix
In the WebView page of the UI automation test App, the processing method when the search bar has no search button
程序开发的一些常规套路(一)
Qixi Festival earn badges
【背包九讲——01背包问题】
1007 Climb Stairs (greedy | C thinking)
uboot开启调试打印信息
开发属于自己的node包
upload upload pictures to Tencent cloud, how to upload pictures
App快速开发建设心得:小程序+自定义插件的重要性
C++ core programming
Talk about 20 common problems in data governance
多列属性column元素的可见性:display、visibility、opacity、垂直对齐方式:vertical-align、z-index 越大越显示在上层
[极客大挑战 2019]FinalSQL
[BSidesCF 2019]Kookie
bytebuffer internal structure
Event parse tree Drain3 usage and explanation
Learning and finishing of probability theory 8: Geometric and hypergeometric distributions
社区分享|腾讯海外游戏基于JumpServer构建游戏安全运营能力
仪表板展示 | DataEase看中国:数据呈现中国资本市场