Flutter 使用者互動事件的響應

移動的小太陽發表於2021-03-28

Flutter 使用者互動事件的響應

在APP 開發中,如何響應使用者互動事件是一個很重要的部分,在原生的Android 開發中,通過給view新增事件監聽器來完成事件的監聽。在Flutter 是如何監聽和響應使用者的手勢操作的呢?

手勢操作在 Flutter 中分為兩類:

  1. 第一類是原始的指標事件(Pointer Event),即原生開發中常見的觸控事件,表示螢幕上觸控(或滑鼠、手寫筆)行為觸發的位移行為;
  2. 第二類則是手勢識別(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提供了豐富的方法來完成各種事件的監聽。

在父子關係的檢視中,通常最終會確認由子檢視來響應事件。而這也是合乎常理的:從視覺效果上看,子檢視的檢視層級位於父檢視之上,相當於對其進行了遮擋,因此從事件處理上看,子檢視自然是事件響應的第一責任人。當有時候需要付檢視也能響應到互動事件,改怎麼辦呢?

為了讓父容器也能接收到手勢,我們需要同時使用 RawGestureDetectorGestureFactory,來改變競技場決定由誰來響應使用者事件的結果。

在Flutter 會使用手勢競技場來進行各個手勢的 PK,以保證最終只會有一個手勢能夠響應使用者行為。如果我們希望同時能有多個手勢去響應使用者行為,需要去自定義手勢,利用 RawGestureDetector 和手勢工廠類,在競技場 PK 失敗時,手動把它復活。

步驟:

  1. 定義了一個繼承自點選手勢識別器 TapGestureRecognizer 的類,並重寫了其 rejectGesture 方法,手動地把自己又復活了
  2. 需要將手勢識別器和其工廠類傳遞給 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,
              ),
            ),
          ),
        ),
      ),
    );
  }
}
複製程式碼

image.png 可以看到在點選中間的綠色控制元件,外部也可以接收到了點選事件了。

總結

在 Flutter中 使用者互動的事件分為兩種,一種是 指標事件,通過Listener可以監聽到手勢的按下、抬起、移動;第二種是 手勢事件,可以可以通過GestureDetector監聽到長按、雙擊、拖拽 等複雜的手勢事件。當需要父容器也能響應到子控制元件的事件,通過自定義手勢識別器,利用 RawGestureDetector 和手勢工廠類,在競技場 PK 失敗時,手動把它復活。

相關文章