深入理解Flutter的GestureDetector组件

引言

上一篇文章 深入理解Flutter的Listener组件 介绍了触控事件的监听原理,让我们对Flutter中触摸事件的传递过程有了进一步的认识。

今天我们学习一下手势识别组件GestureDetector的原理。GestureDetector的内部实现使用的是Listener组件,如果对Listener还不太熟悉,可以先了解一下Listener的原理。

源码解析

一、GestureDetector是Listener的封装

GestureDector是一个无状态组件,它的build方法如下所示。

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

build方法直接返回了RawGestureDetector组件,说明GestureDetector是由子组件RawGestureDetector构成的。而RawGestureDetector是一个有状态组件,它的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组件,这也证明了上面的结论:

GestureDetector的内部实现使用的是Listener组件。

二、GestureDetector的实现原理

相比于ListenerGestureDetector有自己的属性,如onDoubleTaponLongPressonHorizontalDragStartonVerticalDragStart等。

其实说到底,这些属性也是由ListeneronPointerDownonPointerMoveonPointerUp这三个属性封装而成的。

重新看一下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传递进来的,也就是说GestureDector自上而下的Widget构成如下图所示。

从之前对Listener组件的分析可知,Listener中的Child的触摸事件由onPointerDownonPointerMoveonPointerUp等属性值决定。

这里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保存下来,然后遍历参数中的gestures,若原有的_recognizers有该手势类型对象,则使用,否则调用gestures[type]constructor方法。然后继续调用gestures[type]initializer方法。记住constructorinitializer这两个方法,后面的分析需要用到。

_syncAll方法在两处地方被调用,分别是initStatedidUpdateWidget方法。

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

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

组件初始化会调用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手势族里面就包含了onTapDownonTapUponTaponTapCancelonSecondaryTapDownonSecondaryTapUponSecondaryTapCancel事件。

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

3、LongPressGestureRecognizer手势族里面就包含了onLongPressonLongPressStartonLongPressMoveUpdateonLongPressEndonLongPressUp事件。

gestures中的每一个值都是GestureRecognizerFactory类型。通过GestureRecognizerFactoryWithHandlers的构造方法,分别给GestureRecognizerFactoryconstructor、initializer方法进行初始化。

还记得RawGestureDetector组件的_syncAll中提到的constructor、initializer方法么?所以结合起来看,我们得出了如下结论:

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");
    },
),

运行上面的代码后,展示如下。

GestureDector本质上也是 Listener,所以当我们点击了 click me文案后,需要执行命中测试,命中测试列表如下所示: RenderParagraph-> RenderPositionedBox-> RenderDecoratedBox-> RenderConstrainedBox-> RenderPointerListener

根据命中测试列表,从上而下执行每一个对象的handleEvent方法。Listener对应的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);
 }

这里的onPointerDownonPointerMoveonPointerUponPointerCancelonPointerSignal属性和Listener中是一一对应的。

当点击click me文案时,由于onPointerDown!=null && event is PointerDownEvent为true,从而执行了Listener中的onPointerDown方法,也就是RawGestureDetector组件的_handlePointerDown方法。

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

_recognizers.values值遍历的结果我们上面分析过了,这里遍历的结果是每次都会去执行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;
}

这里直接调用了父类的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调用了addRoute方法,参数为PointerDownEvent事件的唯一值(pointer)、handleEvent对象(由GestureRecognizer的子类实现)、PointerDownEvent事件坐标系(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对应的LinkedHashSet,不存在则新建一个,然后创建一个_RouteEntry对象,并将routetransform传递过去。

2、第二步调用了_addPointerToArena方法。

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

_addPointerToArena方法中,也通过GestureBinding.instance.gestureArena调用了add方法,参数为PointerDownEvent事件的唯一值(pointer)、GestureRecognizer对象(具体子类)。

还记得我们点击click me文案时,上面提到的命中测试列表么?其实上面只是列出了一部分,在RenderPointerListener的最后还有WidgetsFlutterBinding。所以应该是这样的: RenderParagraph->RenderPositionedBox->RenderDecoratedBox->RenderConstrainedBox->RenderPointerListener->...->WidgetsFlutterBinding

所以在命中测试列表最后一步,执行的是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);
    }
}

方法中参数event是一个PointerEvent对象,由PointerDownEvent、一系列PointerMoveEventPointerUpEvent事件组成,对于每一个PointerEvent事件,都会执行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中取出该触摸事件,并执行_dispatch将该事件分发下去。这里_routeMap对应的数据,在上面的startTrackingPointer方法中已分析过。

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

event.transfromed方法会对当前触摸事件对象进行坐标系转换。一般来说,非特殊情况下,这里转换前和转换后是同一个触摸事件对象。然后调用了_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);
}

这里最重要的方法是执行了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对象(对应着每一个GestureRecognizer对象),并执行它的resolved方法,然后再调用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);
      }
    }
}

该方法主要的作用是处理手势冲突,通过手势冲突处理后,能成功执行的手势事件会调用_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);
}

然后再执行GestureArenaMemberacceptGesture方法。该方法是抽象方法,具体的实现是在其子类中。我们看一下TapGestureRecognizer的实现。

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

这里_checkDown方法主要处理按下事件,_checkUp主要处理抬起事件。这也说明了TapGestureRecognizer手势族只处理手势的按下与抬起。其他事件由其他手势族进行处理。

_checkDown_checkUp方法后面还会调用诸多方法,最终会调用onTapDownonTapUp方法,这里的方法链路就不再分析了,有兴趣的同学可以去看看源码。

总结

本文以TapGestureRecognizer作为例子,分析了GestureDector组件的触摸事件的原理。GestureDector组件的底层是通过Listener实现的,并且与Listener一样也需要对触摸事件进行命中测试。GestureDector组件的各个属性方法在得到响应之前,会通过WidgetsFlutterBinding做事件分发,并通过手势冲突竞技场做手势冲突管理,最终通过的手势事件才会分发到各个GestureRecognizer对象的handleEvent方法进行处理,结果才会是各个GestureRecognizer对象的属性方法得到响应。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值