深入理解Flutter的GestureDetector組件

引言

上一篇文章 深入理解Flutter的Listener組件
介紹了觸控事件的監(jiān)聽原理,讓我們對Flutter中觸摸事件的傳遞過程有了進(jìn)一步的認(rèn)識。

今天我們學(xué)習(xí)一下手勢識別組件GestureDetector的原理。GestureDetector的內(nèi)部實(shí)現(xiàn)使用的是Listener組件,如果對Listener還不太熟悉,可以先了解一下Listener的原理。

源碼解析

一、GestureDetector是Listener的封裝

GestureDector是一個無狀態(tài)組件,它的build方法如下所示。

class GestureDetector extends StatelessWidget {
    ...省略
    
    Widget build(BuildContext context) {
        ...省略
        return RawGestureDetector(
          gestures: gestures,
          behavior: behavior,
          excludeFromSemantics: excludeFromSemantics,
          child: child,
        );
    }
}

build方法直接返回了RawGestureDetector組件,說明GestureDetector是由子組件RawGestureDetector構(gòu)成的。而RawGestureDetector是一個有狀態(tài)組件,它的Statebuild方法如下所示。

class RawGestureDetector extends StatefulWidget {
  @override
  RawGestureDetectorState createState() => RawGestureDetectorState();
}

class RawGestureDetectorState extends State<RawGestureDetector> {
    ...省略
    
    @override
    Widget build(BuildContext context) {
        Widget result = Listener(
          onPointerDown: _handlePointerDown,
          behavior: widget.behavior ?? _defaultBehavior,
          child: widget.child,
        );
        if (!widget.excludeFromSemantics)
          result = _GestureSemantics(owner: this, child: result);
        return result;
    }
}

build方法里面返回了Listener組件,這也證明了上面的結(jié)論:

GestureDetector的內(nèi)部實(shí)現(xiàn)使用的是Listener組件。

二、GestureDetector的實(shí)現(xiàn)原理

相比于ListenerGestureDetector有自己的屬性,如onDoubleTaponLongPress、onHorizontalDragStartonVerticalDragStart等。

其實(shí)說到底,這些屬性也是由ListeneronPointerDown、onPointerMove、onPointerUp這三個屬性封裝而成的。

重新看一下RawGestureDetectorStatebuild方法。

@override
Widget build(BuildContext context) {
    Widget result = Listener(
      onPointerDown: _handlePointerDown,
      behavior: widget.behavior ?? _defaultBehavior,
      child: widget.child,
    );
    if (!widget.excludeFromSemantics)
      result = _GestureSemantics(owner: this, child: result);
    return result;
}

Listener組件的child屬性是由GestureDector傳遞進(jìn)來的,也就是說GestureDector自上而下的Widget構(gòu)成如下圖所示。

image

從之前對Listener組件的分析可知,Listener中的Child的觸摸事件由onPointerDown、onPointerMoveonPointerUp等屬性值決定。

這里Listener屬性值為_handlePointerDown,它是一個方法。

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

該方法遍歷了_recognizers里面的值(值類型為GestureRecognizer),_recognizers又是在_syncAll方法中賦值的。

void _syncAll(Map<Type, GestureRecognizerFactory> gestures) {
    final Map<Type, GestureRecognizer> oldRecognizers = _recognizers;
    _recognizers = <Type, GestureRecognizer>{};
    for (Type type in gestures.keys) {
      _recognizers[type] = oldRecognizers[type] ?? gestures[type].constructor(); //重要方法
      gestures[type].initializer(_recognizers[type]); //重要方法
    }
    for (Type type in oldRecognizers.keys) {
      if (!_recognizers.containsKey(type))
        oldRecognizers[type].dispose();
    }
}

_syncAll方法會將原有的_recognizers保存下來,然后遍歷參數(shù)中的gestures,若原有的_recognizers有該手勢類型對象,則使用,否則調(diào)用gestures[type]constructor方法。然后繼續(xù)調(diào)用gestures[type]initializer方法。記住constructorinitializer這兩個方法,后面的分析需要用到。

_syncAll方法在兩處地方被調(diào)用,分別是initStatedidUpdateWidget方法。

@override
void initState() {
    super.initState();
    _syncAll(widget.gestures);
}

@override
void didUpdateWidget(RawGestureDetector oldWidget) {
    super.didUpdateWidget(oldWidget);
    _syncAll(widget.gestures);
}

