從源碼看flutter(五):GestureDetector篇

從源碼看flutter系列集合web

開篇

flutter的觸摸事件涉及到的東西比較多,本篇文章將會從 GestureDetector 做爲切入點,來對觸摸事件的實現作一個全面的瞭解bash

GestureDetector

class GestureDetector extends StatelessWidget {
  ...
  @override
  Widget build(BuildContext context) {
    final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
    ...
    return RawGestureDetector(
      gestures: gestures,
      behavior: behavior,
      excludeFromSemantics: excludeFromSemantics,
      child: child,
    );
  }
}
複製代碼

這裏,會將全部在 GestureDetector 設置的各類點擊事件,放在各個 GestureRecognizer 中,而後經過 GestureRecognizerFactory 對其進行封裝,存入 gestures 中,這裏 GestureRecognizerFactory 的實現仍是蠻有意思的,感興趣的小夥伴能夠去看一下它的源碼less

這裏,咱們先簡單瞭解一下 GestureRecognizeride

GestureRecognizer

abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableTreeMixin {
  ...
  void addPointer(PointerDownEvent event) { 
    ...
    if (isPointerAllowed(event)) {
      addAllowedPointer(event);
    } else {
      handleNonAllowedPointer(event);
    }
  }
  
  @protected
  void addAllowedPointer(PointerDownEvent event) { }
  ...
}
複製代碼

GestureRecognizer 主要提供了addPointer(downEvent) 用於接收 PointerDownEvent 對象,它會調用 addAllowedPointer(event) 方法,由子類去具體實現ui

GestureRecognizer 繼承於 GestureArenaMember,它只提供了兩個方法this

abstract class GestureArenaMember {
  void acceptGesture(int pointer);

  void rejectGesture(int pointer);
}
複製代碼

分別表示手勢競技場成員獲勝與失敗時會調用的方法,接下來咱們來看一下 RawGestureDetectorspa

RawGestureDetector

class RawGestureDetector extends StatefulWidget {
    ...
}

class RawGestureDetectorState extends State<RawGestureDetector> {
  ...
  void _handlePointerDown(PointerDownEvent event) {
    assert(_recognizers != null);
    for (final GestureRecognizer recognizer in _recognizers.values)
      recognizer.addPointer(event);
  }
  。。。
  @override
  Widget build(BuildContext context) {
    Widget result = Listener(
      onPointerDown: _handlePointerDown,
      behavior: widget.behavior ?? _defaultBehavior,
      child: widget.child,
    );
    if (!widget.excludeFromSemantics)
      result = _GestureSemantics(...);
    return result;
  }
  ...
}
複製代碼

RawGestureDetector 是一個 StatefulWidget ,它默認返回的是 Listener 對象。其中還有一個 _GestureSemantics 它用於實現一些具備語義化的手勢,好比長按展現 tooltip等,這裏咱們不用關注它。debug

這裏將以前經過 GestureDetector 傳入的 gestures 轉換成了 _recognizers,而且將他們放在了 _handlePointerDown(event) 方法裏經過 onPointerDown 傳給了 Listener 對象code

這裏還須要注意, _handlePointerDown(event) 中對 GestureRecognizer 對象進行了遍歷,並調用了他們的 addPointer(event) 方法orm

接下來咱們看一下 Listener

Listener

class Listener extends StatelessWidget {
  ...
  @override
  Widget build(BuildContext context) {
    Widget result = _child;
    if (onPointerEnter != null ||
        onPointerExit != null ||
        onPointerHover != null) {
      result = MouseRegion(...);
    }
    result = _PointerListener(
      onPointerDown: onPointerDown,
      onPointerUp: onPointerUp,
      onPointerMove: onPointerMove,
      onPointerCancel: onPointerCancel,
      onPointerSignal: onPointerSignal,
      behavior: behavior,
      child: result,
    );
    return result;
  }
}
複製代碼

Listener 是一個 StatelessWidget ,當傳入的部分事件不爲 null 時,好比懸停事件 onPointerHover,返回的就是 MouseRegion,它用來處理鼠標的輸入,默認返回 _PointerListener,這裏咱們只須要關注這個對象

