扣丁書屋

探索 Flutter 模擬事件觸發 | 開發者說·DTalk

如果可以模擬 PointerEvent 進行分發,那么在應用中就可以通過代碼來觸發手勢事件,這樣就能解放雙手。如果結合語音監聽,通過代碼處理,說話也能觸發手勢操作,豈不美哉。

作為探索完手勢機制滑動機制,又有完成這兩本小冊的我,感覺這個問題應該可解。下面就將整個問題的解決過程進行梳理,帶大家再認識一下手勢底層的相關實現。

一、模擬按下事件

1. 思路分析 1

PointerEvent 作為手勢機制中被傳遞的數據,它記錄著觸點的 id,坐標、觸點類型等信息。所以如果有辦法發送一個 PointerDownEvent 的消息,不就表示按下了嗎?

2. 自己分發事件

然后想到手勢事件分發是由 GestureBinding 處理的,而我們可以通過 GestureBinding.instance 獲取 GestureBinding 對象。那是不是意味著,可以自己來分發一個 PointerDownEvent 的消息。于是創建如下示例界面: 上部有兩個按鈕分別用于模擬滑動模擬點擊。

我們現在的目標是通過模擬點擊可以點擊右下角的加號按鈕,從而讓上面黃色區域內的數字自加;通過模擬滑動讓列表滑動。 于是寫了如下 48 行的代碼通過 GestureBinding 對象的 dispatchEvent 來分發事件: 現在問題來了,第二入參需要傳入 HitTestResult 對象。 但它是一個可空的入參,所以傳個 null 試試:-

GestureBinding.instance!.dispatchEvent(p0, null);

很不出所料地,拋了異常,看來這樣直接發送消息似乎并不是正解。那么來分析一下這樣為何不可。

3. GestureBinding#dispatchEvent 的邏輯處理下面通過調試來看一下 GestureBinding#dispatchEvent 的邏輯處理: 402 行表示,當 hitTestResult 為 null 時,當前的 event 對象類型必須是 PointerAddedEvent 和 PointerRemovedEvent。而我們上面傳入 PointerDownEvent,使用肯定會拋異常。

所以現在的問題是,如果我們無法創建 HitTestResult,就無法通過 dispatchEvent 方法來分發 PointerDownEvent 事件。但 HitTestResult 是從 hitTest 收集的,我們似乎很難去主動創建,似乎問題進入了死胡同。

二、單擊事件是如何觸發的

1. 回顧單擊事件的觸發

如下是點擊加好按鈕時 FloatingActionButton#onPressed 回調觸發的方法棧情況,可以看到是在分發 PointerUpEvent 類型事件下觸發單擊事件的:

其實這也很好理解: 一個單擊事件的觸發條件并非只是分發 PointerDownEvent 而已,TapGestureRecognizer 手勢檢測器至少需要按下、抬起才會被觸發。

所以我們可以在 GestureBinding#dispatchEvent 分發方法打個斷點,通過點擊 + 按鈕,看看有哪些 PointerDownEvent 會被分發。

2. 單擊事件分發的 PointerEvent

如下所示,首先會分發 PointerAddEvent 事件,此時 hitTestResult 為 null, 接下來分發 PointerDownEvent 事件,可以看出此時 hitTestResult 就已經非空了,這說明在分發 PointerAddEvent 事件后,分發 PointerDownEvent 事件前,肯定有對 HitTestResult 進行收錄的處理。

最后分發 PointerDownEvent 事件,然后就觸發了單擊事件的回調。

3. HitTestResult 的收集

那接下來看一下 PointerDownEvent 事件分發前,HitTestResult 是如何被收集的。其實想知道這點很簡單,dispatchEvent 既然要傳入 HitTestResult 對象,只要通過調試看一下這個對象的來源即可:

只要往下看兩個方法棧,很容易定位到在 GestureBinding._handlePointerEventImmediately 方法中當 event 是 PointerDownEvent、PointerSignalEvent、PointerHoverEvent 時,都會創建 HitTestResult 對象,在通過 hitTest 方法來收集測試結果。

至于 hitTest 方法是如何從頂層的 RenderView 一層層測試的,這里就不展開了。感興趣的可以自己調試看看。

其實這樣一來,我們如果可以觸發這個方法就好了,但可惜它是個私有成員方法。但我們眼睛可以稍微向下瞄一個方法棧,普通成員方法 GestureBinding.handlePointerEvent 可以觸發這個私有方法。到這里,一個解決方案就應運而生了。

三、模擬事件觸發的實現

如下效果所示: 通過模擬點擊可以點擊右下角的加號按鈕,從而讓上面黃色區域內的數字自加;通過模擬滑動讓列表滑動。這樣我們就實現了通過代碼觸發手勢事件。

