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、全面深刻觸摸和滑動原理)

相關文章
相關標籤/搜索