今天我們來講講GestureDetector的深度剖析,只有了解原理了,才能知道手勢沖突如何解決以及如何更靈活的運用手勢。
我們先來看看GestureDetector的內(nèi)部結(jié)構(gòu)
1.GestureDetector只是一個包裝類,最終還是由Listener的RenderPointListener執(zhí)行事件的操作
2.點擊事件開始時會首先執(zhí)行RawGestureDetector的_handlePointDown方法。
//GestureDetector
class GestureDetector extends StatelessWidget {
Widget build(BuildContext context) {
if (onTapDown != null ||
onTapUp != null ||
.....
) {
//包裝相關(guān)的手勢類(單擊手勢)
gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(..........
},
);
}
if (onDoubleTap != null ||
onDoubleTapDown != null ||
onDoubleTapCancel != null
.....
) {
//包裝相關(guān)的手勢類(雙擊手勢)
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])跟新手勢一樣,就直接賦值
//如果舊手勢跟新手勢不一樣,則需要調(diào)用舊手勢的構(gòu)造函數(shù)并執(zhí)行初始化方法。
_recognizers![type] = oldRecognizers[type] ?? gestures[type]!.constructor();
gestures[type]!.initializer(_recognizers![type]!);
}
//對舊手勢執(zhí)行dispose()方法進(jìn)行銷毀。
for (final Type type in oldRecognizers.keys) {
if (!_recognizers!.containsKey(type)) {
oldRecognizers[type]!.dispose();
}
}
}
@override
Widget build(BuildContext context) {
//RawGestureDetector最終調(diào)用的是Listener,Listener才是重點需要關(guān)注的。
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) {
//主要的手勢類,重點關(guān)注這個RenderPointerListener
return RenderPointerListener(
onPointerDown: onPointerDown,
onPointerMove: onPointerMove,
.......
behavior: behavior,
);
}
}
abstract class OneSequenceGestureRecognizer extends GestureRecognizer {
//...省略100字
}
abstract class GestureRecognizer extends
GestureArenaMember with DiagnosticableTreeMixin {
//...省略100字
}
在點擊事件進(jìn)來時,事件的傳遞流程梳理
1.每個傳進(jìn)來的手勢都會被打包成GestureRecognizer的相關(guān)子類,OneSequenceGestureRecognizer是我們最常用的子類,同時它也是眾多子類的父類。
2.打包好的GestureRecognizer相關(guān)子類會緩存到RawGestureDetector的gestures變量中以供后面手勢加入競技場和競爭的時候使用到
3.RawGestureDetector每一次的初始化的時候都會對gestures列表進(jìn)行更新,始終保持最新的緩存
4.這個RenderPointerListener是Listener的renderObject,這個類間接繼承了HitTestTarget接口,其實所有的renderObject都實現(xiàn)了HitTestTarget這個接口。顧名思義,單擊測試目標(biāo)。后面手勢競爭的時候都會對實現(xiàn)HitTestTarget的實例進(jìn)行單擊測試。
5.GestureRecognizer同時也繼承了GestureArenaMember(競技成員),所以子類也可以看作是競技成員
下圖是GestureRecognizer的部分繼承關(guān)系
//RenderPointerListener(RenderObject)
@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
assert(debugHandleEvent(event, entry));
if (event is PointerDownEvent) {
//這里執(zhí)行的就是上圖的_handlePointDown方法
return onPointerDown?.call(event);
}
if (event is PointerMoveEvent) {
return onPointerMove?.call(event);
}
if (event is PointerUpEvent) {
return onPointerUp?.call(event);
}
....
}
小結(jié):這一部分主要講述了手勢事件如何封裝成GestureRecognizer,并最終走到RenderPointerListener的handleEvent對手勢類進(jìn)行處理進(jìn)行處理。在此之前我們先看看GestureBinding的初始化監(jiān)聽。對于后面的手勢競爭有非常大的幫助。
mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
//創(chuàng)建手勢競技場(負(fù)責(zé)決出競技勝者)
final GestureArenaManager gestureArena = GestureArenaManager();
//創(chuàng)建目標(biāo)收集類(負(fù)責(zé)收集單擊測試目標(biāo))
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類中執(zhí)行runApp()方法的時候,就開始對GestureBinding。的相關(guān)監(jiān)聽進(jìn)行初始化
2.同時創(chuàng)建手勢競技場GestureArenaManager負(fù)責(zé)管理不同的競技場,每個競技場又管理者不同的GestureArenaMember的成員
3.同時創(chuàng)建了目標(biāo)收集器_hitTests針對不同的事件類型創(chuàng)建不同的HitTestResult(這就是真正的目標(biāo)收集器,即收集實現(xiàn)了HitTestTarget接口的RenderObject和GestureBinding)單擊測試目標(biāo)收集器
4.注意,GestureBinding也是實現(xiàn)了HitTestTarget的單擊目標(biāo)測試,同上面所說的RenderObject一樣會接受單擊目標(biāo)測試。
5.GestureBinding實現(xiàn)了_handlePointerEventImmediately這個方法,由android或ios監(jiān)聽事件并傳入,對事件進(jìn)行進(jìn)一步的處理。
手勢處理主要分兩個階段
1.目標(biāo)收集
2.手勢競爭
一、目標(biāo)收集
//GestureBinding的實現(xiàn),監(jiān)聽底層收到的事件進(jìn)行相關(guān)處理并傳輸
void _handlePointerEventImmediately(PointerEvent event) {
HitTestResult? hitTestResult;
if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent || event is PointerPanZoomStartEvent) {
//創(chuàng)建一個目標(biāo)收集器。一般事件開始的時候都會先走這個,這是第一步。
hitTestResult = HitTestResult();
//單擊測試,收集可以響應(yīng)單機(jī)測試的實例
hitTestInView(hitTestResult, event.position, event.viewId);
if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
//存儲單擊測試結(jié)果,后面可以使用到
_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事件,取出單擊測試的結(jié)果(通過單擊測試的實例)
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中又調(diào)用了renderView的hitTest
_viewIdToRenderView[viewId]?.hitTest(result, position: position);
//這里最后才會調(diào)用GestureBinding的hitTestInView
super.hitTestInView(result, position, viewId);
}
bool hitTest(HitTestResult result, { required Offset position }) {
if (child != null) {
//這里又開始觸發(fā)子節(jié)點的hitTest
child!.hitTest(BoxHitTestResult.wrap(result), position: position);
}
//最后將自身即根renderview加入單擊測試結(jié)果
result.add(HitTestEntry(this));
return true;
}
//子節(jié)點的單擊測試方法
bool hitTest(BoxHitTestResult result, { required Offset position }) {
//這里判斷單擊的position是否在_size的范圍內(nèi)(就是是否在子節(jié)點的范圍內(nèi))
if (_size!.contains(position)) {
if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
//子節(jié)點在此刻通過了單擊測試,即在點擊范圍內(nèi),則加入到result中。
result.add(BoxHitTestEntry(this, position));
return true;
}
}
return false;
}
//開始事件分發(fā)
void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
if (hitTestResult == null) {
try {
//hitTestResult為null說明是可能回調(diào)了
//PointerHoverEvent,PointerAddedEvent,PointerRemovedEvent
//由路由分發(fā)出去
pointerRouter.route(event);
} catch (exception, stack) {
for (final HitTestEntry entry in hitTestResult.path) {
try {
//執(zhí)行所有目標(biāo)收集到的單擊測試實例的handleEvent方法。
entry.target.handleEvent(event.transformed(entry.transform), entry);
} catch (exception, stack) {
}
}
}
//GestureDetector實現(xiàn)的RenderPointerListener(調(diào)用子節(jié)點的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);
}
//省略一萬字....
}
目標(biāo)收集流程
1.當(dāng)事件下發(fā)到GestureBinding時,會分別對不同事件進(jìn)行判斷處理,其中有手勢點擊和手勢抬起事件
2.當(dāng)進(jìn)入PointerDownEvent的時候,先進(jìn)入Rendview根節(jié)點的hitTest方法調(diào)用hitTestChildren進(jìn)行子節(jié)點遍歷,判斷子節(jié)點是否符合點擊范圍,如果符合則將自身加入hitTestResult單擊測試列表中
3.當(dāng)遍歷完子節(jié)點后再單擊測試Rendview根節(jié)點,只有hitTestSelf條件符合了才能將自身加入到單擊測試列表hitTestResult中。
4.最后一步再將GestureBinding加入到單擊測試列表中,因為GestureBinding也實現(xiàn)了HitTestTarget單擊測試目標(biāo)接口。都會進(jìn)行測試。
5.當(dāng)單擊目標(biāo)測試完成后,將進(jìn)行事件分發(fā),分發(fā)到各個RendObject(都實現(xiàn)了HitTestTarget方法)中,即調(diào)用每個實例的handleEvent方法。
6.handleEvent的第一次點擊即PointerDownEvent終會執(zhí)行RawGestureDetector的_handlePointerDown方法
至此,目標(biāo)收集流程已經(jīng)結(jié)束,現(xiàn)在開始事件競爭
二、手勢競爭
手勢收集完成后,就進(jìn)入到了RawGestureDetector的_handlePointerDown方法
void _handlePointerDown(PointerDownEvent event) {
assert(_recognizers != null);
//這是對前面我們收集的競技成員進(jìn)行遍歷訪問
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);
}
//在這里開始將當(dāng)前的GestureArenaMember(就是上面的Recognizer)添加到競技場gestureArena
GestureArenaEntry _addPointerToArena(int pointer) {
if (_team != null) {
return _team!.add(pointer, this);
}
return GestureBinding.instance.gestureArena.add(pointer, this);
}
1._recognizers最初在初始化的時候已經(jīng)對不同的事件進(jìn)行手勢封裝成GestureRecognizer的不同子類,現(xiàn)在就是開始用的時候了
2.往下走最終調(diào)用_addPointerToArena方法,即將GestureRecognizer的子類實現(xiàn)添加到GestureBinding的gestureArena手勢競技場中進(jìn)行手勢競爭。
三、?決出最終勝者
由于目標(biāo)收集中最后一個加入的是GestureBinding,所以GestureBinding也是實現(xiàn)了HitTestTarget接口
@override // from HitTestTarget
void handleEvent(PointerEvent event, HitTestEntry entry) {
pointerRouter.route(event);
if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
//關(guān)閉競技場
gestureArena.close(event.pointer);
} else if (event is PointerUpEvent || event is PointerPanZoomEndEvent) {
//清理競技場
gestureArena.sweep(event.pointer);
} else if (event is PointerSignalEvent) {
pointerSignalResolver.resolve(event);
}
}
//關(guān)閉競技場(只是做個標(biāo)記)
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) {
//沒有成員,則移除當(dāng)前手勢的競技場
_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添加進(jìn)競技場之后,在GestureBinding開始決出勝者,即可以響應(yīng)點擊事件的成員。
2.在競技場關(guān)閉的時候開始決出勝者,也是事件開始的事件即PointerDownEvent事件,開始決策
3.手勢成員只有一個的時候,直接取第一個手勢為勝者,手勢成員為空的時候,直接清理競技場,手勢成員已經(jīng)有勝者的時候,直接確定競技場勝者
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.
//直接取第一個成員作為勝者并調(diào)用其的acceptGesture
state.members.first.acceptGesture(pointer);
// 其他的敗者則調(diào)用rejectGesture
for (int i = 1; i < state.members.length; i++) {
state.members[i].rejectGesture(pointer);
}
}
}
文章來源:http://www.zghlxwxcb.cn/news/detail-859776.html
只要競技場沒有被掛起或為空,且此時會有多個手勢競技成員,這時候會取第一個成員作為勝者,就是renderbox最里面的那個。下節(jié)重點講解雙擊事件以及事件的靈活運用。文章來源地址http://www.zghlxwxcb.cn/news/detail-859776.html
到了這里,關(guān)于Flutter原理篇:GestureDetector原理深度剖析及手勢原理(上)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!