Flutter之事件处理

在学习flutter的时候忽然想到,flutter既然不像其余跨平台框架那样采用系统原生渲染,那么flutter就应该拥有本身的事件处理机制。本着好奇的心理,来对flutter的事件处理机制一窥究竟。html

一、flutter事件传递

事件都是由硬件收集起来的,而后传递给软件。那么在flutter中,事件的源头在哪尼?前端

通过分析源码能够发现。在类window中,flutter经过方法onPointerDataPacket来接收硬件传递过来的事件,也就是说该方法是flutter接收事件的源头,而该方法是在类GestureBinding初始化的时候设置的。因此在类GestureBinding的方法_handlePointerDataPacket中开始处理事件。segmentfault

mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    //_handlePointerDataPacket(是一个私有方法)接收系统传递过来的事件,
    window.onPointerDataPacket = _handlePointerDataPacket;
  }

  ...

  //一个FIFO的双端队列,用来存储事件
  final Queue<PointerEvent> _pendingPointerEvents = Queue<PointerEvent>();

  void _handlePointerDataPacket(ui.PointerDataPacket packet) {
    //这里作了如下两个操做
    //一、将pointer数据转换为逻辑像素,从而隔离真实设备。
    //二、将转换后的数据加入到FIFO双端队列中
    _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
    if (!locked){
      //开始处理事件
      _flushPointerEventQueue();
    }
  }

  ...

  void _flushPointerEventQueue() {
    assert(!locked);
    //若是队列中有数据,就处理
    while (_pendingPointerEvents.isNotEmpty)
      _handlePointerEvent(_pendingPointerEvents.removeFirst());
  }

  ...
  
  final Map<int, HitTestResult> _hitTests = <int, HitTestResult>{};
  
  void _handlePointerEvent(PointerEvent event) {
    assert(!locked);
    HitTestResult hitTestResult;
    //若是是Down事件
    if (event is PointerDownEvent) {
      assert(!_hitTests.containsKey(event.pointer));
      hitTestResult = HitTestResult();
      //将事件要通过的全部Widget添加到一个集合中
      hitTest(hitTestResult, event.position);
      _hitTests[event.pointer] = hitTestResult;
      assert(() {
        if (debugPrintHitTestResults)
          debugPrint('$event: $hitTestResult');
        return true;
      }());
    } else if (event is PointerUpEvent || event is PointerCancelEvent) {
      hitTestResult = _hitTests.remove(event.pointer);
    } else if (event.down) {
      //若是是move事件
      hitTestResult = _hitTests[event.pointer];
    }
    assert(() {
      if (debugPrintMouseHoverEvents && event is PointerHoverEvent)
        debugPrint('$event');
      return true;
    }());
    if (hitTestResult != null ||
        event is PointerHoverEvent ||
        event is PointerAddedEvent ||
        event is PointerRemovedEvent) {
      //分发事件
      dispatchEvent(event, hitTestResult);
    }
  }

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

  //事件的分发
  @override // from HitTestDispatcher
  void dispatchEvent(PointerEvent event, HitTestResult hitTestResult) {
    assert(!locked);
    ...
    for (HitTestEntry entry in hitTestResult.path) {
      try {
        //分发给子Widget处理
        entry.target.handleEvent(event, entry);
      } catch (exception, stack) {
        ...
      }
    }
  }
  
  //
  @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);
    }
  }
}
复制代码

上面就是处理事件分发的源码,它主要作了如下三步操做。浏览器

1.一、接收事件

因为接收硬件的数据都是与真实设备相关的,因此须要经过PointerEventConverterexpand方法来隔离设备相关性。相似Android中的pxdpmarkdown

只有将数据进行设备隔离后才能放入FIFO的双端队列——ListQueue中。笔者认为这里使用队列是为了防止Widget处理不及时从而致使阻塞。以下图所示。框架

1.二、Down事件处理

Android同样,Down事件在flutter中也是很是重要的一个事件,它是从ListQueue中取出的第一个事件。经过该事件,flutter能够获取到目标Widget到根Widget的路径,并将路径上的全部Widget添加到一个集合List中。以下图所示。 less

