Flutter原理篇:GestureDetector手势的运行以及冲突的原理

上一篇我们写到了《FlutterFlutter原理篇:事件机制传播与响应机制与HitTestBehavior的介绍》忘记了的同学可以回过头看一看点击事件是怎么回事的,今天我们来讲讲Flutter手势GestureDetector到底是怎么回事,以及来分析一下他背后的运行原理

在我们讨论他的运行原理之前,我们先来看看他是怎么使用的

import 'package:flutter/material.dart';

class TestGestureDetector extends StatelessWidget{
  @override
  Widget build(BuildContext context) {

    return GestureDetector(
      onTapUp: (x)=>print("1"),
      child: Container(
        width:200,
        height: 200,
        color: Colors.red,
      ),
    );
  }
}

看起来还是蛮简单的嘛,好像与Listener使用差不多啊,确实是差不多,而且他的原理里面就用到了Listener,要不我们去看看他是怎么一回事
其实他继承于StatelessWidget,那么我们直接看他的build方法看看:

@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;
      },
    );
  }
    //省略部分代码

return RawGestureDetector(
  gestures: gestures,
  behavior: behavior,
  excludeFromSemantics: excludeFromSemantics,
  child: child,
);

}

大家要知道一点的就是一个GestureDetector可能对应好多个手势识别器Recognizer,因为我们的代码里面只传递了onTapUp事件所以我们就只看TapGestureRecognizer即可,首先第一行初始化了Map<Type, GestureRecognizerFactory> gestures这么一个Map,这个map的key是GestureRecognizer的类型,他的value存的是GestureRecognizer对应的Factory

GestureRecognizerFactory

@optionalTypeArgs
abstract class GestureRecognizerFactory<T extends GestureRecognizer> {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const GestureRecognizerFactory();

  /// Must return an instance of T.
  T constructor();

  /// Must configure the given instance (which will have been created by
  /// `constructor`).
  ///
  /// This normally means setting the callbacks.
  void initializer(T instance);

  bool _debugAssertTypeMatches(Type type) {
    assert(type == T, 'GestureRecognizerFactory of type $T was used where type $type was specified.');
    return true;
  }
}

class GestureRecognizerFactoryWithHandlers<T extends GestureRecognizer> extends GestureRecognizerFactory<T> {
  /// Creates a gesture recognizer factory with the given callbacks.
  ///
  /// The arguments must not be null.
  const GestureRecognizerFactoryWithHandlers(this._constructor, this._initializer)
    : assert(_constructor != null),
      assert(_initializer != null);

  final GestureRecognizerFactoryConstructor<T> _constructor;

  final GestureRecognizerFactoryInitializer<T> _initializer;

  @override
  T constructor() => _constructor();

  @override
  void initializer(T instance) => _initializer(instance);
}

_constructor与_initializer对应的类型如下:

/// Signature for closures that implement [GestureRecognizerFactory.constructor].
typedef GestureRecognizerFactoryConstructor<T extends GestureRecognizer> = T Function();

/// Signature for closures that implement [GestureRecognizerFactory.initializer].,
typedef GestureRecognizerFactoryInitializer<T extends GestureRecognizer> = void Function(T instance);

代码很简单不赘述了,我们需要知道的gestures[TapGestureRecognizer]存放的是TapGestureRecognizer的构造函数与初始化函数即可,其中我们写的onTapUp函数就在里面,我们再来看看函数返回的是一个RawGestureDetector,我们去看看他,其实他是一个StatefullWidget,他在初始化State的时候会调用上面提到的构造器以及初始化函数去生成Map<Type, GestureRecognizer>? _recognizers对象,他的key为识别器,value为识别器对象

RawGestureDetectorState.initState

  @override
  void initState() {
    super.initState();
    _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
    _syncAll(widget.gestures);
  }

RawGestureDetectorState._syncAll

 void _syncAll(Map<Type, GestureRecognizerFactory> gestures) {
    assert(_recognizers != null);
    final Map<Type, GestureRecognizer> oldRecognizers = _recognizers!;
    _recognizers = <Type, GestureRecognizer>{};
    for (final Type type in gestures.keys) {
      assert(gestures[type] != null);
      assert(gestures[type]!._debugAssertTypeMatches(type));
      assert(!_recognizers!.containsKey(type));
      _recognizers![type] = oldRecognizers[type] ?? gestures[type]!.constructor();
      assert(_recognizers![type].runtimeType == type, 'GestureRecognizerFactory of type $type created a GestureRecognizer of type ${_recognizers![type].runtimeType}. The GestureRecognizerFactory must be specialized with the type of the class that it returns from its constructor method.');
      gestures[type]!.initializer(_recognizers![type]!);
    }
    for (final Type type in oldRecognizers.keys) {
      if (!_recognizers!.containsKey(type))
        oldRecognizers[type]!.dispose();
    }
  }