從前面咱們能夠知道,咱們只對 Listener 傳入了 onPointerDown ,因此這裏傳遞給 _PointerListener 的其餘手勢回調都是 null

_PointerListener

class _PointerListener extends SingleChildRenderObjectWidget {
  ...
  @override
  RenderPointerListener createRenderObject(BuildContext context) {
    return RenderPointerListener(
      onPointerDown: onPointerDown,
      ...
    );
  }
  ...
}
複製代碼

_PointerListener 是一個 SingleChildRenderObjectWidget ,它對應的 RenderObjectRenderPointerListener ,咱們來看一下這個對象

RenderPointerListener

class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
  ...
  @override
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    ...
    if (onPointerDown != null && event is PointerDownEvent)
      return onPointerDown(event);
    ...
  }
}
複製代碼

以前咱們分析 RenderObject 時就知道, RenderObject 對象有一個 HitTestTarget 接口

abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget { ... }

abstract class HitTestTarget {
  factory HitTestTarget._() => null;

  void handleEvent(PointerEvent event, HitTestEntry entry);
}
複製代碼

這個接口提供了 handleEvent(...) 方法供 RenderObject 實現去處理各類手勢事件,最終基本上都是交給子類去實現這個方法。這裏的 RenderPointerListener 就是如此

這裏對於 GestureDetector 的總體結構有了一個初步的瞭解,而且沒法再往下深刻了,接下來咱們將從另一個切入點來看手勢事件。那就是手勢分發的起點

手勢分發流程

起點其實不難找,以前咱們就知道過了,runApp(...) 方法做爲flutter的入口,會對 WidgetsFlutterBinding 進行初始化,WidgetsFlutterBinding 混入了多個 Binding 對象,其中就有專門處理手勢的 GestureBinding,咱們看一下就知道了

手勢分發起點: GestureBinding

GestureBindinginitInstances() 中能夠看到以下內容

mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    window.onPointerDataPacket = _handlePointerDataPacket;
  }
  ...
}
複製代碼

這裏的 _handlePointerDataPacket 就是觸摸事件的起點,它會處理設備輸入的信息,將其轉換爲flutter中的手勢事件

_handlePointerDataPacket(...)

final Queue<PointerEvent> _pendingPointerEvents = Queue<PointerEvent>();
  
  void _handlePointerDataPacket(ui.PointerDataPacket packet) {
    _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
    if (!locked)
      _flushPointerEventQueue();
  }
複製代碼

手勢事件都被存放在了一個隊列中,以後會調用 _flushPointerEventQueue() 來進行手勢分發

_flushPointerEventQueue(...)

void _flushPointerEventQueue() {
    assert(!locked);
    while (_pendingPointerEvents.isNotEmpty)
      _handlePointerEvent(_pendingPointerEvents.removeFirst());
  }
複製代碼

這裏經過遍歷隊列,調用 _handlePointerEvent(event) 對各個event進行處理

_handlePointerEvent(event)

void _handlePointerEvent(PointerEvent event) {
    ...
    HitTestResult hitTestResult;
    if (event is PointerDownEvent || event is PointerSignalEvent) {
      ...
    } else if (event is PointerUpEvent || event is PointerCancelEvent) {
      ...
    } else if (event.down) {
      ...
    }
    ...
    if (hitTestResult != null ||
        event is PointerHoverEvent ||
        event is PointerAddedEvent ||
        event is PointerRemovedEvent) {
      dispatchEvent(event, hitTestResult);
    }
  }
複製代碼

當你在在屏幕上點擊一下,觸發的事件流程以下:

PointerHoverEvent -> PointerDownEvent -> PointerUpEvent

在屏幕上滑動時,流程以下

PointerHoverEvent -> PointerDownEvent -> ...PointerMoveEvent... -> PointerUpEvent

PointerHoverEvent 主要用於 flutter_web 中的鼠標懸停事件,這裏咱們不關注它,咱們能夠看一下,當觸發 PointerDownEvent 時,作了些什麼