将路径上的全部 Widget添加到集合后,就会遍历该集合,并将 Down事件交给集合中的全部 Widget处理。以下图所示。

1.三、其余事件处理

Down事件处理完毕之后,其余事件(如moveup等)会遍历集合,并将事件传递给集合中的全部Widget处理,以下图所示。ide

能够发现事件是从目标 WidgetRenderView(根 Widget)传递的。若是是作前端开发的,想必对这一流程比较熟悉,由于这与前端开发中浏览器的事件 冒泡机制类似, 但在 flutter中是没有机制来取消或中止”冒泡“过程的,而浏览器的冒泡是能够中止的。

二、flutter事件拦截

事件既然有分发,那么确定也可以拦截,相对于Android而言,笔者认为flutter的事件拦截要简单不少。在flutter中,能够经过AbsorbPointerIgnorePointer这两个Widget来拦截事件。post

2.一、AbsorbPointer

AbsorbPointer是一个Widget,它的主要做用就是拦截其子Widget响应事件。它的实现原理其实很简单,就是在响应Donw事件时,不会将其子Widget添加到集合中,这样其子Widget就没法接收到事件。以下图所示。 学习

AbsorbPointer中能够修改 absorbing的值来让子 Widget响应事件。

注意:AbsorbPointer自己能够响应事件

2.二、IgnorePointer

AbsorbPointer是一个Widget,它的主要做用就是拦截其子Widget响应事件。它的实现原理其实很简单,就是在响应Donw事件时,不会将自身及其子Widget添加到集合中,这样本身及其子Widget就没法接收到事件。以下图所示。

IgnorePointer中能够修改 ignoring的值来让本身及其子 Widget响应事件。

注意:IgnorePointer自己没法响应事件

2.三、事件拦截的应用

AbsorbPointer为例,下面来看一下如何拦截事件。

class MyHomePage extends StatelessWidget {
  _onPointerDown(PointerDownEvent event) {
    print("_onPointerDown:" + event.toString());
  }

  _onPointerMove(PointerMoveEvent event) {
    print("_onPointerMove:" + event.toString());
  }

  _onPointerUp(PointerUpEvent event) {
    print("_onPointerUp:" + event.toString());
  }

  _onPointerDown1(PointerDownEvent event) {
    print("_onPointerDown1:" + event.toString());
  }

  _onPointerMove1(PointerMoveEvent event) {
    print("_onPointerMove1:" + event.toString());
  }

  _onPointerUp1(PointerUpEvent event) {
    print("_onPointerUp1:" + event.toString());
  }

  @override
  Widget build(BuildContext context) {
    return Listener(
        onPointerDown: _onPointerDown,
        onPointerMove: _onPointerMove,
        onPointerUp: _onPointerUp,
        child: AbsorbPointer(
          //该值为false则下面的Listener不会打印任何信息
          //absorbing: false,
            child: Listener(
          onPointerDown: _onPointerDown1,
          onPointerMove: _onPointerMove1,
          onPointerUp: _onPointerUp1,
          child: Container(
            color: Colors.red,
            width: 200.0,
            height: 100.0,
          ),
        )));
  }
}
复制代码

Listener能够监听最原始的事件,经过该Widget能够拿到事件的相关信息。经过运行上面代码能够发现第二个Listener中的相关信息都不会被打印。但若是将absorbing的值改成false。则第二个Listener的信息都会打印出来。

IgnorePointerAbsorbPointer的用法基本一致。

三、手势处理

前面介绍了flutter中的事件分发及处理,但都是基于最原始的指针信息。当若是咱们想要实现点击、双击、快速滑动等功能时,经过最原始的指针信息就比较麻烦了,这时候就须要使用flutter给咱们封装好了的Widget——GestureDetector

3.一、GestureDetector

GestureDetector封装了点击、双击、滑动等大量功能,使开发者能够快速使用这些基础性功能。

