Flutter 小技巧之快速理解手勢邏輯

語言: CN / TW / HK

theme: smartblue

又到了小技巧系列更新時間,今天我們主要分享 Flutter 裡的手勢觸控邏輯,其實在很久之前我就寫過 《面深入觸控和滑動原理》相關的原始碼分析文章,但是最近有人說原始碼分析看不懂,有沒有簡要和好理解的角度,那麼本篇就用更簡單的角度,帶大家理解 Flutter 裡的手勢相關邏輯

GestureDetector

不管你用 InkWellInkResponseTextButton 還是 ElevatedButton , 它們針對手勢的處理邏輯都是來自於 GestureDetector ,也就是理解 Flutter 的手勢處理邏輯入門,核心可以從分析 GestureDetector 開始。

其實更嚴格意義上講,手勢事件是來自 ListenerGestureDetector 是針對 Listener 進行了封裝,只是為了避免複雜的原始碼分析,這裡就不做展開,你可以簡單理解為:並不是所有的控制元件都會響應手勢,只有帶有 Listener 的才會響應,這主要體現在觸控事件的遞迴響應上。

GestureDetector 裡關於事件的響應邏輯主要來自於各種 GestureRecognizer (手勢識別)的實現邏輯,不同的手勢識別邏輯會響應不同手勢結果,相互競爭,最後在 GestureArenaManager (競技場) 決定出一個勝利者。

簡單來說,在競技場裡手勢基本遵循兩個邏輯:

  • 每個 Recognizer 都可以隨時選擇失敗退出,當競技場只有它一個的時候它就贏了
  • 每個 Recognizer 都可以隨時宣佈自己獲得勝利,這時其他 Recognizer 也將不再響應

那麼如下圖所示,在 GestureDetector 裡主要有這 8 種 GestureRecognizer 在處理不同的場景,他們會根據使用者使用 GestureDetector 時的引數搭配來參與到事件競技場裡。

舉個例子,當你使用了 GestureDetector 並配置了 onTaponLongPressonDoubleTap ,它們是如何分別響應手勢事件的?

這裡的核心邏輯就在於 deadline (時間) 的處理,不管是 onLongPress 還是 onDoubleTap 都是靠 deadline 來判斷勝負

例如,當用戶只是普通點選時,如下程式碼所示,因為預設 LongPressGestureRecognizer 的 deadline 是 500 毫秒,所以在定時器達到 500ms 響應之前,就會因為 PointerUpEvent 導致長按定時器停止,無法觸發響應長按事件

反之如果沒有 PointerUpEvent 產生,那麼 500 ms 之後 LongPressGestureRecognizer 就會響應,直接宣佈勝利(accepted)。

預設情況下 GestureDetector 是不支援修改 deadline ,只有直接使用 LongPressGestureRecognizer 時才可以修改 deadline 的時長。

類似的邏輯在 DoubleTapGestureRecognizer 下也有,DoubleTap 的 deadline 是 300 毫秒,當用戶首次點選時會註冊一個定時器,如果 300 毫秒以內使用者沒有產生新的點選,那麼 DoubleTapGestureRecognizer 就會宣佈“失敗“退出競技,反之如果在 300 毫秒內有新的點選,則直接宣佈“獲勝”,響應 DoubleTap 回撥。

那這時候有人就要問了:“DoubleTap 過程中,為什麼不會觸發 onTap” ? 這就需要說到 TapGestureRecognizer 的觸發邏輯。

繼續前面 GestureDetector 並配置了 onTaponLongPressonDoubleTap 的例子,在使用者只做普通點選的時候,前面說過:

  • LongPressGestureRecognizer 的定時器 deadline 還沒到 500 毫秒會因為 Up 事件而導致失敗退出
  • DoubleTapGestureRecognizer 會因為定時器超過 deadline 300 毫秒,沒有下一個點選而宣佈退出

那麼在 Long 和 Double 都失敗的情況下,此時 GestureArenaManager (競技場) 裡的成員就只有 TapGestureRecognizer ,這時候競技場會 close ,會觸發競技場的 sweep 邏輯,直接讓最後剩下來的 Recognizer“勝利”,響應 onTap 事件。

所以 TapGestureRecognizer 靠的是勝者為王。