final Map<int, HitTestResult> _hitTests = <int, HitTestResult>{};
  ...
    if (event is PointerDownEvent || event is PointerSignalEvent) {
      assert(!_hitTests.containsKey(event.pointer));
      hitTestResult = HitTestResult();
      hitTest(hitTestResult, event.position);
      if (event is PointerDownEvent) {
        _hitTests[event.pointer] = hitTestResult;
      }
      ...
    }
  ...
  @override // from HitTestable
  void hitTest(HitTestResult result, Offset position) {
    result.add(HitTestEntry(this));
  }
複製代碼

這裏的 hitTest(...) 由接口 HitTestable 提供,值得注意的是 GestureBindingRendererBinding 都實現了這個接口,能夠看一下在 RendererBinding 中的實現

///RendererBinding
  @override
  void hitTest(HitTestResult result, Offset position) {
    assert(renderView != null);
    renderView.hitTest(result, position: position);
    super.hitTest(result, position);
  }
複製代碼

這裏調用了 RenderViewhitTest(...) 方法,咱們已經知道過了,它是根 RenderObject 對象,進入它的 hitTest(...) 看一下

///RenderView
  bool hitTest(HitTestResult result, { Offset position }) {
    if (child != null)
      child.hitTest(BoxHitTestResult.wrap(result), position: position);
    result.add(HitTestEntry(this));
    return true;
  }
複製代碼

這裏的 childRenderBox 對象,在 RenderBox 中默認提供了 hitTest(...) 這個方法

///RenderBox
  bool hitTest(BoxHitTestResult result, { @required Offset position }) {
    ...
    if (_size.contains(position)) {
      if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
        result.add(BoxHitTestEntry(this, position));
        return true;
      }
    }
    return false;
  }
複製代碼

因此看到這裏你就能知道,當觸發了 PointerDownEvent 事件時,會調用全部 RenderBoxhitTest(...) 方法,將符合條件的對象放入到 BoxHitTestEntry(...) 中,再存入 HitTestResult 維護的 List<HitTestEntry> _path

HitTestEntry 接受的對象是 HitTestTarget ,上面咱們也提到過了,RenderObject 是實現了這個接口的。因此最後這些 RenderBox 會被存入 BoxHitTestEntry 先放入 List<HitTestEntry> 中,其次是存入了 HitTestEntryRenderView ,最後纔是 GestureBinding 對象

至於爲何要把這些對象收集起來放入 HitTestResult 呢?後面會逐步說明

HitTestResult 被建立後,會被存入GestureBinding 維護的 Map<int, HitTestResult> _hitTests 中,keyevent.pointer ,每觸發一次事件,pointer 的值都會+1,不會重複

接下來,會進入 dispatchEvent(event, hitTestResult) 方法,進行分發事件

dispatchEvent(...)

@override // from HitTestDispatcher
  void dispatchEvent(PointerEvent event, HitTestResult hitTestResult) {
    ...
    if (hitTestResult == null) {
      assert(event is PointerHoverEvent || event is PointerAddedEvent || event is PointerRemovedEvent);
      try {
        pointerRouter.route(event);
      } catch (exception, stack) {
        ...
      return;
    }
    for (final HitTestEntry entry in hitTestResult.path) {
      try {
        entry.target.handleEvent(event.transformed(entry.transform), entry);
      }
      ...
    }
  }
複製代碼

咱們主要關注 entry.target.handleEvent(...) 方法,這裏對以前在 hitTest(...) 中添加的各個實現了 HitTestTarget 接口的對象,調用其 handleEvent(...) 方法。而 hitTestResult.path 的順序咱們已經說過了,大體是下面這樣:

... -> RenderPointerListener -> ... -> RenderView -> GestureBinding

這裏會依次調用他們實現的 handleEvent(...) 。而事件的分發,就是經過這樣實現的!

開始手勢分發

咱們再次進入 RenderPointerListenerhandleEvent(...) 方法

RenderPointerListener

@override
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    ...
    if (onPointerDown != null && event is PointerDownEvent)
      return onPointerDown(event);
    ...
  }
複製代碼

這裏會調用 onPointerDown(event) ,經過前面的的瞭解,咱們知道這個方法就是 RawGestureDetectorState 傳入的 _handlePointerDown(...),再來看一遍

void _handlePointerDown(PointerDownEvent event) {
    for (final GestureRecognizer recognizer in _recognizers.values)
      recognizer.addPointer(event);
  }
