android中存在事件沖突茬缩,flutter其實(shí)也存在,但是官方友好的出了一個(gè)控件(GestureDetector)來解決這個(gè)問題
在事件分發(fā)那里吼旧,分析了基礎(chǔ)手勢控件Listener的事件傳遞(傳送門)凰锡,我們先看下面這個(gè)例子:
class TouchDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Listener(
child: Container(
color: Colors.blue,
width: 300,
height: 300,
child: Stack(
//將布局按比例分成坐標(biāo),原點(diǎn)(0,0)位于中間,(-1,-1)為左上
alignment: Alignment(0, 0),
children: <Widget>[
Listener(
child: Container(
color: Colors.red,
width: 100,
height: 100,
),
onPointerDown: (event){
print("內(nèi)層");
},
),
],
)
),
onPointerDown: (event){
print("外層");
},
);
}
}
這個(gè)界面簡單掂为,就是一個(gè)大的正方形里有一個(gè)紅色的小正方形裕膀。當(dāng)我們點(diǎn)擊紅色正方形時(shí):
I/flutter ( 4898): 內(nèi)層
I/flutter ( 4898): 外層
打印結(jié)果是先內(nèi)層,再外層勇哗。根據(jù)事件分發(fā)里L(fēng)istener的分析昼扛,可以知道result列表的添加順序是先內(nèi)層,再外層
Ok智绸,進(jìn)入正題野揪,GestureDetector如何解決事件沖突的?
GestureDetector
GestureDetector是一個(gè)StatelessWidget瞧栗,直接看build方法
class GestureDetector extends StatelessWidget {
...
@override
Widget build(BuildContext context) {
//保存手勢識別工廠的map
//GestureRecognizerFactory保存GestureRecognizer的構(gòu)造方法和init方法
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
//單擊事件
if (onTapDown != null || onTapUp != null || onTap != null || onTapCancel != null) {
gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
//1.構(gòu)造方法
() => TapGestureRecognizer(debugOwner: this),
//2.初始化方法
(TapGestureRecognizer instance) {
instance
..onTapDown = onTapDown
..onTapUp = onTapUp
..onTap = onTap
..onTapCancel = onTapCancel;
},
);
}
//雙擊事件
if (onDoubleTap != null) {
gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
() => DoubleTapGestureRecognizer(debugOwner: this),
(DoubleTapGestureRecognizer instance) {
instance
..onDoubleTap = onDoubleTap;
},
);
}
//長按事件
if (onLongPress != null || onLongPressUp !=null) {
gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
() => LongPressGestureRecognizer(debugOwner: this),
(LongPressGestureRecognizer instance) {
instance
..onLongPress = onLongPress
..onLongPressUp = onLongPressUp;
},
);
}
//垂直拖拽事件
if (onVerticalDragDown != null ||
onVerticalDragStart != null ||
onVerticalDragUpdate != null ||
onVerticalDragEnd != null ||
onVerticalDragCancel != null) {
gestures[VerticalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
() => VerticalDragGestureRecognizer(debugOwner: this),
(VerticalDragGestureRecognizer instance) {
instance
..onDown = onVerticalDragDown
..onStart = onVerticalDragStart
..onUpdate = onVerticalDragUpdate
..onEnd = onVerticalDragEnd
..onCancel = onVerticalDragCancel;
},
);
}
//水平拖拽事件
if (onHorizontalDragDown != null ||
onHorizontalDragStart != null ||
onHorizontalDragUpdate != null ||
onHorizontalDragEnd != null ||
onHorizontalDragCancel != null) {
gestures[HorizontalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
() => HorizontalDragGestureRecognizer(debugOwner: this),
(HorizontalDragGestureRecognizer instance) {
instance
..onDown = onHorizontalDragDown
..onStart = onHorizontalDragStart
..onUpdate = onHorizontalDragUpdate
..onEnd = onHorizontalDragEnd
..onCancel = onHorizontalDragCancel;
},
);
}
//同時(shí)水平和垂直拖拽事件
if (onPanDown != null ||
onPanStart != null ||
onPanUpdate != null ||
onPanEnd != null ||
onPanCancel != null) {
gestures[PanGestureRecognizer] = GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
() => PanGestureRecognizer(debugOwner: this),
(PanGestureRecognizer instance) {
instance
..onDown = onPanDown
..onStart = onPanStart
..onUpdate = onPanUpdate
..onEnd = onPanEnd
..onCancel = onPanCancel;
},
);
}
//縮放手勢事件
if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) {
gestures[ScaleGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>(
() => ScaleGestureRecognizer(debugOwner: this),
(ScaleGestureRecognizer instance) {
instance
..onStart = onScaleStart
..onUpdate = onScaleUpdate
..onEnd = onScaleEnd;
},
);
}
return RawGestureDetector(
gestures: gestures,
behavior: behavior,
excludeFromSemantics: excludeFromSemantics,
child: child,
);
}
}
在gestures中斯稳,已經(jīng)將回調(diào)事件方法分好了手勢識別類,繼續(xù)往下看
class RawGestureDetector extends StatefulWidget {
...
@override
RawGestureDetectorState createState() => RawGestureDetectorState();
}
查看state的build方法
class RawGestureDetectorState extends State<RawGestureDetector> {
...
@override
Widget build(BuildContext context) {
Widget result = Listener(
onPointerDown: _handlePointerDown,
behavior: widget.behavior ?? _defaultBehavior,
child: widget.child
);
//excludeFromSemantics默認(rèn)為false的
if (!widget.excludeFromSemantics)
result = _GestureSemantics(owner: this, child: result);
return result;
}
void _handlePointerDown(PointerDownEvent event) {
assert(_recognizers != null);
//給每個(gè)手勢識別類添加手指按下事件
for (GestureRecognizer recognizer in _recognizers.values)
//關(guān)鍵
recognizer.addPointer(event);
}
}
GestureRecognizer的子類比較多迹恐,這里我們選水平滑動(dòng)HorizontalDragGestureRecognizer進(jìn)行分析挣惰, addPointer位于其父類DragGestureRecognizer中:
@override
void addPointer(PointerEvent event) {
//開始追蹤手勢
startTrackingPointer(event.pointer);
//慣性滑動(dòng)追蹤類
_velocityTrackers[event.pointer] = VelocityTracker();
if (_state == _DragState.ready) {
_state = _DragState.possible;
_initialPosition = event.position;
_pendingDragOffset = Offset.zero;
_lastPendingEventTimestamp = event.timeStamp;
if (onDown != null) //回調(diào)方法onDown
invokeCallback<void>('onDown', () => onDown(DragDownDetails(globalPosition: _initialPosition)));
} else if (_state == _DragState.accepted) {
resolve(GestureDisposition.accepted);
}
}
_DragState共三個(gè)狀態(tài):ready(準(zhǔn)備,初始值)、possible(正在手勢競爭中)殴边、accepted(競爭勝利)
@protected
void startTrackingPointer(int pointer) {
//handleEvent為一個(gè)函數(shù)
GestureBinding.instance.pointerRouter
.addRoute(pointer, handleEvent);
_trackedPointers.add(pointer);
assert(!_entries.containsValue(pointer));
//每個(gè)手勢的入場券列表賦值
_entries[pointer] = _addPointerToArena(pointer);
}
addRoute傳遞的handleEvent為一個(gè)函數(shù)憎茂,這個(gè)函數(shù)后面再分析
a. pointerRouter中的_routeMap賦值
_routeMap的值就是通過addRoute方法來添加的
typedef PointerRoute = void Function(PointerEvent event);
//參數(shù)二為handleEvent函數(shù)
void addRoute(int pointer, PointerRoute route) {
final LinkedHashSet<PointerRoute> routes = _routeMap.putIfAbsent(pointer, () => LinkedHashSet<PointerRoute>());
assert(!routes.contains(route));
routes.add(route);
}
b. gestureArena中的_arenas賦值
回到前面,繼續(xù)看_addPointerToArena方法
GestureArenaEntry _addPointerToArena(int pointer) {
if (_team != null)
return _team.add(pointer, this);
return GestureBinding.instance.gestureArena.add(pointer, this);
}
基本和上面的添加方法是一致的
GestureArenaEntry add(int pointer, GestureArenaMember member) {
//put: 重復(fù)的key覆蓋
//putIfAbsent: 重復(fù)的key不添加
final _GestureArena state = _arenas.putIfAbsent(pointer, () {
assert(_debugLogDiagnostic(pointer, '★ Opening new gesture arena.'));
return _GestureArena();
});
state.add(member);
assert(_debugLogDiagnostic(pointer, 'Adding: $member'));
//返回了GestureArenaEntry對象锤岸,持有手勢競技場管理類對象竖幔、手勢的id、手勢識別類的對象
return GestureArenaEntry._(this, pointer, member);
}
_instance是GestureBinding中的一個(gè)靜態(tài)對象是偷,構(gòu)造函數(shù)中對其賦值拳氢,還記得事件分發(fā)那最終分析到handleEvent:
final PointerRouter pointerRouter = PointerRouter();
final GestureArenaManager gestureArena = GestureArenaManager();
@override
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);
}
}
之前到了這一步就沒有繼續(xù)追蹤下去,其實(shí)內(nèi)部還通過列表進(jìn)行了操作蛋铆,看下route方法做了什么
void route(PointerEvent event) {
//根據(jù)前面的分析馋评,_routeMap列表中已經(jīng)有數(shù)據(jù)了
final LinkedHashSet<PointerRoute> routes = _routeMap[event.pointer];
final List<PointerRoute> globalRoutes = List<PointerRoute>.from(_globalRoutes);
if (routes != null) {
//遍歷列表
for (PointerRoute route in List<PointerRoute>.from(routes)) {
if (routes.contains(route))
//執(zhí)行
_dispatch(event, route);
}
}
for (PointerRoute route in globalRoutes) {
if (_globalRoutes.contains(route))
_dispatch(event, route);
}
}
PointerRoute是一個(gè)帶參數(shù)無返回類型的函數(shù)(注:PointerRouter是一個(gè)類)
void _dispatch(PointerEvent event, PointerRoute route){
try {
//執(zhí)行匿名函數(shù),即handleEvent函數(shù)
route(event);
} catch (exception, stack) {
...
}
}
執(zhí)行HorizontalDragGestureRecognizer中的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.position);
}
//手勢為PointerMoveEvent
if (event is PointerMoveEvent) {
final Offset delta = event.delta;
if (_state == _DragState.accepted) {
//如果手勢競爭勝利刺啦,則回調(diào)onUpdate方法
if (onUpdate != null) {
invokeCallback<void>('onUpdate', () => onUpdate(DragUpdateDetails(
sourceTimeStamp: event.timeStamp,
delta: _getDeltaForDetails(delta),
primaryDelta: _getPrimaryValueFromOffset(delta),
globalPosition: event.position,
)));
}
} else {
//手勢開始競爭判斷
_pendingDragOffset += delta;
_lastPendingEventTimestamp = event.timeStamp;
//當(dāng)添加滿足留特,則決策判斷為accepted
if (_hasSufficientPendingDragDeltaToAccept)
resolve(GestureDisposition.accepted);
}
}
//如果是up或canel事件禽炬,則刪除路由关面,即pointerRouter移除此handleEvent,即此次事件不再調(diào)用
stopTrackingIfPointerNoLongerDown(event);
}
_hasSufficientPendingDragDeltaToAccept如何為true呢横蜒?
const double kTouchSlop = 18.0;
@override
bool get _hasSufficientPendingDragDeltaToAccept => _pendingDragOffset.dx.abs() > kTouchSlop;
只要移動(dòng)的x差值絕對值大于18符合條件捧韵,垂直滑動(dòng)則為dy市咆,至于PanGestureRecognizer:
const double kPanSlop = kTouchSlop * 2.0;
@override
bool get _hasSufficientPendingDragDeltaToAccept {
//斜邊的絕對值大于2倍的kTouchSlop
return _pendingDragOffset.distance > kPanSlop;
}
由上面可以得出,當(dāng)down手勢傳遞過來再来,只記錄速度,而move手勢傳遞過來,列表中所有的DragGestureRecognizer類都會(huì)調(diào)用handleEvent
如果此時(shí)列表中同時(shí)HorizontalDragGestureRecognizer和VerticalDragGestureRecognizer芒篷,那么它們誰獲得勝利呢搜变?
主要看dx和dy的差值誰先大于kTouchSlop,但如果正好在斜45°移動(dòng)時(shí)针炉,dx和dy差值變化是一樣的挠他,這是主要看它們加入列表的先后順序決定,先添加的獲勝
_state 狀態(tài)修改為_DragState.accepted過程
假如此時(shí)已經(jīng)滿足條件篡帕,進(jìn)入resolve方法
void resolve(GestureDisposition disposition) {
//_entries前面流程創(chuàng)建了殖侵,_entries.values鍵值對中取出全部的value列表
final List<GestureArenaEntry> localEntries = List<GestureArenaEntry>.from(_entries.values);
_entries.clear();
for (GestureArenaEntry entry in localEntries)
entry.resolve(disposition);
}
會(huì)遍歷所有 GestureArenaEntry 的 resolve
class GestureArenaEntry {
GestureArenaEntry._(this._arena, this._pointer, this._member);
final GestureArenaManager _arena; //當(dāng)前競技場管理者
final int _pointer; //手勢的id
final GestureArenaMember _member; //即HorizontalDragGestureRecognizer
void resolve(GestureDisposition disposition) {
_arena._resolve(_pointer, _member, disposition);
}
}
管理者管理所有的事件
void _resolve(int pointer, GestureArenaMember member, GestureDisposition disposition) {
final _GestureArena state = _arenas[pointer];
if (state == null)
return;
//省略斷言
...
//state.members為當(dāng)前競技場里所有手勢識別類的列表
//所有不滿足的類移出列表
if (disposition == GestureDisposition.rejected) {
state.members.remove(member);
member.rejectGesture(pointer);
if (!state.isOpen)
_tryToResolveArena(pointer, state);
} else {
assert(disposition == GestureDisposition.accepted);
//默認(rèn)為true,當(dāng)競技場close是修改為false镰烧,此時(shí)為false
if (state.isOpen) {
//記錄獲勝的手勢識別類
state.eagerWinner ??= member;
} else {
assert(_debugLogDiagnostic(pointer, 'Self-declared winner: $member'));
//調(diào)用
_resolveInFavorOf(pointer, state, member);
}
}
state.isOpen這個(gè)值在PointerDownEvent手勢后會(huì)修改為false
void _resolveInFavorOf(int pointer, _GestureArena state, GestureArenaMember member) {
//省略斷言
...
_arenas.remove(pointer);
for (GestureArenaMember rejectedMember in state.members) {
if (rejectedMember != member)
rejectedMember.rejectGesture(pointer);
}
member.acceptGesture(pointer);
}
其他的手勢識別類調(diào)用rejectGesture拢军,而勝者調(diào)用acceptGesture
@override
void acceptGesture(int pointer) {
if (_state != _DragState.accepted) {
//修改狀態(tài)
_state = _DragState.accepted;
final Offset delta = _pendingDragOffset;
final Duration timestamp = _lastPendingEventTimestamp;
_pendingDragOffset = Offset.zero;
_lastPendingEventTimestamp = null;
//回調(diào)onStart方法
if (onStart != null) {
invokeCallback<void>('onStart', () => onStart(DragStartDetails(
sourceTimeStamp: timestamp,
globalPosition: _initialPosition,
)));
}
//回調(diào)onUpdate方法
if (delta != Offset.zero && onUpdate != null) {
final Offset deltaForDetails = _getDeltaForDetails(delta);
invokeCallback<void>('onUpdate', () => onUpdate(DragUpdateDetails(
sourceTimeStamp: timestamp,
delta: deltaForDetails,
primaryDelta: _getPrimaryValueFromOffset(delta),
globalPosition: _initialPosition + deltaForDetails,
)));
}
}
}
勝利后,因?yàn)樾薷牧藸顟B(tài)怔鳖,所以之后的move手勢不會(huì)再走這一步茉唉,在前面的方法就直接回調(diào)onUpdate方法
再來看看競爭失敗后會(huì)怎么樣
@override
void rejectGesture(int pointer) {
stopTrackingPointer(pointer);
}
@protected
void stopTrackingPointer(int pointer) {
if (_trackedPointers.contains(pointer)) {
//刪除路由
GestureBinding.instance.pointerRouter.removeRoute(pointer, handleEvent);
//移出手勢id
_trackedPointers.remove(pointer);
if (_trackedPointers.isEmpty)
didStopTrackingLastPointer(pointer);
}
}
isOpen值修改
回到最開始,gestureArena.close(event.pointer)中
void close(int pointer) {
final _GestureArena state = _arenas[pointer];
if (state == null)
return;
//關(guān)閉競技場结执,等待勝者出來
state.isOpen = false;
assert(_debugLogDiagnostic(pointer, 'Closing', state));
_tryToResolveArena(pointer, state);
}
查看 _tryToResolveArena方法
void _tryToResolveArena(int pointer, _GestureArena state) {
assert(_arenas[pointer] == state);
assert(!state.isOpen);
//只剩一個(gè)度陆,即無競爭者
if (state.members.length == 1) {
//直接調(diào)用
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);
}
}
而gestureArena.sweep(event.pointer)則是清除所有的路由和記錄(當(dāng)無選手hold)
hold和release
目前源碼里只DoubleTapGestureRecognizer使用
void hold(int pointer) {
final _GestureArena state = _arenas[pointer];
if (state == null)
return;
state.isHeld = true;
assert(_debugLogDiagnostic(pointer, 'Holding', state));
}
void release(int pointer) {
final _GestureArena state = _arenas[pointer];
if (state == null)
return;
state.isHeld = false;
assert(_debugLogDiagnostic(pointer, 'Releasing', state));
if (state.hasPendingSweep)
sweep(pointer);
}
hold后,狀態(tài)isHeld修改為true献幔,在sweep中就不會(huì)清理
void sweep(int pointer) {
final _GestureArena state = _arenas[pointer];
if (state == null)
return;
assert(!state.isOpen);
//hold懂傀,直接返回,不做清空處理
if (state.isHeld) {
//修改狀態(tài)蜡感,在release方法中用于重新調(diào)用sweep方法
state.hasPendingSweep = true;
assert(_debugLogDiagnostic(pointer, 'Delaying sweep', state));
return;
}
assert(_debugLogDiagnostic(pointer, 'Sweeping', state));
_arenas.remove(pointer);
if (state.members.isNotEmpty) {
assert(_debugLogDiagnostic(pointer, 'Winner: ${state.members.first}'));
//第一個(gè)勝者接收手勢
state.members.first.acceptGesture(pointer);
//所有選手退出競技場
for (int i = 1; i < state.members.length; i++)
state.members[i].rejectGesture(pointer);
}
}
來看一下DoubleTapGestureRecognizer中具體怎么使用
void _handleEvent(PointerEvent event) {
final _TapTracker tracker = _trackers[event.pointer];
assert(tracker != null);
if (event is PointerUpEvent) {
if (_firstTap == null)
//這里會(huì)hold蹬蚁,讓競技場等待下一次分出勝負(fù)
_registerFirstTap(tracker);
else
//調(diào)用release,釋放
_registerSecondTap(tracker);
} else if (event is PointerMoveEvent) {
if (!tracker.isWithinTolerance(event, kDoubleTapTouchSlop))
_reject(tracker);
} else if (event is PointerCancelEvent) {
_reject(tracker);
}
}
記錄第一次點(diǎn)擊
void _registerFirstTap(_TapTracker tracker) {
//開啟計(jì)時(shí)器铸敏,超時(shí)時(shí)間為300ms
_startDoubleTapTimer();
//調(diào)用hold
GestureBinding.instance.gestureArena.hold(tracker.pointer);
_freezeTracker(tracker);
_trackers.remove(tracker.pointer);
_clearTrackers();
//記錄
_firstTap = tracker;
}
記錄第二次點(diǎn)擊
void _registerSecondTap(_TapTracker tracker) {
_firstTap.entry.resolve(GestureDisposition.accepted);
tracker.entry.resolve(GestureDisposition.accepted);
_freezeTracker(tracker);
_trackers.remove(tracker.pointer);
//雙擊回調(diào)方法是否存在
if (onDoubleTap != null)
invokeCallback<void>('onDoubleTap', onDoubleTap);
_reset();
}
在_reset() 清空記錄缚忧,狀態(tài)重置
void _reset() {
//停止計(jì)時(shí)器
_stopDoubleTapTimer();
if (_firstTap != null) {
//清空firstTap
final _TapTracker tracker = _firstTap;
_firstTap = null;
_reject(tracker);
//調(diào)用release
GestureBinding.instance.gestureArena.release(tracker.pointer);
}
_clearTrackers();
}
總結(jié)
- 路由在Listener的onPointerDown建立,在gestureArena的sweep清空(可能在這之前已經(jīng)清空)
- 一個(gè)完整的事件由down手勢開始杈笔、up手勢結(jié)束闪水。down手勢時(shí)列表中登記所有要競爭的手勢識別類(競技選手),競技場管理者進(jìn)行管理蒙具,后續(xù)的事件會(huì)通過路由依次傳遞到列表的手勢識別類中球榆,當(dāng)?shù)怯浭謩葑R別類不滿足時(shí)會(huì)退出競爭
GestureArenaManager:競技場管理者,根據(jù)Pointer的多少來構(gòu)建場地
- close:競技場關(guān)閉禁筏,PointerDown手勢觸發(fā)
- sweep:競技場清理持钉,PointerUp手勢觸發(fā)
- hold:保持,sweep時(shí)不會(huì)清理
- release:釋放篱昔,取消保持每强,會(huì)調(diào)用sweep
_GestureArena:競技場場地
GestureArenaEntry:競技場入場券
GestureRecognizer:競技選手 - acceptGesture: 勝者回調(diào)
- rejectGesture: 敗者回調(diào)
- addPointer: 添加手勢始腾,啟動(dòng)追蹤
- invokeCallback: 回調(diào)方法
class GestureArenaManager {
final Map<int, _GestureArena> _arenas = <int, _GestureArena>{};
}
class _GestureArena {
final List<GestureArenaMember> members = <GestureArenaMember>[];
}
class OneSequenceGestureRecognizer extends GestureArenaMember {
final Map<int, GestureArenaEntry> _entries = <int, GestureArenaEntry>{};
}
- 水平或垂直或pan 拖動(dòng),由滑動(dòng)的距離來判斷
tap和doubleTap 由按的次數(shù)判斷(hold)
tap和longPress 由按下的時(shí)間判斷
父類和子類共用同一手勢空执,優(yōu)先子類