今天我们来讲讲GestureDetector的深度剖析,只有了解原理了,才能知道手势冲突如何解决以及如何更灵活的运用手势。
我们先来看看GestureDetector的内部结构
1.GestureDetector只是一个包装类,最终还是由Listener的RenderPointListener执行事件的操作
2.点击事件开始时会首先执行RawGestureDetector的_handlePointDown方法。
//GestureDetector
class GestureDetector extends StatelessWidget {
Widget build(BuildContext context) {
if (onTapDown != null ||
onTapUp != null ||
.....
) {
//包装相关的手势类(单击手势)
gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(..........
},
);
}
if (onDoubleTap != null ||
onDoubleTapDown != null ||
onDoubleTapCancel != null
.....
) {
//包装相关的手势类(双击手势)
gestures[DoubleTapGestureRecognizer]=GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(..........
},
);
}
........
return RawGestureDetector(
gestures: gestures,
behavior: behavior,
excludeFromSemantics: excludeFromSemantics,
child: child,
);
}
//RawGestureDetector的state方法
class RawGestureDetectorState extends State<RawGestureDetector> {
@override
void initState() {
super.initState();
_semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
_syncAll(widget.gestures);
}
void _syncAll(Map<Type, GestureRecognizerFactory> gestures) {
//手势保存
final Map<Type, GestureRecognizer> oldRecognizers = _recognizers!;
_recognizers = <Type, GestureRecognizer>{};
//遍历新手势,过滤旧手势。gestures是从GestureDetector传过来的新手势
for (final Type type in gestures.keys) {
//如果是旧的手势(oldRecognizers[type])跟新手势一样,就直接赋值
//如果旧手势跟新手势不一样,则需要调用旧手势的构造函数并执行初始化方法。
_recognizers![type] = oldRecognizers[type] ?? gestures[type]!.constructor();
gestures[type]!.initializer(_recognizers![type]!);
}
//对旧手势执行dispose()方法进行销毁。
for (final Type type in oldRecognizers.keys) {
if (!_recognizers!.containsKey(type)) {
oldRecognizers[type]!.dispose();
}
}
}
@override
Widget build(BuildContext context) {
//RawGestureDetector最终调用的是Listener,Listener才是重点需要关注的。
Widget result = Listener(
onPointerDown: _handlePointerDown,
onPointerPanZoomStart: _handlePointerPanZoomStart,
behavior: widget.behavior ?? _defaultBehavior,
child: widget.child,
);
return result;
}
}
//Listener的类
class Listener extends SingleChildRenderObjectWidget {
@override
RenderPointerListener createRenderObject(BuildContext context) {
//主要的手势类,重点关注这个RenderPointerListener
return RenderPointerListener(
onPointerDown: onPointerDown,
onPointerMove: onPointerMove,
.......
behavior: behavior,
);
}
}
abstract class OneSequenceGestureRecognizer extends GestureRecognizer {
//...省略100字
}
abstract class GestureRecognizer extends
GestureArenaMember with DiagnosticableTreeMixin {
//...省略100字
}
在点击事件进来时,事件的传递流程梳理
1.每个传进来的手势都会被打包成GestureRecognizer的相关子类,OneSequenceGestureRecognizer是我们最常用的子类,同时它也是众多子类的父类。
2.打包好的GestureRecognizer相关子类会缓存到RawGestureDetector的gestures变量中以供后面手势加入竞技场和竞争的时候使用到
3.RawGestureDetector每一次的初始化的时候都会对gestures列表进行更新,始终保持最新的缓存
4.这个RenderPointerListener是Listener的renderObject,这个类间接继承了HitTestTarget接口,其实所有的renderObject都实现了HitTestTarget这个接口。顾名思义,单击测试目标。后面手势竞争的时候都会对实现HitTestTarget的实例进行单击测试。
5.GestureRecognizer同时也继承了GestureArenaMember(竞技成员),所以子类也可以看作是竞技成员
下图是GestureRecognizer的部分继承关系
//RenderPointerListener(RenderObject)
@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
assert(debugHandleEvent(event, entry));
if (event is PointerDownEvent) {
//这里执行的就是上图的_handlePointDown方法
return onPointerDown?.call(event);
}
if (event is PointerMoveEvent) {
return onPointerMove?.call(event);
}
if (event is PointerUpEvent) {
return onPointerUp?.call(event);
}
....
}
小结:这一部分主要讲述了手势事件如何封装成GestureRecognizer,并最终走到RenderPointerListener的handleEvent对手势类进行处理进行处理。在此之前我们先看看GestureBinding的初始化监听。对于后面的手势竞争有非常大的帮助。
mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
//创建手势竞技场(负责决出竞技胜者)
final GestureArenaManager gestureArena = GestureArenaManager();
//创建目标收集类(负责收集单击测试目标)
final Map<int, HitTestResult> _hitTests = <int, HitTestResult>{};
}
//手势竞技场
class GestureArenaManager {
//管理不同的成员管理类
final Map<int, _GestureArena> _arenas = <int, _GestureArena>{};
}
//手势竞技成员管理
class _GestureArena {
//每个成员管理类都持有一个竞技成员列表,即上面说的GestureRecognizer啦
final List<GestureArenaMember> members = <GestureArenaMember>[];
bool isOpen = true;
bool isHeld = false;
}
1.我们最初在main类中执行runApp()方法的时候,就开始对GestureBinding。的相关监听进行初始化
2.同时创建手势竞技场GestureArenaManager负责管理不同的竞技场,每个竞技场又管理者不同的GestureArenaMember的成员
3.同时创建了目标收集器_hitTests针对不同的事件类型创建不同的HitTestResult(这就是真正的目标收集器,即收集实现了HitTestTarget接口的RenderObject和GestureBinding)单击测试目标收集器
4.注意,GestureBinding也是实现了HitTestTarget的单击目标测试,同上面所说的RenderObject一样会接受单击目标测试。
5.GestureBinding实现了_handlePointerEventImmediately这个方法,由android或ios监听事件并传入,对事件进行进一步的处理。
手势处理主要分两个阶段
1.目标收集
2.手势竞争
一、目标收集
//GestureBinding的实现,监听底层收到的事件进行相关处理并传输
void _handlePointerEventImmediately(PointerEvent event) {
HitTestResult? hitTestResult;
if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent || event is PointerPanZoomStartEvent) {
//创建一个目标收集器。一般事件开始的时候都会先走这个,这是第一步。
hitTestResult = HitTestResult();
//单击测试,收集可以响应单机测试的实例
hitTestInView(hitTestResult, event.position, event.viewId);
if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
//存储单击测试结果,后面可以使用到
_hitTests[event.pointer] = hitTestResult;
}
} else if (event is PointerUpEvent || event is PointerCancelEvent || event is PointerPanZoomEndEvent) {
//移除本次事件
hitTestResult = _hitTests.remove(event.pointer);
} else if (event.down || event is PointerPanZoomUpdateEvent) {
//其他类型的down事件,取出单击测试的结果(通过单击测试的实例)
hitTestResult = _hitTests[event.pointer];
}
if (hitTestResult != null ||
event is PointerAddedEvent ||
event is PointerRemovedEvent) {
//分配事件给通过单击测试的实例(hitTestResult)
dispatchEvent(event, hitTestResult);
}
}
//RendererBinding
@override
void hitTestInView(HitTestResult result, Offset position, int viewId) {
//在RenderBinding中又调用了renderView的hitTest
_viewIdToRenderView[viewId]?.hitTest(result, position: position);
//这里最后才会调用GestureBinding的hitTestInView
super.hitTestInView(result, position, viewId);
}
bool hitTest(HitTestResult result, { required Offset position }) {
if (child != null) {
//这里又开始触发子节点的hitTest
child!.hitTest(BoxHitTestResult.wrap(result), position: position);
}
//最后将自身即根renderview加入单击测试结果
result.add(HitTestEntry(this));
return true;
}
//子节点的单击测试方法
bool hitTest(BoxHitTestResult result, { required Offset position }) {
//这里判断单击的position是否在_size的范围内(就是是否在子节点的范围内)
if (_size!.contains(position)) {
if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
//子节点在此刻通过了单击测试,即在点击范围内,则加入到result中。
result.add(BoxHitTestEntry(this, position));
return true;
}
}
return false;
}
//开始事件分发
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
if (hitTestResult == null) {
try {
//hitTestResult为null说明是可能回调了
//PointerHoverEvent,PointerAddedEvent,PointerRemovedEvent
//由路由分发出去
pointerRouter.route(event);
} catch (exception, stack) {
for (final HitTestEntry entry in hitTestResult.path) {
try {
//执行所有目标收集到的单击测试实例的handleEvent方法。
entry.target.handleEvent(event.transformed(entry.transform), entry);
} catch (exception, stack) {
}
}
}
//GestureDetector实现的RenderPointerListener(调用子节点的handleEvent)
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);
}
//省略一万字....
}
目标收集流程
1.当事件下发到GestureBinding时,会分别对不同事件进行判断处理,其中有手势点击和手势抬起事件
2.当进入PointerDownEvent的时候,先进入Rendview根节点的hitTest方法调用hitTestChildren进行子节点遍历,判断子节点是否符合点击范围,如果符合则将自身加入hitTestResult单击测试列表中
3.当遍历完子节点后再单击测试Rendview根节点,只有hitTestSelf条件符合了才能将自身加入到单击测试列表hitTestResult中。
4.最后一步再将GestureBinding加入到单击测试列表中,因为GestureBinding也实现了HitTestTarget单击测试目标接口。都会进行测试。
5.当单击目标测试完成后,将进行事件分发,分发到各个RendObject(都实现了HitTestTarget方法)中,即调用每个实例的handleEvent方法。
6.handleEvent的第一次点击即PointerDownEvent终会执行RawGestureDetector的_handlePointerDown方法
至此,目标收集流程已经结束,现在开始事件竞争
二、手势竞争
手势收集完成后,就进入到了RawGestureDetector的_handlePointerDown方法
void _handlePointerDown(PointerDownEvent event) {
assert(_recognizers != null);
//这是对前面我们收集的竞技成员进行遍历访问
for (final GestureRecognizer recognizer in _recognizers!.values) {
//给每个竞技成员跟踪点击事件
recognizer.addPointer(event);
}
}
}
void addPointer(PointerDownEvent event) {
_pointerToKind[event.pointer] = event.kind;
if (isPointerAllowed(event)) {
addAllowedPointer(event);
} else {
handleNonAllowedPointer(event);
}
}
}
//由子类OneSequenceGestureRecognizer(这个比较常用)
void addAllowedPointer(PointerDownEvent event) {
//开始跟踪事件处理
startTrackingPointer(event.pointer, event.transform);
}
}
@protected
void startTrackingPointer(int pointer, [Matrix4? transform]) {
//省略部分代码...
_entries[pointer] = _addPointerToArena(pointer);
}
//在这里开始将当前的GestureArenaMember(就是上面的Recognizer)添加到竞技场gestureArena
GestureArenaEntry _addPointerToArena(int pointer) {
if (_team != null) {
return _team!.add(pointer, this);
}
return GestureBinding.instance.gestureArena.add(pointer, this);
}
1._recognizers最初在初始化的时候已经对不同的事件进行手势封装成GestureRecognizer的不同子类,现在就是开始用的时候了
2.往下走最终调用_addPointerToArena方法,即将GestureRecognizer的子类实现添加到GestureBinding的gestureArena手势竞技场中进行手势竞争。
三、 决出最终胜者
由于目标收集中最后一个加入的是GestureBinding,所以GestureBinding也是实现了HitTestTarget接口
@override // from HitTestTarget
void handleEvent(PointerEvent event, HitTestEntry entry) {
pointerRouter.route(event);
if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
//关闭竞技场
gestureArena.close(event.pointer);
} else if (event is PointerUpEvent || event is PointerPanZoomEndEvent) {
//清理竞技场
gestureArena.sweep(event.pointer);
} else if (event is PointerSignalEvent) {
pointerSignalResolver.resolve(event);
}
}
//关闭竞技场(只是做个标记)
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;
_tryToResolveArena(pointer, state);
}
//决出竞技场最终的胜者
void _tryToResolveArena(int pointer, _GestureArena state) {
if (state.members.length == 1) {
//只有一个成员,决出胜者
scheduleMicrotask(() => _resolveByDefault(pointer, state));
} else if (state.members.isEmpty) {
//没有成员,则移除当前手势的竞技场
_arenas.remove(pointer);
} else if (state.eagerWinner != null) {
//只有一个胜者,直接宣布获胜
_resolveInFavorOf(pointer, state, state.eagerWinner!);
}
}
void _resolveByDefault(int pointer, _GestureArena state) {
if (!_arenas.containsKey(pointer)) {
return; // This arena has already resolved.
}
final List<GestureArenaMember> members = state.members;
_arenas.remove(pointer);
//取第一个作为胜者
state.members.first.acceptGesture(pointer);
}
1.前面子类GestureRecognizer添加进竞技场之后,在GestureBinding开始决出胜者,即可以响应点击事件的成员。
2.在竞技场关闭的时候开始决出胜者,也是事件开始的事件即PointerDownEvent事件,开始决策
3.手势成员只有一个的时候,直接取第一个手势为胜者,手势成员为空的时候,直接清理竞技场,手势成员已经有胜者的时候,直接确定竞技场胜者
void sweep(int pointer) {
final _GestureArena? state = _arenas[pointer];
if (state == null) {
//竞技场为空直接返回。
return;
}
assert(!state.isOpen);
if (state.isHeld) {
//竞技场被挂起,直接返回。一般是双击的时候会用到这个挂起
state.hasPendingSweep = true;
return;
}
//移除竞技场
_arenas.remove(pointer);
//竞技场成员不为空
if (state.members.isNotEmpty) {
// First member wins.
//直接取第一个成员作为胜者并调用其的acceptGesture
state.members.first.acceptGesture(pointer);
// 其他的败者则调用rejectGesture
for (int i = 1; i < state.members.length; i++) {
state.members[i].rejectGesture(pointer);
}
}
}
文章来源:https://www.toymoban.com/news/detail-859776.html
只要竞技场没有被挂起或为空,且此时会有多个手势竞技成员,这时候会取第一个成员作为胜者,就是renderbox最里面的那个。下节重点讲解双击事件以及事件的灵活运用。文章来源地址https://www.toymoban.com/news/detail-859776.html
到了这里,关于Flutter原理篇:GestureDetector原理深度剖析及手势原理(上)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!