組件初始化會調(diào)用initState方法,并傳遞了widgetgestures屬性,這里的widget是指RawGestureDetector組件。

讓我們再回過頭來看GestureDectorbuild方法,如下所示。

@override
Widget build(BuildContext context) {
    final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};

    if (
      onTapDown != null ||
      onTapUp != null ||
      onTap != null ||
      onTapCancel != null ||
      onSecondaryTapDown != null ||
      onSecondaryTapUp != null ||
      onSecondaryTapCancel != null
    ) {
      gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
        () => TapGestureRecognizer(debugOwner: this),
        (TapGestureRecognizer instance) {
          instance
            ..onTapDown = onTapDown
            ..onTapUp = onTapUp
            ..onTap = onTap
            ..onTapCancel = onTapCancel
            ..onSecondaryTapDown = onSecondaryTapDown
            ..onSecondaryTapUp = onSecondaryTapUp
            ..onSecondaryTapCancel = onSecondaryTapCancel;
        },
      );
    }

    if (onDoubleTap != null) {
      gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
        () => DoubleTapGestureRecognizer(debugOwner: this),
        (DoubleTapGestureRecognizer instance) {
          instance
            ..onDoubleTap = onDoubleTap;
        },
      );
    }
    if (onLongPress != null ||
        onLongPressUp != null ||
        onLongPressStart != null ||
        onLongPressMoveUpdate != null ||
        onLongPressEnd != null) {
      gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
        () => LongPressGestureRecognizer(debugOwner: this),
        (LongPressGestureRecognizer instance) {
          instance
            ..onLongPress = onLongPress
            ..onLongPressStart = onLongPressStart
            ..onLongPressMoveUpdate = onLongPressMoveUpdate
            ..onLongPressEnd =onLongPressEnd
            ..onLongPressUp = onLongPressUp;
        },
      );
    }
    ...省略
    return RawGestureDetector(
      gestures: gestures,
      behavior: behavior,
      excludeFromSemantics: excludeFromSemantics,
      child: child,
    );
}

首先初始化了gestures,并且對于每一種手勢族都定義了一種類型。

1、TapGestureRecognizer手勢族里面就包含了onTapDown、onTapUponTap、onTapCancel、onSecondaryTapDownonSecondaryTapUp、onSecondaryTapCancel事件。

2、DoubleTapGestureRecognizer手勢族里面就包含了onDoubleTap事件。

3、LongPressGestureRecognizer手勢族里面就包含了onLongPress、onLongPressStartonLongPressMoveUpdate、onLongPressEnd、onLongPressUp事件。

image

gestures中的每一個值都是GestureRecognizerFactory類型。通過GestureRecognizerFactoryWithHandlers的構(gòu)造方法,分別給GestureRecognizerFactoryconstructor、initializer方法進(jìn)行初始化。

還記得RawGestureDetector組件的_syncAll中提到的constructor、initializer方法么?所以結(jié)合起來看,我們得出了如下結(jié)論:

GestureDetector的多種手勢屬性,都有其所屬的手勢族(GestureRecognizerFactory對象)。這些屬性會通過手勢族的initializer方法保存起來。

三、舉個例子

GestureDetector(
    child: ConstrainedBox(
      constraints: BoxConstraints.tight(Size(300, 150)),
      child: Container(
        color: Colors.blue,
        child: Center(
          child: Text('click me'),
        ),
      ),
    ),
    onTapDown: (TapDownDetails details) {
      print("onTap down");
    },
    onTapUp: (TapUpDetails details) {
      print("onTap up");
    },
),

運(yùn)行上面的代碼后,展示如下。

image

GestureDector本質(zhì)上也是Listener,所以當(dāng)我們點(diǎn)擊了click me文案后,需要執(zhí)行命中測試,命中測試列表如下所示:RenderParagraph->RenderPositionedBox->RenderDecoratedBox->RenderConstrainedBox->RenderPointerListener

根據(jù)命中測試列表,從上而下執(zhí)行每一個對象的handleEvent方法。Listener對應(yīng)的RenderObject就是RenderPointerListener,而RenderPointerListenerhandleEvent方法如下。