GestureDetector({
    Key key,
    this.child,
    this.onTapDown,
    this.onTapUp,
    this.onTap,
    this.onTapCancel,
    this.onDoubleTap,
    this.onLongPress,
    this.onLongPressUp,
    this.onLongPressDragStart,
    this.onLongPressDragUpdate,
    this.onLongPressDragUp,
    this.onVerticalDragDown,
    this.onVerticalDragStart,
    this.onVerticalDragUpdate,
    this.onVerticalDragEnd,
    this.onVerticalDragCancel,
    this.onHorizontalDragDown,
    this.onHorizontalDragStart,
    this.onHorizontalDragUpdate,
    this.onHorizontalDragEnd,
    this.onHorizontalDragCancel,
    this.onForcePressStart,
    this.onForcePressPeak,
    this.onForcePressUpdate,
    this.onForcePressEnd,
    this.onPanDown,
    this.onPanStart,
    this.onPanUpdate,
    this.onPanEnd,
    this.onPanCancel,
    this.onScaleStart,
    this.onScaleUpdate,
    this.onScaleEnd,
    this.behavior,
    this.excludeFromSemantics = false,
    this.dragStartBehavior = DragStartBehavior.down,
  }) 
复制代码

从上面代码能够看出,GestureDetector的功能仍是蛮丰富的。下面来看如何使用GestureDetector,以点击事件为例。

class MyHomePage extends StatelessWidget {
  _onPointerDown(PointerDownEvent event) {
    print("_onPointerDown:" + event.toString());
  }

  _onPointerMove(PointerMoveEvent event) {
    print("_onPointerMove:" + event.toString());
  }

  _onPointerUp(PointerUpEvent event) {
    print("_onPointerUp:" + event.toString());
  }

  _onPointerEnter(PointerEnterEvent event) {
    print("_onPointerEnter:" + event.toString());
  }

  _onPointerExit(PointerExitEvent event) {
    print("_onPointerExit:" + event.toString());
  }

  _onPointerHover(PointerHoverEvent event) {
    print("_onPointerHover:" + event.toString());
  }

  _onPointerDown1(PointerDownEvent event) {
    print("_onPointerDown1:" + event.toString());
  }

  _onPointerMove1(PointerMoveEvent event) {
    print("_onPointerMove1:" + event.toString());
  }

  _onPointerUp1(PointerUpEvent event) {
    print("_onPointerUp1:" + event.toString());
  }

  @override
  Widget build(BuildContext context) {
    return Listener(
        onPointerDown: _onPointerDown,
        onPointerMove: _onPointerMove,
        onPointerUp: _onPointerUp,
        onPointerEnter: _onPointerEnter,
        onPointerExit: _onPointerExit,
        onPointerHover: _onPointerHover,
        child: Stack(
          children: <Widget>[
            GestureDetector(
              //监听点击事件
              onTap: () => {print("点击事件")},
              //监听横向滑动事件
              onVerticalDragUpdate: (DragUpdateDetails details) =>
                  {print("横向滑动:" + details.toString())},
              child: Listener(
                  onPointerDown: _onPointerDown1,
                  onPointerMove: _onPointerMove1,
                  onPointerUp: _onPointerUp1,
                  child: Center(
                    child: Container(
                      color: Colors.red,
                      width: 200.0,
                      height: 100.0,
                    ),
                  )),
            ),
          ],
        ));
  }
}
复制代码

经过上面代码就能够实现Widget的点击功能。使用起来蛮简单,但实现原理仍是比较复杂的。

3.二、实现原理

在前面说过,事件会从最底层的WidgetRenderView传递。但其实事件传递给RenderView后,还会传递给一个类GestureBinding,在该类中会对事件作最终处理。

也就是说事件开始于GestureBinding_handlePointerDataPacket方法,结束于GestureBindinghandleEvent方法,而GestureDetector的一系列事件(如点击、双击、滑动等)也是在GestureBindinghandleEvent方法中转发的。

final PointerRouter pointerRouter = PointerRouter();
  //该方法是作事件的最后处理,不会在往其余地方传递
  @override // from HitTestTarget
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    //转发在GestureBinding中添加的事件
    pointerRouter.route(event);
    if (event is PointerDownEvent) {
      gestureArena.close(event.pointer);
    } else if (event is PointerUpEvent) {
      //解决手势冲突,会响应最底层的Widget
      gestureArena.sweep(event.pointer);
    }
  }
复制代码

在类PointerRouter中有一个Map。当GestureDetector响应按下事件时就会往PointerRouter中的Map添加一个回调方法。而后经过PointerRouterroute方法将事件交给不一样GestureDetector来处理。