主要是:
_recognizers![type] = oldRecognizers[type] ?? gestures[type]!.constructor();
gestures[type]!.initializer(_recognizers![type]!);
这两行构造和初始化对象

我们接着看他对应的build函数:

RawGestureDetectorState.build

@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;
}

OK,这里直接是使用到了Listener来构造的Widget,并且指定了onPointerDown函数,由于上一篇我们说到了Listener的运行原理,在被命中的情况下添加进队列,再在事件分发的时候执行handleEvent方法然后继而会执行我们添加的onPointerDown方法,我们再来看看这个方法:

void _handlePointerDown(PointerDownEvent event) {
  assert(_recognizers != null);
  for (final GestureRecognizer recognizer in _recognizers!.values)
    recognizer.addPointer(event);
}

这里貌似是把他加进了recognizer这个里面,我们看看这个addPointer方法

GestureRecognizer.addPointer

  void addPointer(PointerDownEvent event) {
    _pointerToKind[event.pointer] = event.kind;
    if (isPointerAllowed(event)) {
      addAllowedPointer(event);
    } else {
      handleNonAllowedPointer(event);
    }
  }

BaseTapGestureRecognizer.addAllowedPointer

 @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);
    }
  }

_reset 做了一些重置的操作

PrimaryPointerGestureRecognizer.addAllowedPointer

 @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));
    }
  }

第一句是调用了父类的方法,下面是记录了一些offset作为后面点击判断使用的

OneSequenceGestureRecognizer.addAllowedPointer

  @override
  @protected
  void addAllowedPointer(PointerDownEvent event) {
    startTrackingPointer(event.pointer, event.transform);
  }

TapGestureRecognizer间接继承于OneSequenceGestureRecognizer

OneSequenceGestureRecognizer.startTrackingPointer

  @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);
  }

这里最重要的是第一句

PointerRouter.addRoute

 void addRoute(int pointer, PointerRoute route, [Matrix4? transform]) {
    final Map<PointerRoute, Matrix4?> routes = _routeMap.putIfAbsent(
      pointer,
      () => <PointerRoute, Matrix4?>{},
    );
    assert(!routes.containsKey(route));
    routes[route] = transform;
  }

这里面生成了一个Map对象routes他的key为pointer(也就是pointer指针),他的value为一个类型为<PointerRoute, Matrix4?>的Map,然后把这个Map的key赋值为PointerRoute route,value赋值为传过来的矩阵transform(我们这里没有使用到矩阵不用管它,这里默认是一个单位矩阵),这个PointerRoute route其实是上面传过来的handleEvent函数,我们看看他

PrimaryPointerGestureRecognizer.handleEvent

  @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);
  }

OK初始化完了,这里经过了很多步骤只是routes[route] = transform把他存起来了而已,那么怎么去调用呢,这个就要说到了事件传递的时候,大家还记得我们在事件测试命中的时候那个函数吗?

RendererBinding.hitTest

  @override
  void hitTest(HitTestResult result, Offset position) {
    assert(renderView != null);
    assert(result != null);
    assert(position != null);
    renderView.hitTest(result, position: position);
    super.hitTest(result, position);  //RendererBinding继承于GestureBinding
  }

RendererBinding继承于GestureBinding,最后一句调用的是GestureBinding.hitTest

GestureBinding.hitTest

  @override // from HitTestable
  void hitTest(HitTestResult result, Offset position) {
    result.add(HitTestEntry(this));
  }

这里把GestureBinding对象加入到了命中测试队列,等待事件传递的时候就会执行他的handleEvent方法:

GestureBinding.handleEvent

  @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.route(event);,这里面我们留一个彩蛋,因为这里事件类型为PointerDownEvent的时候不会直接进行我们的函数回调,我们要等到PointerDownUp的时候才会正确执行,但是我们先往下看,因为我们先忽略一些细节只看回调的处理

  void route(PointerEvent event) {
    final Map<PointerRoute, Matrix4?>? routes = _routeMap[event.pointer];
    final Map<PointerRoute, Matrix4?> copiedGlobalRoutes = Map<PointerRoute, Matrix4?>.from(_globalRoutes);
    if (routes != null) {
      _dispatchEventToRoutes(
        event,
        routes,
        Map<PointerRoute, Matrix4?>.from(routes),
      );
    }
    _dispatchEventToRoutes(event, _globalRoutes, copiedGlobalRoutes);
  }