@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
    assert(debugHandleEvent(event, entry));
    if (onPointerDown != null && event is PointerDownEvent)
      return onPointerDown(event);
    if (onPointerMove != null && event is PointerMoveEvent)
      return onPointerMove(event);
    if (onPointerUp != null && event is PointerUpEvent)
      return onPointerUp(event);
    if (onPointerCancel != null && event is PointerCancelEvent)
      return onPointerCancel(event);
    if (onPointerSignal != null && event is PointerSignalEvent)
      return onPointerSignal(event);
 }

這里的onPointerDown、onPointerMoveonPointerUp、onPointerCancel、onPointerSignal屬性和Listener中是一一對應(yīng)的。

當(dāng)點(diǎn)擊click me文案時,由于onPointerDown!=null && event is PointerDownEvent為true,從而執(zhí)行了Listener中的onPointerDown方法,也就是RawGestureDetector組件的_handlePointerDown方法。

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

_recognizers.values值遍歷的結(jié)果我們上面分析過了,這里遍歷的結(jié)果是每次都會去執(zhí)行GestureRecognizer對象的addPointer方法。

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

首先通過了isPointerAllowed方法判斷PointerDownEvent手勢事件是否被GestureRecognizer對象所接受,一般每一個GestureRecognizer對象都會重寫isPointerAllowed方法。

對于上面的例子,這里的GestureRecognizer對象就是TapGestureRecognizer,它的addAllowedPointer方法如下所示。

@override
void addAllowedPointer(PointerDownEvent event) {
    super.addAllowedPointer(event);
    _initialButtons = event.buttons;
}

這里直接調(diào)用了父類的addAllowedPointer方法。

 @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方法。

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

1、第一步通過GestureBinding.instance.pointerRouter調(diào)用了addRoute方法,參數(shù)為PointerDownEvent事件的唯一值(pointer)、handleEvent對象(由GestureRecognizer的子類實(shí)現(xiàn))、PointerDownEvent事件坐標(biāo)系(transform)。

注意:這里的GestureBinding.instance返回的是GestureBinding的對象,它是一個單例類,作用是管理手勢事件生命周期與手勢沖突。

void addRoute(int pointer, PointerRoute route, [Matrix4 transform]) {
    final LinkedHashSet<_RouteEntry> routes = _routeMap.putIfAbsent(pointer, () => LinkedHashSet<_RouteEntry>());
    assert(!routes.any(_RouteEntry.isRoutePredicate(route)));
    routes.add(_RouteEntry(route: route, transform: transform));
}

addRoute方法將在_routeMap中尋找pointer對應(yīng)的LinkedHashSet,不存在則新建一個,然后創(chuàng)建一個_RouteEntry對象,并將routetransform傳遞過去。

2、第二步調(diào)用了_addPointerToArena方法。

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

_addPointerToArena方法中,也通過GestureBinding.instance.gestureArena調(diào)用了add方法,參數(shù)為PointerDownEvent事件的唯一值(pointer)、GestureRecognizer對象(具體子類)。

還記得我們點(diǎn)擊click me文案時,上面提到的命中測試列表么?其實(shí)上面只是列出了一部分,在RenderPointerListener的最后還有WidgetsFlutterBinding。所以應(yīng)該是這樣的:
RenderParagraph->RenderPositionedBox->RenderDecoratedBox->RenderConstrainedBox->RenderPointerListener->...->WidgetsFlutterBinding

所以在命中測試列表最后一步,執(zhí)行的是WidgetsFlutterBindinghandleEvent方法,這一步很重要,我們來看一下。

@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);
    } else if (event is PointerSignalEvent) {
      pointerSignalResolver.resolve(event);
    }
}

方法中參數(shù)event是一個PointerEvent對象,由PointerDownEvent、一系列PointerMoveEvent、PointerUpEvent事件組成,對于每一個PointerEvent事件,都會執(zhí)行pointerRouterroute方法。這里的pointerRouter對象就是GestureBinding.instance.pointerRouter對象。

void route(PointerEvent event) {
    final LinkedHashSet<_RouteEntry> routes = _routeMap[event.pointer];
    final List<_RouteEntry> globalRoutes = List<_RouteEntry>.from(_globalRoutes);
    if (routes != null) {
      for (_RouteEntry entry in List<_RouteEntry>.from(routes)) {
        if (routes.any(_RouteEntry.isRoutePredicate(entry.route)))
          _dispatch(event, entry);
      }
    }
    for (_RouteEntry entry in globalRoutes) {
      if (_globalRoutes.any(_RouteEntry.isRoutePredicate(entry.route)))
        _dispatch(event, entry);
    }
}

