跳到主內容

點選、拖拽及其他手勢

Flutter 中點選和拖拽等手勢的工作原理。

本文件介紹瞭如何在 Flutter 中監聽並響應手勢。常見手勢包括點選、拖拽和縮放。

Flutter 的手勢系統分為兩個獨立的層級。第一層是原始指標事件,用於描述指標(例如觸控、滑鼠和觸控筆)在螢幕上的位置和移動。第二層是手勢,用於描述由一個或多個指標移動組成的語義動作。

指標 (Pointers)

#

指標代表使用者與裝置螢幕互動的原始資料。共有四種類型的指標事件:

PointerDownEvent

指標已在特定位置接觸螢幕。

PointerMoveEvent

指標已從螢幕上的一個位置移動到另一個位置。

PointerUpEvent

指標已停止與螢幕接觸。

PointerCancelEvent

來自該指標的輸入不再指向此應用。

當指標按下時,框架會對應用進行命中測試 (hit test),以確定指標接觸螢幕的位置存在哪些元件。指標按下事件(以及該指標隨後的事件)將分發給命中測試找到的最內層元件。隨後,事件會沿元件樹向上冒泡,並分發給從最內層元件到樹根路徑上的所有元件。目前沒有機制可以取消或停止指標事件的後續分發。

若要直接從元件層監聽指標事件,請使用 Listener 元件。但通常建議優先使用下文討論的“手勢”。

手勢

#

手勢代表語義動作(例如點選、拖拽和縮放),這些動作透過多個獨立的指標事件識別,甚至可能涉及多個獨立的指標。手勢可以分發多個事件,對應於手勢的生命週期(例如,拖拽開始、拖拽更新和拖拽結束)。

點選

onTapDown

可能導致點選的指標已在特定位置接觸螢幕。

onTapUp

觸發點選的指標已在特定位置停止接觸螢幕。

onTap

之前觸發 onTapDown 的指標也觸發了 onTapUp,最終導致了一次點選。

onTapCancel

之前觸發 onTapDown 的指標最終並未導致點選。

雙擊

onDoubleTap

使用者在同一位置短時間內連續點選了兩次螢幕。

長按

onLongPress

指標在同一位置長時間保持與螢幕接觸。

垂直拖動

onVerticalDragStart

指標已接觸螢幕並可能開始垂直移動。

onVerticalDragUpdate

一個正在與螢幕接觸並進行垂直移動的指標,已經在垂直方向上發生了位移。

onVerticalDragEnd

之前與螢幕接觸並垂直移動的指標不再與螢幕接觸,並且在停止接觸螢幕時以特定速度移動。

水平拖動

onHorizontalDragStart

指標已接觸螢幕並可能開始水平移動。

onHorizontalDragUpdate

一個正在與螢幕接觸並進行水平移動的指標,已經在水平方向上發生了位移。

onHorizontalDragEnd

一個之前正在與螢幕接觸並進行水平移動的指標,現在已脫離螢幕,並在停止接觸時保持著特定的速度。

平移 (Pan)

onPanStart

指標已接觸螢幕,且可能開始進行水平或垂直移動。如果設定了 onHorizontalDragStartonVerticalDragStart,此回撥會報錯。

onPanUpdate

一個正在與螢幕接觸並沿垂直或水平方向移動的指標。如果設定了 onHorizontalDragUpdateonVerticalDragUpdate,此回撥會報錯。

onPanEnd

一個之前與螢幕接觸的指標現已脫離螢幕,並在停止接觸時保持著特定的速度。如果設定了 onHorizontalDragEndonVerticalDragEnd,此回撥會報錯。

為元件新增手勢檢測

#

若要從元件層監聽手勢,請使用 GestureDetector

如果您使用的是 Material 元件,其中許多元件已經能夠響應點選或手勢。例如,IconButtonTextButton 響應按下(點選)操作,而 ListView 透過滑動響應來觸發滾動。如果您不使用這些元件,但希望在點選時出現“墨水飛濺 (ink splash)”效果,可以使用 InkWell

手勢消歧 (Gesture disambiguation)

#

在螢幕上的特定位置,可能存在多個手勢檢測器。例如:

  • ListTile 有一個響應整個 ListTile 的點選識別器,而在尾部的圖示按鈕周圍還有一個巢狀的識別器。尾部圖示的螢幕矩形區域現在被兩個手勢識別器覆蓋,如果發生了點選,它們需要協商由誰來處理該手勢。
  • 單個 GestureDetector 覆蓋的螢幕區域配置為處理多種手勢,例如長按和點選。當用戶觸控式螢幕幕該區域時,點選識別器必須與長按識別器進行協商。根據指標後續的操作,其中一個識別器將接收該手勢;如果使用者執行的操作既不是點選也不是長按,則兩者都不會接收該手勢。

所有這些手勢檢測器都會監聽流經的指標事件流,並嘗試識別特定手勢。GestureDetector 元件會根據其哪些回撥函式非空(non-null),來決定嘗試識別哪些手勢。

當螢幕上某個指標對應多個手勢識別器時,框架會透過讓每個識別器加入手勢競技場 (gesture arena) 來確定使用者意圖,從而消除歧義。手勢競技場使用以下規則確定勝出者:

  • 在任何時候,識別器都可以自我淘汰並離開競技場。如果競技場中只剩下一個識別器,該識別器勝出。

  • 在任何時候,識別器都可以宣佈自己勝出,從而導致所有剩餘的識別器失敗。

例如,在區分水平拖拽和垂直拖拽時,兩個識別器在接收到指標按下事件時都會進入競技場。識別器會觀察指標移動事件。如果使用者在水平方向上移動指標超過一定數量的邏輯畫素,水平識別器就會宣佈勝出,手勢被解釋為水平拖拽。同樣,如果使用者在垂直方向上移動超過一定數量的邏輯畫素,垂直識別器就會宣佈自己勝出。

當只有水平(或垂直)拖拽識別器時,手勢競技場也非常有用。在這種情況下,競技場中只有一個識別器,水平拖拽會立即被識別,這意味著水平移動的第一畫素就可以被視為拖拽,使用者無需等待進一步的手勢消歧。