所以基於這個例子,配合一開始說的兩個邏輯,就可以直觀的理解 Flutter 手勢競技場裡的響應邏輯和關鍵 deadline 的作用。

多個 GestureDetector

那麼前面都是隻有一個 GestureDetector 的場景,如果有兩個呢?如下程式碼所示,在巢狀兩個 GestureDetector 下,它們的響應邏輯會是怎麼樣的?

當區域內有兩個 GestureDetector 的時候,使用者在普通點選時,因為 deadline 影響,依舊會是在競技場 close 時才響應 onTap但是不同在於此時競技場裡還會有多個 Recognizer 存在,這時候只有排在列表的第一個的 Recognizer 可以贏得事件,也就是上門程式碼裡的紅色 200x200 小方塊。

因為對於多個 GestureDetector 的情況, Recognizer 在競技場列表(List<GestureArenaMember)裡的順序和 HitTest 時的遞迴呼叫有關係,簡單說就是:遞迴呼叫會就讓我們自下而上的得到一個 HitTestResult 列表,程式碼裡最後的 child 會在最上面

同時對於單個 GestureDetector 而言,TapGestureRecognizer 會是 _recognizers 的第一個,所以 first 會是響應了 TapGestureRecognizer ,詳細邏輯可以看 《面深入觸控和滑動原理》

所以簡單理解:

  • 兩個 GestureDetector 在競技場裡的 member 列表排序時,作為 child 的紅色 GestureDetector 因為 HitTest 遞迴會被排前面
  • GestureDetector 內部 TapGestureRecognizer 會在其內部 _recognizers 排第一

所以 member.first 最終響應了 TapGestureRecognizer ,回到上面兩個定律,如果結合多個 GestureDetector 的場景,就應該是:

  • 每個 Recognizer 都可以隨時選擇失敗退出,當競技場只有它一個的時候它就贏了;如果不止一個,那麼在競技場 close 時, member.first 會獲得響應
  • 每個 Recognizer 都可以隨時宣佈自己獲得勝利,這是其他 Recognizer 也將不再響應

進階補充

前面簡單介紹了 Flutter 的手勢響應的基礎邏輯,這裡再額外補充兩個知識點。

首先,當用戶在長按的時候, GestureDetector 何時會發出 onTapDown 事件

這其實就涉及了另外一個 deadline 引數,當用戶在長按的時候,Recognizer 還會觸發另外一個定時器,然後通過執行 didExceedDeadline 來發出 onTapDown 事件。

那麼問題又來了,既然長按會觸發 onTapDown 事件,如果點選區域內有兩個 TapGestureRecognizer ,長按的時候因為定時器都觸發了 didExceedDeadline ,那是不是兩個都會收到 onTapDown 事件 ?

答案是:會的!因為定時器都觸發了 didExceedDeadline,從而都發出了 onTapDown 事件,所以兩個 onTapDown 回撥都會執行,但是後續競爭只會有一個控制元件能響應 onLongPress

另外,如果不是長按導致的 Down 事件, 是不會導致兩個 GestureDetector 都觸發回撥 onTapDown 回撥。

第二個補充的是 Listener , 如果你還想深入去看 GestureDetector 的實現,你就會發現 GestureDetectorListener 的封裝也許和你想象不大一樣, 因為 Listener 的封裝只用到了 PointerDown ,並沒有用到 onPointerUp ,那 GestureDetector 是怎麼響應 Up 和 Move 事件?

這就需要說到前面介紹 《面深入觸控和滑動原理》 裡的原始碼分析,但是為了簡單,我們這裡只說結論:

因為只有響應了 PointerDown 事件,對應的 GestureRecognizer 才能被新增到 GestureBindingPointerRouter 事件路由和 GestureArenaManager 事件競技中,而後續的 Up 和 Move 事件主要是通過 GestureBinding 來處理

更簡單的說,就是隻有響應了 PointerDown 事件,控制元件的 Recognizer 才能響應後續統一處理的其他手勢事件,而其他事件不需要在 Listener 這裡獲取回撥

結束

那本篇的小技巧到這裡就結束了,本篇主要是用更直觀和簡單的方式,幫助大家理解 Flutter 裡的觸控響應邏輯,如果對更詳細實現感興趣,可以結合 《面深入觸控和滑動原理》 幫助理解,如果你還有什麼感興趣或者有疑惑的,歡迎留言評論~