複製代碼

addPointer(event) 的內容以前也說過了,最終都是調用 GestureRecognizer 提供的 addAllowedPointer(...) 方法。若是咱們在 GestureDetector 中設置了 onTapDown() 或者其餘點擊事件,這裏就會調用 TapGestureRecognizeraddPointer(...) 方法。咱們就先以它爲例,來看一下都作了些什麼

TapGestureRecognizer

咱們先簡單的看一下 TapGestureRecognizer 的繼承結構

TapGestureRecognizer -> BaseTapGestureRecognizer -> PrimaryPointerGestureRecognizer -> OneSequenceGestureRecognizer -> GestureRecognizer

這些類中,只有 GestureRecognizer 實現了 addPointer(...) 方法,只有 BaseTapGestureRecognizerPrimaryPointerGestureRecognizer 實現了 addAllowedPointer(...) 方法

能夠先來看一下 BaseTapGestureRecognizeraddAllowedPointer(...)

BaseTapGestureRecognizer

PointerDownEvent _down;
  ...
  @override
  void addAllowedPointer(PointerDownEvent event) {
    assert(event != null);
    if (state == GestureRecognizerState.ready) {
      _down = event;
    }
    if (_down != null) {
      super.addAllowedPointer(event);
    }
  }
複製代碼

這裏只是作了一個簡單的賦值,保存傳遞的 PointerDownEvent ,它會在 _reset() 中被置空

接下來,進入 PrimaryPointerGestureRecognizeraddAllowedPointer(...)

PrimaryPointerGestureRecognizer

@override
  void addAllowedPointer(PointerDownEvent event) {
    startTrackingPointer(event.pointer, event.transform);
    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));
    }
  }
複製代碼

這裏主要關注 startTrackingPointer(...) 方法,它在 OneSequenceGestureRecognizer 中實現

OneSequenceGestureRecognizer

@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);
  }
複製代碼

上面有兩個比較關鍵的地方,先看第一個 addRoute(...)

///PointerRouter
  final Map<int, Map<PointerRoute, Matrix4>> _routeMap = <int, Map<PointerRoute, Matrix4>>{};
  ...
  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;
  }
複製代碼

在這裏把 handleEvent(...) 方法做爲 PointerRoute 傳入到了 PointerRouter 中,它的做用,咱們後面就知道了。接下來看另外一個很是關鍵的地方:_entries[pointer] = _addPointerToArena(pointer)

GestureArenaTeam _team;
  ...
  GestureArenaEntry _addPointerToArena(int pointer) {
    ...
    return GestureBinding.instance.gestureArena.add(pointer, this);
  }
複製代碼

能夠看到,這裏將當前的 OneSequenceGestureRecognizer 做爲 GestureArenaMember 對象,傳入了 GestureBinding 中維護的 GestureArenaManager 內。也就是將須要競技的手勢成員,放入了手勢競技場內。

那麼到這裏,RenderPointerListenerhandleEvent(...) 就執行完畢了,接下來會執行 RenderViewhandleEvent(...),不過因爲它並無重寫這個方法,因此咱們會直接來到 GestureBindinghandleEvent(...)

GestureBinding

final PointerRouter pointerRouter = PointerRouter();
  ...
  @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);
    }
  }
複製代碼

這裏就要進入這個很是重要的 route(event) 方法了

PointerRouter

final Map<int, Map<PointerRoute, Matrix4>> _routeMap = <int, Map<PointerRoute, Matrix4>>{};
  final Map<PointerRoute, Matrix4> _globalRoutes = <PointerRoute, Matrix4>{};


  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);
  }
複製代碼

這裏有兩個 _dispatchEventToRoutes(...) 方法,後者執行的是 _globalRoutes 中的 PointerRoute,而 _globalRoutes 中存放的是其餘的全局手勢,好比用於隱藏 ToolTips 的手勢等,目前還不太瞭解它的其餘具體做用。

不過前面的 routes 就是咱們以前在 OneSequenceGestureRecognizerstartTrackingPointer(...) 中添加的各個 handleEvent(...) 方法

能夠看一下 _dispatchEventToRoutes(...)