GestureBinding内部会用Listener对子Widget进行一层包裹。这样能够监听到Down事件并向PointerRouter中的Map添加回调方法。

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

/// State for a [RawGestureDetector].
class RawGestureDetectorState extends State<RawGestureDetector> {
  Map<Type, GestureRecognizer> _recognizers = const <Type, GestureRecognizer>{};

  ...

  void _handlePointerDown(PointerDownEvent event) {
    assert(_recognizers != null);
    //注册回调方法
    for (GestureRecognizer recognizer in _recognizers.values)
      recognizer.addPointer(event);
  }

  ...

  @override
  Widget build(BuildContext context) {
    //建立一个Listener
    Widget result = Listener(
      onPointerDown: _handlePointerDown,
      behavior: widget.behavior ?? _defaultBehavior,
      child: widget.child
    );
    //Listener包裹子Widget
    if (!widget.excludeFromSemantics)
      result = _GestureSemantics(owner: this, child: result);
    return result;
  }
  
  ...
}

//以点击事件(TapGestureRecognizer)为例,TapGestureRecognizer继承自PrimaryPointerGestureRecognizer
abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecognizer {
  ...
  //抽象的回调方法,须要在子类实现
  @protected
  void handleEvent(PointerEvent event);

  @override
  void addPointer(PointerDownEvent event) {
    //注册回调方法
    startTrackingPointer(event.pointer);
    if (state == GestureRecognizerState.ready) {
      state = GestureRecognizerState.possible;
      primaryPointer = event.pointer;
      initialPosition = event.position;
      if (deadline != null)
        _timer = Timer(deadline, didExceedDeadline);
    }
  }
  //响应事件
  @override
  void handleEvent(PointerEvent event) {
    assert(state != GestureRecognizerState.ready);
    if (event.pointer == primaryPointer) {
      final bool isPreAcceptSlopPastTolerance =
          state == GestureRecognizerState.possible &&
          preAcceptSlopTolerance != null &&
          _getDistance(event) > preAcceptSlopTolerance;
      final bool isPostAcceptSlopPastTolerance =
          state == GestureRecognizerState.accepted &&
          postAcceptSlopTolerance != null &&
          _getDistance(event) > postAcceptSlopTolerance;

      if (event is PointerMoveEvent && (isPreAcceptSlopPastTolerance || isPostAcceptSlopPastTolerance)) {
        resolve(GestureDisposition.rejected);
        //移除回调方法
        stopTrackingPointer(primaryPointer);
      } else {
        handlePrimaryPointer(event);
      }
    }
    stopTrackingIfPointerNoLongerDown(event);
  }
  
  ...
}


abstract class OneSequenceGestureRecognizer extends GestureRecognizer {
  ...

  @protected
  void startTrackingPointer(int pointer) {
    //向PointerRouter中的Map添加回调方法handleEvent
    GestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent);
    _trackedPointers.add(pointer);
    assert(!_entries.containsValue(pointer));
    _entries[pointer] = _addPointerToArena(pointer);
  }

  
  @protected
  void stopTrackingPointer(int pointer) {
    if (_trackedPointers.contains(pointer)) {
      //向PointerRouter中的Map移除回调方法handleEvent
      GestureBinding.instance.pointerRouter.removeRoute(pointer, handleEvent);
      _trackedPointers.remove(pointer);
      if (_trackedPointers.isEmpty)
        didStopTrackingLastPointer(pointer);
    }
  }
  
  ...
}
复制代码

recognizer.addPointer(event)中就会向PointerRouter中的Map添加回调方法。这样就GestureDetector就可以响应点击、滑动等一系列事件了。固然这些事件处理完毕后,也会从PointerRouterMap中移除回调方法。

四、总结

flutter的事件处理整体上来讲比Android要简单一些,因此也相对而言好理解一些。本文从总体逻辑上分析了flutter的事件处理,但愿能帮助你们。

【参考资料】

Pointer事件处理

Flutter中的事件流和手势简析

Flutter完整开发实战详解(十3、全面深刻触摸和滑动原理)

相关文章
相关标签/搜索