第一句话就是把_routeMap的value拿出来,key为pointer,

  /// Unique identifier for the pointer, not reused. Changes for each new
  /// pointer down event.
  final int pointer;

注意这个pointer是唯一的,不可重用的,但是每次pointer down的时候会改变,也就是说一次事件的down,move,up其实这个是一样的,所以我在down注册的key可以在up的时候取出来

最后面回到这里来:

PointerRouter._dispatch

  @pragma('vm:notify-debugger-on-exception')
  void _dispatch(PointerEvent event, PointerRoute route, Matrix4? transform) {
    try {
      event = event.transformed(transform);
      route(event);
    } catch (exception, stack) {
      InformationCollector? collector;
      assert(() {
        collector = () sync* {
          yield DiagnosticsProperty<PointerRouter>('router', this, level: DiagnosticLevel.debug);
          yield DiagnosticsProperty<PointerRoute>('route', route, level: DiagnosticLevel.debug);
          yield DiagnosticsProperty<PointerEvent>('event', event, level: DiagnosticLevel.debug);
        };
        return true;
      }());
      FlutterError.reportError(FlutterErrorDetails(
        exception: exception,
        stack: stack,
        library: 'gesture library',
        context: ErrorDescription('while routing a pointer event'),
        informationCollector: collector,
      ));
    }
  }

route(event);这一句话就是直接调用了上面那个函数,我们再看一遍:

PrimaryPointerGestureRecognizer.handleEvent

  @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);
  }

这里最主要的就是handlePrimaryPointer

BaseTapGestureRecognizer.handlePrimaryPointer

  @override
  void handlePrimaryPointer(PointerEvent event) {
    if (event is PointerUpEvent) {
      _up = event;
      _checkUp();
    } else if (event is PointerCancelEvent) {
      resolve(GestureDisposition.rejected);
      if (_sentTapDown) {
        _checkCancel(event, '');
      }
      _reset();
    } else if (event.buttons != _down!.buttons) {
      resolve(GestureDisposition.rejected);
      stopTrackingPointer(primaryPointer!);
    }
  }

假设我们的是PointerUpEvent事件,这里直接调用_checkUp函数:

 void _checkUp() {
    if (!_wonArenaForPrimaryPointer || _up == null) {
      return;
    }
    assert(_up!.pointer == _down!.pointer);
    handleTapUp(down: _down!, up: _up!);
    _reset();
  }

因为这个函数里面_wonArenaForPrimaryPointer这个变量是为false,_up变量为null,但是在上面的GestureBinding.handleEvent(Event为PointerDownEvent)的gestureArena.close(event.pointer);中会把_wonArenaForPrimaryPointer变量设置为true,我们简单的看一下:

GestureArenaManager.close

  /// Prevents new members from entering the arena.
  ///
  /// Called after the framework has finished dispatching the pointer down 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;
    assert(_debugLogDiagnostic(pointer, 'Closing', state));
    _tryToResolveArena(pointer, state);
  }
  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!);
    }
  }

这里面使用了这个微队列任务在做这个置位操作:scheduleMicrotask(() => _resolveByDefault(pointer, state)); 老实说至于为什么使用微队列来做这个操作我一下还没想通,直接调用不行?

  void _resolveInFavorOf(int pointer, _GestureArena state, GestureArenaMember member) {
    assert(state == _arenas[pointer]);
    assert(state != null);
    assert(state.eagerWinner == null || state.eagerWinner == member);
    assert(!state.isOpen);
    _arenas.remove(pointer);
    for (final GestureArenaMember rejectedMember in state.members) {
      if (rejectedMember != member)
        rejectedMember.rejectGesture(pointer);
    }
    member.acceptGesture(pointer);
  }

BaseTapGestureRecognizer.acceptGesture

  @override
  void acceptGesture(int pointer) {
    super.acceptGesture(pointer);
    if (pointer == primaryPointer) {
      _checkDown();
      _wonArenaForPrimaryPointer = true;  //注意这一句
      _checkUp();                //这里_checkUp里面的_up为null不会直接往下执行
    }
  }