void _dispatchEventToRoutes(
    PointerEvent event,
    Map<PointerRoute, Matrix4> referenceRoutes,
    Map<PointerRoute, Matrix4> copiedRoutes,
  ) {
    copiedRoutes.forEach((PointerRoute route, Matrix4 transform) {
      if (referenceRoutes.containsKey(route)) {
        _dispatch(event, route, transform);
      }
    });
  }
  ...
  void _dispatch(PointerEvent event, PointerRoute route, Matrix4 transform) {
    try {
      event = event.transformed(transform);
      route(event);
    }
    ...
  }
複製代碼

就是遍歷而後執行全部的 PointerRoute 方法,這裏的 PointerRoute 就是以前的各個 OneSequenceGestureRecognizer 中的 handleEvent(...)

這裏要注意不要把 OneSequenceGestureRecognizerhandleEvent(...)RenderPointerListenerhandleEvent(...) 混淆了

OneSequenceGestureRecognizer 提供了 handleEvent(...) ,交由子類去實現,咱們接着以前的點擊事件,實現它的點擊子類是 PrimaryPointerGestureRecognizer ;若是是拖拽事件的話,實現的子類就是 DragGestureRecognizer

PrimaryPointerGestureRecognizer

@override
  void handleEvent(PointerEvent event) {
    assert(state != GestureRecognizerState.ready);
    if (state == GestureRecognizerState.possible && event.pointer == primaryPointer) {
      final bool isPreAcceptSlopPastTolerance = ...;
      final bool isPostAcceptSlopPastTolerance = ...;

      if (event is PointerMoveEvent && (isPreAcceptSlopPastTolerance || isPostAcceptSlopPastTolerance)) {
        resolve(GestureDisposition.rejected);
        stopTrackingPointer(primaryPointer);
      } else {
        handlePrimaryPointer(event);
      }
    }
    stopTrackingIfPointerNoLongerDown(event);
  }
  
  @protected
  void handlePrimaryPointer(PointerEvent event);
複製代碼

當前事件是 PointerMoveEvent 時,會作一個判斷,若是必定時間內滑動的距離超過 18px 那麼就會進入第一個判斷中,該手勢會被拒絕掉,也就是從競技場中會被移除,此時會觸發 onTapCancel

咱們主要關心手勢有效時的方法 handlePrimaryPointer(event),能夠看到這個方法是交由子類去實現的,實現它的子類天然就是 BaseTapGestureRecognizer

BaseTapGestureRecognizer

@override
  void handlePrimaryPointer(PointerEvent event) {
    if (event is PointerUpEvent) {
      _up = event;
      _checkUp();
    } else if (event is PointerCancelEvent) {
      resolve(GestureDisposition.rejected);
      ...
      _reset();
    } else if (event.buttons != _down.buttons) {
      resolve(GestureDisposition.rejected);
      stopTrackingPointer(primaryPointer);
    }
  }
複製代碼

能夠看到,在這裏只對 PointerUpEventPointerCancelEvent 進行了處理,並無處理 PointerDownEvent ,這裏很天然的就能夠知道, PointerDownEvent 確定被放在了 GestureBindinghandleEvent(...) 的後面部分進行處理

不過咱們這裏仍是能夠先看一下 PointerUpEvent 是如何處理的,進入 _checkUp() 方法

@protected
  void handleTapUp({ PointerDownEvent down, PointerUpEvent up });
  ...
  void _checkUp() {
    ...
    handleTapUp(down: _down, up: _up);
    _reset();
  }
複製代碼

_checkUp() 中調用了 handleTapUp(...) 方法,它是一個交由子類實現的方法,而 BaseTapGestureRecognizer 的子類就是 TapGestureRecognizer

TapGestureRecognizer

@protected
  T invokeCallback<T>(String name, RecognizerCallback<T> callback, { String debugReport() }) {
    ...
      result = callback();
    ...
    return result;
  }

  @protected
  @override
  void handleTapUp({PointerDownEvent down, PointerUpEvent up}) {
    final TapUpDetails details = TapUpDetails(...);
    switch (down.buttons) {
      case kPrimaryButton:
        if (onTapUp != null)
          invokeCallback<void>('onTapUp', () => onTapUp(details));
        if (onTap != null)
          invokeCallback<void>('onTap', onTap);
        break;
        ...
      default:
    }
  }
