Flutter 使用者互動事件的響應
在APP 開發中,如何響應使用者互動事件是一個很重要的部分,在原生的Android 開發中,通過給view新增事件監聽器來完成事件的監聽。在Flutter 是如何監聽和響應使用者的手勢操作的呢?
手勢操作在 Flutter 中分為兩類:
- 第一類是原始的指標事件(Pointer Event),即原生開發中常見的觸控事件,表示螢幕上觸控(或滑鼠、手寫筆)行為觸發的位移行為;
- 第二類則是手勢識別(Gesture Detector),表示多個原始指標事件的組合操作,如點選、雙擊、長按等,是指標事件的語義化封裝。
指標事件
指標事件指的是,使用者的手指在控制元件上按下、移動和抬起事件。在Flutter 中事件的分發機制和原生的一樣都是冒泡機制類似,事件會從這個最內層的元件開始,沿著元件樹向根節點向上冒泡分發。
關於元件層面的原始指標事件的監聽,Flutter 提供了 Listener Widget,可以監聽其子 Widget 的原始指標事件。 看一個例子:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("指標事件"),
),
body: Listener(
child: Container(
width: 350,
height: 350,
color: Colors.yellow,
),
onPointerDown: (event) => print("this is onPointerDown"),
onPointerMove: (event) => print("this is onPointerMove"),
onPointerUp: (event) => print("this is onPointerUp"),
),
);
}
}
複製程式碼
給 Scaffold包裹了一個 Listener來監聽 指標事件,通過 onPointerDown、onPointerMove、onPointerUp方法進行結果回撥。
結果
flutter: this is onPointerDown
flutter: this is onPointerUp
flutter: this is onPointerDown
flutter: this is onPointerMove
flutter: this is onPointerMove
flutter: this is onPointerMove
flutter: this is onPointerMove
flutter: this is onPointerMove
flutter: this is onPointerUp
複製程式碼
手勢識別
雖然通過 Listener widget 可以完成對指標事件的監聽,但是對於一些比較複雜的互動如,長按、雙擊、拖拽 等,還是通過指標事件來監聽處理就很麻煩了。這時候Flutter 給我們提供了 GestureDetector。GestureDetector 是一個處理各種高階使用者觸控行為的 Widget,與 Listener 一樣,也是一個功能性元件。
下面看看一個例子:在 Stack 佈局上新增一個 綠色方塊,然後使之隨著手指滑動而滑動。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("GestureDetector 手勢監聽"),
),
body: Stack(
children: [
Positioned(
top: _top,
left: _left,
child: GestureDetector(
child: Container(
color: Colors.green,
width: 60,
height: 60,
),
onTap: () => print("onTap"),
//點選回撥
onDoubleTap: () => print("onDoubleTap"),
//雙擊回撥
onLongPress: () => print("onLongPress"),
//長按回撥
onPanUpdate: (e) {
setState(() {
_left = _left + e.delta.dx;
_top = _top + e.delta.dy;
});
},
)),
],
),
);
複製程式碼
可以看到 GestureDetector提供了豐富的方法來完成各種事件的監聽。
在父子關係的檢視中,通常最終會確認由子檢視來響應事件。而這也是合乎常理的:從視覺效果上看,子檢視的檢視層級位於父檢視之上,相當於對其進行了遮擋,因此從事件處理上看,子檢視自然是事件響應的第一責任人。當有時候需要付檢視也能響應到互動事件,改怎麼辦呢?
為了讓父容器也能接收到手勢,我們需要同時使用 RawGestureDetector
和 GestureFactory
,來改變競技場決定由誰來響應使用者事件的結果。
在Flutter 會使用手勢競技場來進行各個手勢的 PK,以保證最終只會有一個手勢能夠響應使用者行為。如果我們希望同時能有多個手勢去響應使用者行為,需要去自定義手勢,利用 RawGestureDetector 和手勢工廠類,在競技場 PK 失敗時,手動把它復活。
步驟:
- 定義了一個繼承自點選手勢識別器 TapGestureRecognizer 的類,並重寫了其 rejectGesture 方法,手動地把自己又復活了
- 需要將手勢識別器和其工廠類傳遞給 RawGestureDetector,以便使用者產生手勢互動事件時能夠立刻找到對應的識別方法。
下面看看一個例子:
//1.定義了一個繼承自點選手勢識別器 TapGestureRecognizer 的類,並重寫了其 rejectGesture 方法,手動地把自己又復活了
class MultipleTapGestureRecognizer extends TapGestureRecognizer {
@override
void rejectGesture(int pointer) {
acceptGesture(pointer);
}
}
//2. 接下來,我們需要將手勢識別器和其工廠類傳遞給 RawGestureDetector,以便使用者產生手勢互動事件時能夠立刻找到對應的識別方法。
//RawGestureDetector 的初始化函式所做的配置工作,就是定義不同手勢識別器和其工廠類的對映關係。
// 這裡,由於我們只需要處理點選事件,所以只配置一個識別器即可。
// 工廠類的初始化採用 GestureRecognizerFactoryWithHandlers 函式完成,這個函式提供了手勢識別物件建立,以及對應的初始化入口。
class UserEvenDemo3 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("父容器也能響應到事件"),
),
body: RawGestureDetector(
gestures: {
// 通過 GestureRecognizerFactoryWithHandlers 返回自定的手勢識別器
MultipleTapGestureRecognizer: GestureRecognizerFactoryWithHandlers<
MultipleTapGestureRecognizer>(
() => MultipleTapGestureRecognizer(),
(MultipleTapGestureRecognizer instance) {
// 處理對呀的手勢事件
instance.onTap = () => print('Parent tapped'); //父檢視的點選回撥
})
},
child: Container(
color: Colors.pinkAccent,
child: Center(
child: GestureDetector(
onTap: () => print('Child tapped'), //子檢視的點選回撥
child: Container(
color: Colors.blueAccent,
width: 200.0,
height: 200.0,
),
),
),
),
),
);
}
}
複製程式碼
可以看到在點選中間的綠色控制元件,外部也可以接收到了點選事件了。
總結
在 Flutter中 使用者互動的事件分為兩種,一種是 指標事件,通過Listener可以監聽到手勢的按下、抬起、移動;第二種是 手勢事件,可以可以通過GestureDetector監聽到長按、雙擊、拖拽 等複雜的手勢事件。當需要父容器也能響應到子控制元件的事件,通過自定義手勢識別器,利用 RawGestureDetector 和手勢工廠類,在競技場 PK 失敗時,手動把它復活。