但是到Event为PointerUpEvent的时候再回到handlePrimaryPointer这个函数的时候_wonArenaForPrimaryPointer已经为true,_up变量也会有值,最后还是会回到这个_checkUp函数里面调用handleTapUp:

TapGestureRecognizer.handleTapUp

  @override
  void handleTapUp({ required PointerDownEvent down, required PointerUpEvent up}) {
    final TapUpDetails details = TapUpDetails(
      kind: up.kind,
      globalPosition: up.position,
      localPosition: up.localPosition,
    );
    switch (down.buttons) {
      case kPrimaryButton:
        if (onTapUp != null)
          invokeCallback<void>('onTapUp', () => onTapUp!(details));
        if (onTap != null)
          invokeCallback<void>('onTap', onTap!);
        break;
      case kSecondaryButton:
        if (onSecondaryTapUp != null)
          invokeCallback<void>('onSecondaryTapUp', () => onSecondaryTapUp!(details));
        if (onSecondaryTap != null)
          invokeCallback<void>('onSecondaryTap', () => onSecondaryTap!());
        break;
      case kTertiaryButton:
        if (onTertiaryTapUp != null)
          invokeCallback<void>('onTertiaryTapUp', () => onTertiaryTapUp!(details));
        break;
      default:
    }
  }

这里主要调用的是invokeCallback<void>('onTapUp', () => onTapUp!(details));

也就是运行的是TapGestureRecognizer的onTapUp方法也就是运行的是我们再DEMO中添加的回调方法

好了,讲到这里你已经对于手势的基本运行原理已经很清楚了,其实他就是基于Listener作为监听,利用了事件传播机制作为回调而已(因为默认他就是可以命中的),理解了源码以后就是这么简单


下面我们再来说第二个话题:
手势的冲突以及它背后的原理什么什么,让我们先来看看一个例子:
import 'package:flutter/material.dart';

class TestGestureDetector2 extends StatelessWidget{
  @override
  Widget build(BuildContext context) {

    return GestureDetector( 
      onTapUp: (x)=>print("2"), 
      child: Container(
        width:200,
        height: 200,
        color: Colors.red,
        alignment: Alignment.center,
        child: GestureDetector( 
          onTapUp: (x)=>print("1"), 
          child: Container(
            width: 100,
            height: 100,
            color: Colors.grey,
          ),
        ),
      ),
    );
  }
}

上面的例子是两个GestureDetector手势,嵌套组成父子组件,这个时候我们点击子组件的时候你会发现打印log里面只会打印1,父组件的手势好像没有起作用,这个就是我们今天要说的手势之间的冲突,为什么会有这个冲突呢,让我再回转头来看看上面事件传递机制的时候还有一个细节我们忽略了:

  @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);
  }

第一行在命中的时候把handleEvent加入到了routes里面这个我们上面已经分析过了,我们接着看下面一句,_entries[pointer] = _addPointerToArena(pointer);

OneSequenceGestureRecognizer._addPointerToArena

  GestureArenaEntry _addPointerToArena(int pointer) {
    if (_team != null)
      return _team!.add(pointer, this);
    return GestureBinding.instance!.gestureArena.add(pointer, this);
  }

继续看最后一行:

GestureArenaManager.add

  /// Adds a new member (e.g., gesture recognizer) to the arena.
  GestureArenaEntry add(int pointer, GestureArenaMember member) {
    final _GestureArena state = _arenas.putIfAbsent(pointer, () {
      assert(_debugLogDiagnostic(pointer, '★ Opening new gesture arena.'));
      return _GestureArena();
    });
    state.add(member);
    assert(_debugLogDiagnostic(pointer, 'Adding: $member'));
    return GestureArenaEntry._(this, pointer, member);
  }

_GestureArena.add

  void add(GestureArenaMember member) {
    assert(isOpen);
    members.add(member);
  }

貌似多了几个我们没见过的对象,我来给大家解释一下:

  • GestureArenaManager :这个是整个手势冲突的管理类,他里面有一个final Map<int, _GestureArena> _arenas Map字段专门存放的是key为pointer(上面我们解释过了pointer,也就是每次一个完整事件的唯一标识),value为_GestureArena的对象

  • _GestureArena:这里面是专门存放了一个字段 final List<GestureArenaMember> members = <GestureArenaMember>[]; ,管理所有的GestureArenaMember

  • GestureArenaMember:有两个抽象方法(这两个方法关系到了到底是接纳这个手势,还是拒绝这个手势),很多类直接或者间接继承了他,比如我上面DEMO的TapGestureRecognizer:

abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableTreeMixin {}
abstract class OneSequenceGestureRecognizer extends GestureRecognizer {}
abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecognizer {}
abstract class BaseTapGestureRecognizer extends PrimaryPointerGestureRecognizer {}
class TapGestureRecognizer extends BaseTapGestureRecognizer {}
abstract class GestureArenaMember {
  /// Called when this member wins the arena for the given pointer id.
  void acceptGesture(int pointer);

  /// Called when this member loses the arena for the given pointer id.
  void rejectGesture(int pointer);
}

说道这里我们明白了就是把我们手势识别器对象TapGestureRecognizer存了起来,简单来说就是TapGestureRecognizer存在_GestureArena里面,_GestureArena存在_arenas里面

大家再回过头来看看我们上面提到的彩蛋,我们再来看看里面的细节,首先是这里:

GestureBinding.handleEvent

  @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.route(event);事件一开始是PointerDownEvent的时候并不会直接进行调用,因为上面提到的_wonArenaForPrimaryPointer变量为false,但是也不会在 gestureArena.close(event.pointer); 这个里面进行置位,因为这个时候我们有两个长度的members单位(也就是两个TapGestureRecognizer),不满足判断 state.members.length == 1 为false

  void _tryToResolveArena(int pointer, _GestureArena state) {
    assert(_arenas[pointer] == state);
    assert(!state.isOpen);
    if (state.members.length == 1) {    //这一句为false
      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); 这一句

  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);      //把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);
    }
  }

第一句话先把pointer对应的_GestureArena拿出来,然后 _arenas.remove(pointer); 把pointer里面所有的识别器全部移除,然后 state.members.first.acceptGesture(pointer); 这一句取出第一个识别器调用它的acceptGesture方法,再把调用rejectGesture方法里面的拒绝其他识别器的响应,也就是竞争失败了

  @override
  void rejectGesture(int pointer) {
    super.rejectGesture(pointer);
    if (pointer == primaryPointer) {
      // Another gesture won the arena.
      assert(state != GestureRecognizerState.possible);
      if (_sentTapDown)
        _checkCancel(null, 'forced');
      _reset();
    }
  }

那么问题就来了,谁才是第一个members里面的TapGestureRecognizer对象呢,这个就要提到了我们原原来说过的事件命中机制的原理,child优先添加进入命中队列,优先得到事件分发,这么的话我们的子节点的识别器TapGestureRecognizer会优先调用_handlePointerDown也就是优先调用_addPointerToArena方法加入到了members里面,所以我们最后看看子节点调用的acceptGesture函数:

  @override
  void acceptGesture(int pointer) {
    super.acceptGesture(pointer);
    if (pointer == primaryPointer) {
      _checkDown();
      _wonArenaForPrimaryPointer = true;
      _checkUp();
    }
  }

_wonArenaForPrimaryPointer = true; 先置位,然后调用_checkUp函数去执行handleTapUp继而执行我们TapGestureRecognizer里面的onTapUp函数,所以打印就只会输出子组件打印1

好了,我们总算明白了如果是父子控件都注册了GestureDetector的话,那么只有子组件的会得到响应,原因就是上面提到的这一套竞争机制,不过老实说我总觉得这套机制还可以再完善一下,总感觉不够那么完美,这里我们介绍了一下两个onTapUp事件的冲突,其实多个识别器拖动之间也会有冲突,这里就不详解了,相信大家根据这套机制应该可以自己去弄明白

最后说一说如果真遇到了父子控件都需要注册up回调的时候怎么去解决呢,就是要打破这套竞争机制,最简单的办法就是利用Listener替代父GestureDetector,因为直接使用Listener一开始就不会有这套竞争的规则了

import 'package:flutter/material.dart';

class TestGestureDetector3 extends StatelessWidget{
  @override
  Widget build(BuildContext context) {

    return Scaffold(
        body: Listener( 
        onPointerUp: (x)=>print("2") ,
        child: Container(
          width:200,
          height: 200,
          color: Colors.red,
          alignment: Alignment.center,
          child: GestureDetector( //GestureDetector1
            onTapUp: (x)=>print("1"), 
            child: Container(
              width: 50,
              height: 50,
              color: Colors.grey,
            ),
          ),
        ),
      ),
    );
  }
}

好了今天就到这里了,我们讲解手势运行原理以及冲突的原理已经到了尾声了,如果有喜欢的小伙伴欢迎给我点赞加留言,😁你的点赞加关注是我写作持续的动力,谢谢···

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271