複製代碼

能夠看到,最終經過 invokeCallback(...) 方法執行了傳入的方法,包括 onTapUponTap ,從這裏咱們就知道了,咱們最最經常使用的點擊事件,就是在 TapGestureRecognizerhandleTapUp(...) 中執行的。

TapGestureRecognizer 還有 handleTapDown(...) 用於執行 onTapDown ,它則是經過 BaseTapGestureRecognizer_checkDown() 調用

接下來咱們能夠回到 GestureBinding ,看看 PointerDownEvent 究竟是如何處理的

void handleEvent(PointerEvent event, HitTestEntry entry) {
    pointerRouter.route(event);
    if (event is PointerDownEvent) {
      gestureArena.close(event.pointer);
    }
    ...
  }
複製代碼

GestureArenaManager -> close(...)

void close(int pointer) {
    final _GestureArena state = _arenas[pointer];
    ...
    state.isOpen = false;
    ...
    _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);
    }
  }
複製代碼

close(...) 調用了 _tryToResolveArena(...) 方法,在這個方法中,處理了三種狀況

  • 若是競技場成員只有一個
  • 若是競技場沒有任何成員
  • 若是存在競技場的獲勝者

若是成員只有一個,那麼事件理所應當交給它處理;若是沒有成員就不說了;若是存在勝利者,交給勝利者處理也是正常的。顯然,close(...) 中並無對競技場的成員作一個競爭的處理,它只負責沒有點擊衝突的時候,也就是隻有一個點擊對象。這種狀況最後會經過調用它的 acceptGesture(...) 來觸發 onTapDown

咱們繼續看 close(...) 後面的方法

@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);
    } 
    ...
  }
複製代碼

GestureArenaManager -> sweep(...)

void sweep(int pointer) {
    ...
    if (state.members.isNotEmpty) {
      ...
      state.members.first.acceptGesture(pointer);
      ...
      for (int i = 1; i < state.members.length; i++)
        state.members[i].rejectGesture(pointer);
    }
  }
複製代碼

看看這個方法,多麼簡單粗暴,直接肯定競技場成員中的第一個爲獲勝者!根據前面咱們說過的 hintTest(...) 的添加順序,能夠知道,RenderBox數最下層的對象是最早被添加到列表中的

因此這裏的第一個成員,就是最下層的對象,在屏幕的顯示中,它就是最裏層的元素,因此若是像下面這樣,爲兩個顏色塊設置點擊事件的話,只有紅色的會生效

咱們能夠簡單的看一下 acceptGesture(...) 作了些什麼,這裏會進入 BaseTapGestureRecognizeracceptGesture(...)

///BaseTapGestureRecognizer
  @override
  void acceptGesture(int pointer) {
    super.acceptGesture(pointer);
    if (pointer == primaryPointer) {
      _checkDown();
      _wonArenaForPrimaryPointer = true;
      _checkUp();
    }
  }
複製代碼

能夠看到,這裏的 _checkDown() 中就處理了 onTapDown 事件,後面跟着了一個 _checkUp() 用於對 onTapUponTap 的處理,若是競技場成員只有一個,這裏的 _checkUp() 不會生效

到這裏,關於點擊事件的整個流程咱們都清楚了,能夠分爲下面兩種狀況

  • 競技場只有一個成員時:在 GestureArenaManagerclose(...) 中完成 onTapDown 的調用,此時事件爲 PointDownEvent; 在 PointerRouterroute(...) 方法完成對 onTapUponTap 的調用,此時事件爲 PointUpEvent
  • 競技場有多個成員時:在 GestureArenaManagersweep(...) 方法先完成對 onTapDown 的調用,後完成對 onTapUponTap 的調用,此時事件爲 PointUpEvent

接下來可能你會產生疑問了,若是上面兩個顏色塊監聽的是相同的滑動事件,在競技場中他們又是如何處理的呢?

下面就來簡單的看一下,以 PanGestureRecognizer 爲例

PanGestureRecognizer

簡單的看一下它的結構