route方法會從_routeMap中取出該觸摸事件,并執(zhí)行_dispatch將該事件分發(fā)下去。這里_routeMap對應(yīng)的數(shù)據(jù),在上面的startTrackingPointer方法中已分析過。

void _dispatch(PointerEvent event, _RouteEntry entry) {
    try {
      event = event.transformed(entry.transform);
      entry.route(event);
    } catch (exception, stack) {
      ...省略
    }
}

event.transfromed方法會對當(dāng)前觸摸事件對象進(jìn)行坐標(biāo)系轉(zhuǎn)換。一般來說,非特殊情況下,這里轉(zhuǎn)換前和轉(zhuǎn)換后是同一個觸摸事件對象。然后調(diào)用了_RouteEntryroute方法,將該觸摸事件對象傳遞過去。

這里_RouteEntryroute方法就是上面的startTrackingPointer方法中初始化的,并且它指向的是每一個GestureRecognizer子類的handleEvent方法。

拿上面的例子來說,就是TapGestureRecognizerhandleEvent方法,由于TapGestureRecognizer沒有該方法,我們從它父類PrimaryPointerGestureRecognizer可以找到。

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

這里最重要的方法是執(zhí)行了resolve方法。

void resolve(GestureDisposition disposition) {
    final List<GestureArenaEntry> localEntries = List<GestureArenaEntry>.from(_entries.values);
    _entries.clear();
    for (GestureArenaEntry entry in localEntries)
      entry.resolve(disposition);
}

這里的_entries也是在上面的startTrackingPointer方法分析過的,所以上述方法會遍歷_entries的每一個GestureArenaEntry對象(對應(yīng)著每一個GestureRecognizer對象),并執(zhí)行它的resolved方法,然后再調(diào)用GestureArenaManager_resolve方法。

void _resolve(int pointer, GestureArenaMember member, GestureDisposition disposition) {
    final _GestureArena state = _arenas[pointer];
    if (state == null)
      return; // This arena has already resolved.
    assert(_debugLogDiagnostic(pointer, '${ disposition == GestureDisposition.accepted ? "Accepting" : "Rejecting" }: $member'));
    assert(state.members.contains(member));
    if (disposition == GestureDisposition.rejected) {
      state.members.remove(member);
      member.rejectGesture(pointer);
      if (!state.isOpen)
        _tryToResolveArena(pointer, state);
    } else {
      assert(disposition == GestureDisposition.accepted);
      if (state.isOpen) {
        state.eagerWinner ??= member;
      } else {
        assert(_debugLogDiagnostic(pointer, 'Self-declared winner: $member'));
        _resolveInFavorOf(pointer, state, member);
      }
    }
}

該方法主要的作用是處理手勢沖突,通過手勢沖突處理后,能成功執(zhí)行的手勢事件會調(diào)用_resolveInFavorOf方法。

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 (GestureArenaMember rejectedMember in state.members) {
      if (rejectedMember != member)
        rejectedMember.rejectGesture(pointer);
    }
    member.acceptGesture(pointer);
}

然后再執(zhí)行GestureArenaMemberacceptGesture方法。該方法是抽象方法,具體的實(shí)現(xiàn)是在其子類中。我們看一下TapGestureRecognizer的實(shí)現(xiàn)。

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

這里_checkDown方法主要處理按下事件,_checkUp主要處理抬起事件。這也說明了TapGestureRecognizer手勢族只處理手勢的按下與抬起。其他事件由其他手勢族進(jìn)行處理。

_checkDown_checkUp方法后面還會調(diào)用諸多方法,最終會調(diào)用onTapDownonTapUp方法,這里的方法鏈路就不再分析了,有興趣的同學(xué)可以去看看源碼。

總結(jié)

本文以TapGestureRecognizer作為例子,分析了GestureDector組件的觸摸事件的原理。GestureDector組件的底層是通過Listener實(shí)現(xiàn)的,并且與Listener一樣也需要對觸摸事件進(jìn)行命中測試。GestureDector組件的各個屬性方法在得到響應(yīng)之前,會通過WidgetsFlutterBinding做事件分發(fā),并通過手勢沖突競技場做手勢沖突管理,最終通過的手勢事件才會分發(fā)到各個GestureRecognizer對象的handleEvent方法進(jìn)行處理,結(jié)果才會是各個GestureRecognizer對象的屬性方法得到響應(yīng)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容