android中存在事件冲突,flutter其实也存在,但是官方友好的出了一个控件(GestureDetector)来解决这个问题
在事件分发那里,分析了基础手势控件Listener的事件传递(传送门),我们先看下面这个例子:
class TouchDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Listener(
child: Container(
color: Colors.blue,
width: 300,
height: 300,
child: Stack(
//将布局按比例分成坐标,原点(0,0)位于中间,(-1,-1)为左上
alignment: Alignment(0, 0),
children: <Widget>[
Listener(
child: Container(
color: Colors.red,
width: 100,
height: 100,
),
onPointerDown: (event){
print("内层");
},
),
],
)
),
onPointerDown: (event){
print("外层");
},
);
}
}
这个界面简单,就是一个大的正方形里有一个红色的小正方形。当我们点击红色正方形时:
I/flutter ( 4898): 内层
I/flutter ( 4898): 外层
打印结果是先内层,再外层。根据事件分发里Listener的分析,可以知道result列表的添加顺序是先内层,再外层
Ok,进入正题,GestureDetector如何解决事件冲突的?
GestureDetector
GestureDetector是一个StatelessWidget,直接看build方法
class GestureDetector extends StatelessWidget {
...
@override
Widget build(BuildContext context) {
//保存手势识别工厂的map
//GestureRecognizerFactory保存GestureRecognizer的构造方法和init方法
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
//单击事件
if (onTapDown != null || onTapUp != null || onTap != null || onTapCancel != null) {
gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
//1.构造方法
() => 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;
},
);
}
//同时水平和垂直拖拽事件
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中,已经将回调事件方法分好了手势识别类,继续往下看
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默认为false的
if (!widget.excludeFromSemantics)
result = _GestureSemantics(owner: this, child: result);
return result;
}
void _handlePointerDown(PointerDownEvent event) {
assert(_recognizers != null);
//给每个手势识别类添加手指按下事件
for (GestureRecognizer recognizer in _recognizers.values)
//关键
recognizer.addPointer(event);
}
}
GestureRecognizer的子类比较多,这里我们选水平滑动HorizontalDragGestureRecognizer进行分析, addPointer位于其父类DragGestureRecognizer中:
@override
void addPointer(PointerEvent event) {
//开始追踪手势
startTrackingPointer(event.pointer);
//惯性滑动追踪类
_velocityTrackers[event.pointer] = VelocityTracker();
if (_state == _DragState.ready) {
_state = _DragState.possible;
_initialPosition = event.position;
_pendingDragOffset = Offset.zero;
_lastPendingEventTimestamp = event.timeStamp;
if (onDown != null) //回调方法onDown
invokeCallback<void>('onDown', () => onDown(DragDownDetails(globalPosition: _initialPosition)));
} else if (_state == _DragState.accepted) {
resolve(GestureDisposition.accepted);
}
}
_DragState共三个状态:ready(准备,初始值)、possible(正在手势竞争中)、accepted(竞争胜利)
@protected
void startTrackingPointer(int pointer) {
//handleEvent为一个函数
GestureBinding.instance.pointerRouter
.addRoute(pointer, handleEvent);
_trackedPointers.add(pointer);
assert(!_entries.containsValue(pointer));
//每个手势的入场券列表赋值
_entries[pointer] = _addPointerToArena(pointer);
}
addRoute传递的handleEvent为一个函数,这个函数后面再分析
a. pointerRouter中的_routeMap赋值
_routeMap的值就是通过addRoute方法来添加的
typedef PointerRoute = void Function(PointerEvent event);
//参数二为handleEvent函数
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赋值
回到前面,继续看_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: 重复的key覆盖
//putIfAbsent: 重复的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中的一个静态对象,构造函数中对其赋值,还记得事件分发那最终分析到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);
}
}
之前到了这一步就没有继续追踪下去,其实内部还通过列表进行了操作,看下route方法做了什么
void route(PointerEvent event) {
//根据前面的分析,_routeMap列表中已经有数据了
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))
//执行
_dispatch(event, route);
}
}
for (PointerRoute route in globalRoutes) {
if (_globalRoutes.contains(route))
_dispatch(event, route);
}
}
PointerRoute是一个带参数无返回类型的函数(注:PointerRouter是一个类)
void _dispatch(PointerEvent event, PointerRoute route){
try {
//执行匿名函数,即handleEvent函数
route(event);
} catch (exception, stack) {
...
}
}
执行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) {
//如果手势竞争胜利,则回调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;
//当添加满足,则决策判断为accepted
if (_hasSufficientPendingDragDeltaToAccept)
resolve(GestureDisposition.accepted);
}
}
//如果是up或canel事件,则删除路由,即pointerRouter移除此handleEvent,即此次事件不再调用
stopTrackingIfPointerNoLongerDown(event);
}
_hasSufficientPendingDragDeltaToAccept如何为true呢?
const double kTouchSlop = 18.0;
@override
bool get _hasSufficientPendingDragDeltaToAccept => _pendingDragOffset.dx.abs() > kTouchSlop;
只要移动的x差值绝对值大于18符合条件,垂直滑动则为dy,至于PanGestureRecognizer:
const double kPanSlop = kTouchSlop * 2.0;
@override
bool get _hasSufficientPendingDragDeltaToAccept {
//斜边的绝对值大于2倍的kTouchSlop
return _pendingDragOffset.distance > kPanSlop;
}
由上面可以得出,当down手势传递过来,只记录速度,而move手势传递过来,列表中所有的DragGestureRecognizer类都会调用handleEvent
如果此时列表中同时HorizontalDragGestureRecognizer和VerticalDragGestureRecognizer,那么它们谁获得胜利呢?
主要看dx和dy的差值谁先大于kTouchSlop,但如果正好在斜45°移动时,dx和dy差值变化是一样的,这是主要看它们加入列表的先后顺序决定,先添加的获胜
_state 状态修改为_DragState.accepted过程
假如此时已经满足条件,进入resolve方法
void resolve(GestureDisposition disposition) {
//_entries前面流程创建了,_entries.values键值对中取出全部的value列表
final List<GestureArenaEntry> localEntries = List<GestureArenaEntry>.from(_entries.values);
_entries.clear();
for (GestureArenaEntry entry in localEntries)
entry.resolve(disposition);
}
会遍历所有 GestureArenaEntry 的 resolve
class GestureArenaEntry {
GestureArenaEntry._(this._arena, this._pointer, this._member);
final GestureArenaManager _arena; //当前竞技场管理者
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为当前竞技场里所有手势识别类的列表
//所有不满足的类移出列表
if (disposition == GestureDisposition.rejected) {
state.members.remove(member);
member.rejectGesture(pointer);
if (!state.isOpen)
_tryToResolveArena(pointer, state);
} else {
assert(disposition == GestureDisposition.accepted);
//默认为true,当竞技场close是修改为false,此时为false
if (state.isOpen) {
//记录获胜的手势识别类
state.eagerWinner ??= member;
} else {
assert(_debugLogDiagnostic(pointer, 'Self-declared winner: $member'));
//调用
_resolveInFavorOf(pointer, state, member);
}
}
state.isOpen这个值在PointerDownEvent手势后会修改为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);
}
其他的手势识别类调用rejectGesture,而胜者调用acceptGesture
@override
void acceptGesture(int pointer) {
if (_state != _DragState.accepted) {
//修改状态
_state = _DragState.accepted;
final Offset delta = _pendingDragOffset;
final Duration timestamp = _lastPendingEventTimestamp;
_pendingDragOffset = Offset.zero;
_lastPendingEventTimestamp = null;
//回调onStart方法
if (onStart != null) {
invokeCallback<void>('onStart', () => onStart(DragStartDetails(
sourceTimeStamp: timestamp,
globalPosition: _initialPosition,
)));
}
//回调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,
)));
}
}
}
胜利后,因为修改了状态,所以之后的move手势不会再走这一步,在前面的方法就直接回调onUpdate方法
再来看看竞争失败后会怎么样
@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;
//关闭竞技场,等待胜者出来
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);
//只剩一个,即无竞争者
if (state.members.length == 1) {
//直接调用
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)则是清除所有的路由和记录(当无选手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后,状态isHeld修改为true,在sweep中就不会清理
void sweep(int pointer) {
final _GestureArena state = _arenas[pointer];
if (state == null)
return;
assert(!state.isOpen);
//hold,直接返回,不做清空处理
if (state.isHeld) {
//修改状态,在release方法中用于重新调用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}'));
//第一个胜者接收手势
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)
//这里会hold,让竞技场等待下一次分出胜负
_registerFirstTap(tracker);
else
//调用release,释放
_registerSecondTap(tracker);
} else if (event is PointerMoveEvent) {
if (!tracker.isWithinTolerance(event, kDoubleTapTouchSlop))
_reject(tracker);
} else if (event is PointerCancelEvent) {
_reject(tracker);
}
}
记录第一次点击
void _registerFirstTap(_TapTracker tracker) {
//开启计时器,超时时间为300ms
_startDoubleTapTimer();
//调用hold
GestureBinding.instance.gestureArena.hold(tracker.pointer);
_freezeTracker(tracker);
_trackers.remove(tracker.pointer);
_clearTrackers();
//记录
_firstTap = tracker;
}
记录第二次点击
void _registerSecondTap(_TapTracker tracker) {
_firstTap.entry.resolve(GestureDisposition.accepted);
tracker.entry.resolve(GestureDisposition.accepted);
_freezeTracker(tracker);
_trackers.remove(tracker.pointer);
//双击回调方法是否存在
if (onDoubleTap != null)
invokeCallback<void>('onDoubleTap', onDoubleTap);
_reset();
}
在_reset() 清空记录,状态重置
void _reset() {
//停止计时器
_stopDoubleTapTimer();
if (_firstTap != null) {
//清空firstTap
final _TapTracker tracker = _firstTap;
_firstTap = null;
_reject(tracker);
//调用release
GestureBinding.instance.gestureArena.release(tracker.pointer);
}
_clearTrackers();
}
总结
- 路由在Listener的onPointerDown建立,在gestureArena的sweep清空(可能在这之前已经清空)
- 一个完整的事件由down手势开始、up手势结束。down手势时列表中登记所有要竞争的手势识别类(竞技选手),竞技场管理者进行管理,后续的事件会通过路由依次传递到列表的手势识别类中,当登记手势识别类不满足时会退出竞争
GestureArenaManager:竞技场管理者,根据Pointer的多少来构建场地
- close:竞技场关闭,PointerDown手势触发
- sweep:竞技场清理,PointerUp手势触发
- hold:保持,sweep时不会清理
- release:释放,取消保持,会调用sweep
_GestureArena:竞技场场地
GestureArenaEntry:竞技场入场券
GestureRecognizer:竞技选手 - acceptGesture: 胜者回调
- rejectGesture: 败者回调
- addPointer: 添加手势,启动追踪
- invokeCallback: 回调方法
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 拖动,由滑动的距离来判断
tap和doubleTap 由按的次数判断(hold)
tap和longPress 由按下的时间判断
父类和子类共用同一手势,优先子类