PanGestureRecognizer -> DragGestureRecognizer -> OneSequenceGestureRecognizer -> GestureRecognizer

基本上和拖拽相關的核心邏輯都在 DragGestureRecognizer 中了

從以前咱們的流程就知道,會先走 addAllowedPointer(...) 方法,以後經過在 GestureBindinghandleEvent(...) 中執行 PointerRouterroute(...) 來走 GestureRecognizerhandleEvent(...) 方法

因此咱們先看 DragGestureRecognizeraddAllowedPointer(...)

@override
  void addAllowedPointer(PointerEvent event) {
    startTrackingPointer(event.pointer, event.transform);
    _velocityTrackers[event.pointer] = VelocityTracker();
    if (_state == _DragState.ready) {
      _state = _DragState.possible;
      ...
      _checkDown();
    } else if (_state == _DragState.accepted) {
      resolve(GestureDisposition.accepted);
    }
  }
複製代碼

仍是會先在 startTrackingPointer(...)handleEvent(...) 加入 PointerRouter ,而後把當前對象加入競技場。

接着經過 _checkDown() 執行了 onDown 方法,對於 DragGestureRecognizer 它就是 onPanDown

以上是收到 onPointDownEvent 時的事件,由於是拖拽,接下來會收到 onPointMoveEvent 事件

再看它的 handleEvent(...)

@override
  void handleEvent(PointerEvent event) {
    ...

    if (event is PointerMoveEvent) {
      ...
      if (_state == _DragState.accepted) {
        ...
      } else {
        ...
        if (_hasSufficientGlobalDistanceToAccept)
          resolve(GestureDisposition.accepted);
      }
    }
    ...
  }
複製代碼

其中 _hasSufficientGlobalDistanceToAccept 是交由子類去實現的方法,用於判斷滑動距離是否有效,默認大於36個像素就有效;若是有效,就會進入 resolve(...) 方法,它最終會調用到 GestureArenaManager_resolveInFavorOf(...)

而這個方法,對於傳入的 GestureArenaMember 對象,直接斷定其爲勝出者,並將其餘競技場成員清除掉。而這裏傳入的對象和咱們以前再點擊事件中的同樣,都是樹結構中最下層的對象,也就是屏幕上最裏層的元素

因此這裏的滑動事件在競技場中的處理就是這樣了。而咱們本篇的內容也即將結束

總結

手勢的分發流程大體以下:

  • 觸發手勢: flutter接受到由底層傳來的觸摸事件通知,它會觸發 GestureBinding_handlePointerDataPacket(...) 方法,flutter再這個方法中對傳來的數據進行轉換,變成flutter中適用的格式
  • HitTestTarget對象收集: 經過 hitTest(...) 方法,將 RenderObject樹中符合條件的 RenderBox 對象添加到 HitTestResult 中,添加順序是由底至上的,最上層的兩個對象分別是 GestureBindingRenderView ,這些對象都實現了 HitTestTarget 接口,也就是說他們都具有 handlerEvent(...) 方法
  • 事件分發: 在 dispatchEvent(...) 中,經過遍歷以前添加的對象,調用他們的 handlerEvent(...) 方法來進行事件的分發
  • GestureRecognizer對象收集: 咱們的 GestureDetector 對應的 RenderPointerListener 會進行事件處理,在收到 PointDownEvent 事件時,會將全部 GestureDetector 中註冊的 GestureRecognizer 對象的 handlerEvent 方法做爲 PointerRoute 傳入 PointerRouter ,並將該對象放入 GestureArenaManager 維護的競技場
  • 事件處理: 執行到最外層,也就是 GestureBindinghandleEvent 中,在這裏從競技場選出最終處理手勢的 GestureRecognizer 對象,而後進行手勢處理

ps:以上分析都是基於單點觸摸事件,還沒有對多點觸摸事件進行分析

最後,說明一下,單點觸摸事件的核心並非所謂的手勢競技場,由於根本就沒有一個真正的競技過程。最終都是直接選擇最下層的 GestureDetector 做爲手勢的處理者,單點觸摸事件的核心實際上是這些競技場成員被添加到競技場中的順序——是由底至上的順序

相關文章
相關標籤/搜索