- 原文地址:Flutter Deep Dive: Gestures
- 原文做者:Nash
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:MeFelixWang
- 校對者:HaoChuan9421
Flutter 提供了一些很是棒的預製組件,用於處理觸摸事件,如 in InkWell
和 InkResponse
。用這些組件包裹住你的組件,它們就可以響應觸摸事件了。除此以外,它還會向你的組件添加 Material 風格的飛濺效果。例如,當從組件的邊界延伸出來時,InkResponse
能夠選擇控制飛濺的形狀和剪裁效果。有趣的是 InkWell
和 InkResponse
不會作任何渲染,而是更新父級的 Material 組件。一個常見的例子是圖片。若是用 inkEll
將圖片包裹起來,你會注意到紋波並不可見。這是由於它是在 Material 上的圖片後面繪製的。想讓 Ink
飛濺效果可見,能夠用 Ink.Image
包裹住圖片。雖然這對大多數任務來講頗有用,但若是你想捕獲更多事件,例如當用戶拖動屏幕時,則應該使用 GestureDetector
。前端
簡單來講手勢檢測器是一個無狀態組件,其構造函數中的參數可用於不一樣的觸摸事件。值得注意的是,你不能同時使用 Pan
和 Scale
,由於 Scale
是 Pan
的一個超集。GestureDetector
純粹用於檢測手勢,所以不會給出任何視覺反應(不存在 Material Ink 傳播)。android
下面是一張表格,展現了 GestureDetector
提供的不一樣回調以及對應的簡短描述:ios
屬性/回調 | 描述 |
---|---|
onTapDown |
每次用戶與屏幕聯繫時都會觸發 OnTapDown 。 |
onTapUp |
當用戶中止觸摸屏幕時,onTapUp 被調用。 |
onTap |
當短暫觸摸屏幕時,onTap 被觸發。 |
onTapCancel |
當用戶觸摸屏幕但未完成 Tap 時,將觸發此事件。 |
onDoubleTap |
當屏幕被快速連續觸摸兩次時調用 onDoubleTap 。 |
onLongPress |
用戶觸摸屏幕超過 500毫秒 時,onLongPress 被觸發。 |
onVerticalDragDown |
當指針與屏幕接觸並開始沿垂直方向移動時,onVerticalDown 被調用。 |
onVerticalDragStart |
當指針 開始 沿垂直方向移動時調用 onVerticalDragStart 。 |
onVerticalDragUpdate |
每次指針在屏幕上的位置發生變化時都會調用此方法。 |
onVerticalDragEnd |
當用戶中止移動時,拖動被認爲是完成的,將調用此事件。 |
onVerticalDragCancel |
當用戶忽然中止拖動時調用。 |
onHorizontalDragDown |
當用戶/指針與屏幕接觸並開始水平移動時調用。 |
onHorizontalDragStart |
用戶/指針已與屏幕接觸並 開始 沿水平方向移動。 |
onHorizontalDragUpdate |
每次指針在水平方向/x軸上的位置發生變化時調用。 |
onHorizontalDragEnd |
在水平拖動結束時,將調用此事件。 |
onHorizontalDragCancel |
當指針未成功觸發 onHorizontalDragDown 時調用。 |
onPanDown |
當指針與屏幕接觸時調用。 |
onPanStart |
指針事件開始移動時,onPanStart 觸發。 |
onPanUpdate |
每次指針改變位置時,調用 onPanUpdate 。 |
onPanEnd |
平移完成後,將調用此事件。 |
onScaleStart |
當指針與屏幕接觸並創建 1.0 的焦點時,將調用此事件。 |
onScaleUpdate |
與屏幕接觸的指針指示了新的焦點。 |
onScaleEnd |
當指針再也不與指示手勢結束的屏幕接觸時調用。 |
GestureDetector
會根據哪一個回調非空來決定嘗試識別哪些手勢。這頗有用,由於若是你須要禁用手勢,則須要傳入 null。git
讓咱們以 **onTap**
手勢爲例,肯定如何處理 **GestureDetector**
。github
首先,咱們使用 onTap
回調建立一個 GestureDetector,由於是非 null,當發生 tap 事件時 GestureDetector
會使用咱們的回調。在 GestureDetector
內部,建立了一個 Gesture Factory 。Gesture Recognizer
會作大量工做來肯定正在處理什麼手勢。這個過程對於 GestureDetector
提供的全部回調來講是相同的。GestureFactories
隨後會被傳遞到 RawGestureDetector
。後端
RawGestureDetector
會爲檢測手勢作大量工做。它是一個 有狀態組件 ,當狀態改變時會同步全部手勢,處理識別器,獲取發生的全部 指針事件 並將其發送到註冊的識別器。而後它們將在 手勢競技場 中一決雌雄。bash
RawGestureDetectorbuild
構建方法由一個 用於監聽指針事件的基類 Listener
組成。若是你想使用來自平臺的原始輸入,如向上,向下或取消事件,這是你的首選類。Listener
不會給你任何手勢,只有基本的 onPointerDown
,onPointerUp
,onPointerMove
和 onPointerCancel
事件。一切都必須手動處理,包括向 手勢競技場 報告本身。若是不這樣作,那麼你不會得到自動取消,也沒法參與那裏發生的交互。這是 組件端 的最底層。併發
Listener
是一個 SingleChildRenderObjectWidget
,由繼承自 RenderProxyBoxWithHitTestBehavior
的類 RenderPointerListener
組成的,這意味着它會模仿其子類的屬性,同時容許自定義 HitTestBehavior
。若是你想了解渲染盒及其運做方式的更多信息,請閱讀 Norbert Kozsir 撰寫的這篇文章。框架
HitTestBehaviour
有三個選項,deferToChild
,opaque
和 translucent
。這些來自 GestureDetector
,且能夠在其中進行配置。DeferToChild
將事件沿着組件樹向下傳遞,這也是 默認行爲 。Opaque
會防止後臺組件接收事件,而 Translucent
則容許後臺組件接收事件。less
讓咱們暫時想象一下你有一個嵌套列表的狀況,你想要同時滾動它們。爲此,你須要父組件和子組件都接收到指針。你配置命中測試行爲,使其是半透明的,確保兩個組件都接收到事件,但事情卻不按計劃進行...爲何?
上述問題的答案就是 GestureArena
。
GestureArena
被用於 手勢消歧 。全部識別器都會在這裏一決雌雄併發送出去。在屏幕上的任何給定點處,能夠存在多個手勢識別器。競技場會考慮用戶觸摸屏幕的時長,斜率以及拖動方向來肯定勝利者。
父列表和子列表都會將其識別器發送到競技場,但(在撰寫本文時)只有一個會贏,並且它剛好老是子列表。
修復方法是使用 GestureFactory
的同時使用 RawGestureDetector
來改變競技場的表現。
舉個例子,讓咱們建立一個由兩個容器組成的簡單應用程序。目標是讓子容器和父容器都接收到手勢。
用 RawGestureDetector
將兩個容器都包裹起來。接下來,咱們將建立一個自定義手勢識別器 AllowMultipleGestureRecognizer
。GestureRecognizer
是全部其餘識別器繼承的基類。它爲類提供基礎 API ,以便它們可以與手勢識別器一塊兒工做/交互。值得注意的是,GestureRecognizer
並不關心識別器自己的具體細節。
// 自定義手勢識別器。
// 重寫 rejectGesture()。當一個手勢被拒絕時,將調用此函數。默認狀況下,它會處理
// 識別器並進行清理。可是咱們修改了它,它其實是手動添加的,以代替識別器被處理。
// 結果是你將有兩個識別器在競技場中獲勝。這是共贏。
class AllowMultipleGestureRecognizer extends TapGestureRecognizer {
@override
void rejectGesture(int pointer) {
acceptGesture(pointer);
}
}
複製代碼
在上面的代碼中,咱們正在建立一個繼承自 TapGestureRecognizer
的自定義類 AllowMultipleGestureRecognizer
。這意味着它可以繼承 TapGestureRecognizer
。在這個例子中,咱們重寫了 rejectGesture
,使之不是處理識別器,而是手動接受。
如今咱們將 GestureRecognizerFactoryWithHandlers
中的自定義手勢識別器傳遞給 RawGestureDetector
。
Widget build(BuildContext context) {
return RawGestureDetector(
gestures: {
AllowMultipleGestureRecognizer: GestureRecognizerFactoryWithHandlers<
AllowMultipleGestureRecognizer>(
() => AllowMultipleGestureRecognizer(), //構造函數
(AllowMultipleGestureRecognizer instance) { //初始化器
instance.onTap = () => print('Episode 4 is best! (parent container) ');
},
)
},
複製代碼
如今咱們將 GestureRecognizerFactoryWithHandlers
中的自定義手勢識別器傳遞給 RawGestureDetector
。工廠函數須要兩個屬性,構造函數和初始化器,用於構造和初始化手勢識別器。咱們使用 lambda 傳遞這些參數。如上面的代碼所述,構造函數返回 AllowMultipleGestureRecognizer
的一個新實例,而初始化器則獲取用於監聽 tap 並將一些文本打印到控制檯的屬性 instance
。兩個容器將重複這一過程,惟一的區別是打印的文本。
如下是示例應用的完整源碼:
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
//主函數。 Flutter 應用的入口
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: DemoApp(),
),
),
);
}
// 簡單的演示應用程序,由兩個容器組成。目標是容許多個手勢進入競技場。
// 全部的東西都是經過 `RawGestureDetector` 和自定義 `GestureRecognizer` (繼承自 `TapGestureRecognizer` )
// 將自定義 GestureRecognizer,`AllowMultipleGestureRecognizer` 添加到手勢列表中,並建立一個 `AllowMultipleGestureRecognizer` 類型的 `GestureRecognizerFactoryWithHandlers`。
// 它用給定的回調建立一個手勢識別器工廠函數,在這裏是 `onTap`。
// 它監聽 `onTap` 的一個實例,而後在被調用時向控制檯打印文本。須要注意的是,`RawGestureDetector` 對於兩個容器
// 是相同的。惟一的區別是打印的文本(用來標識組件)。
class DemoApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RawGestureDetector(
gestures: {
AllowMultipleGestureRecognizer: GestureRecognizerFactoryWithHandlers<
AllowMultipleGestureRecognizer>(
() => AllowMultipleGestureRecognizer(),
(AllowMultipleGestureRecognizer instance) {
instance.onTap = () => print('Episode 4 is best! (parent container) ');
},
)
},
behavior: HitTestBehavior.opaque,
//父容器
child: Container(
color: Colors.blueAccent,
child: Center(
//用 RawGestureDetector 將兩個容器包裹起來
child: RawGestureDetector(
gestures: {
AllowMultipleGestureRecognizer:
GestureRecognizerFactoryWithHandlers<
AllowMultipleGestureRecognizer>(
() => AllowMultipleGestureRecognizer(), //構造函數
(AllowMultipleGestureRecognizer instance) { //初始化器
instance.onTap = () => print('Episode 8 is best! (nested container)');
},
)
},
//在第一個容器中建立嵌套容器。
child: Container(
color: Colors.yellowAccent,
width: 300.0,
height: 400.0,
),
),
),
),
);
}
}
// 自定義手勢識別器。
// 重寫 rejectGesture()。當一個手勢被拒絕時,將調用此函數。默認狀況下,它會處理
// 識別器並進行清理。可是咱們修改了它,它其實是手動添加的,以代替識別器被處理。
// 結果是你將有兩個識別器在競技場中獲勝。這是共贏。
class AllowMultipleGestureRecognizer extends TapGestureRecognizer {
@override
void rejectGesture(int pointer) {
acceptGesture(pointer);
}
}
複製代碼
當你點擊黃色容器時,兩個組件都會收到 tap 事件,所以有兩條語句打印到控制檯。
應用程序:
控制檯輸出:
一個手勢獲勝後,競技場將處於 closed
和 swept
狀態。這將丟棄未使用的識別器並重置競技場。而後由勝利手勢執行動做。
回到咱們的 Tap 示例,在此以後,映射到 onTap
的函數如今將被執行。
今天咱們瞭解了 Flutter 框架如何處理手勢。咱們首先了解了 Flutter 爲處理 taps 和其餘觸摸事件提供的夢幻般的預製組件。接下來,咱們討論了 GestureDetector
並實驗了其內部工做方式。經過使用示例,咱們瞭解了 Flutter 如何處理 Tap 手勢。咱們穿過了 RawGestureDetector
這片土地,聆聽了 Listener
的聲音,並向名爲 GestureArena
的神祕的 Flutter 搏擊俱樂部致敬。
最後,咱們從應用程序的角度介紹了 Flutter 中的大部分手勢系統。有了這些知識,你如今應該對如何獲取屏幕上的觸摸並在幕後進行處理有了更好地理解。若是你有任何問題或疑慮,請隨時發表評論或經過 Twitterverse 與我聯繫。
一樣 很是 感謝Simon Lightfoot(又名「Flutter Whisperer」)對本文的貢獻❤
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。