1. 單擊事件

其實我們只需要通過 GestureBinding#handlePointerEvent 依次分發這三個 PointerEvent,就能模擬單擊事件的觸發了。沒錯,就是這么簡單,但其中涉及到的手勢體系知識,還是很值得回味的。

*注: 其中 Offset(322.8, 746.9) 是觸點的位置,是剛才通過調試看到的 + 位置。


void _pressAdd() {
  const PointerEvent addPointer =  PointerAddedEvent(
      pointer: 0,
      position: Offset(322.8, 746.9)
  );
      const PointerEvent downPointer =  PointerDownEvent(
      pointer: 0,
      position: Offset(322.8, 746.9)
  );
  const PointerEvent upPointer =  PointerUpEvent(
      pointer: 0,
      position: Offset(322.8, 746.9)
  );
  GestureBinding.instance!.handlePointerEvent(addPointer);
  GestureBinding.instance!.handlePointerEvent(downPointer);
  GestureBinding.instance!.handlePointerEvent(upPointer);
}

2. 滑動事件的觸發

如下,滑動事件的觸發關鍵點在于 tag1 處,通過 for 循環模擬 20 次偏移量是 20 的向上滑動事件。

void _pressMove() async {
  const PointerEvent addPointer =  PointerAddedEvent(
      pointer: 1,
      position: Offset(122.8, 746.9)
  );
  const PointerEvent downPointer =  PointerDownEvent(
      pointer: 1,
      position: Offset(122.8, 746.9)
  );
  GestureBinding.instance!.handlePointerEvent(addPointer);
  GestureBinding.instance!.handlePointerEvent(downPointer);

  double dy = 20;
  double updateCount = 20;
  for (int i = 0; i < 20; i++) { // tag1
    await Future.delayed(const Duration(milliseconds: 6));
    PointerEvent movePointer =  PointerMoveEvent(
        pointer: 1,
        delta: Offset(0, -dy),
        position: Offset(122.8, 746.9 - i * dy)
    );
    GestureBinding.instance!.handlePointerEvent(movePointer);
  }

  PointerEvent upPointer = PointerUpEvent(
      pointer: 1,
      position: Offset(122.8, 746.9 - dy * updateCount)
  );
  GestureBinding.instance!.handlePointerEvent(upPointer);
}

這樣就可以發現: 只要我們按照各手勢檢測器競技勝利的規則進行模擬處理 PointerEvent 事件,就可以通過代碼完成我們想要觸發的手勢,是不是感覺非常棒。感覺可以結合一下計時器通過發送一系列手勢來完成一些引導操作,或者操作演示。

對于一些流程性的測試,或精準的滑動控制分析,用代碼模擬會顯得更加重要,因為一些性能分析需要控制變量,手動滑動多多少少會有不同,從而影響測試分析的結果。本篇就到這里,希望通過本文您能對 Flutter 的手勢有更深切的認識,也希望 Flutter 模擬事件觸發,在某個時刻可以幫助到您。


https://mp.weixin.qq.com/s/9HLBB6dVSlDk3w9OYKyWLA

最多閱讀

如何有效定位Flutter內存問題? 2年以前  |  12971次閱讀
Flutter的手勢GestureDetector分析詳解 3年以前  |  9239次閱讀
Flutter插件詳解及其發布插件 3年以前  |  8156次閱讀
在Flutter中添加資源和圖片 4年以前  |  6311次閱讀
發布Flutter開發的iOS程序 4年以前  |  5482次閱讀
Flutter 狀態管理指南之 Provider 3年以前  |  5436次閱讀
Flutter for Web詳細介紹 3年以前  |  5236次閱讀
在Flutter中發起HTTP網絡請求 4年以前  |  4981次閱讀
使用Inspector檢查用戶界面 4年以前  |  4852次閱讀
Flutter路由詳解 3年以前  |  4427次閱讀
Flutter Widget框架概述 4年以前  |  3953次閱讀
為Flutter應用程序添加交互 4年以前  |  3918次閱讀
JSON和序列化 4年以前  |  3806次閱讀
推薦5個Flutter重磅開源項目! 2年以前  |  3725次閱讀
Flutter框架概覽 4年以前  |  3659次閱讀
處理文本輸入 4年以前  |  3575次閱讀
使用自定義字體 4年以前  |  3542次閱讀

手機掃碼閱讀
18禁止午夜福利体验区,人与动人物xxxx毛片人与狍,色男人窝网站聚色窝
<蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <文本链> <文本链> <文本链> <文本链> <文本链> <文本链>