Flutter 基礎之 GestureDetector 手勢 (交互模型篇)

聲明:本篇文章已授權微信公衆號 YYGeeker 獨家發佈。
博主原創文章,轉載請註明出處:小嵩的博客

介紹

GestureDetector是Flutter的手勢檢測器,它會嘗試識別與其非null的回調相對應的手勢。如果此Widget組件具有子控件,那麼它的大小調整行爲將遵從該子控件件。如果它沒有子控件,那麼它將變大以適合父控件。

默認情況下,帶有不可見子控件的手勢檢測器會忽略觸摸;可以通過行爲控制此邏輯。GestureDetector還監聽可訪問性事件,並將其映射到callback回調。若要忽略可訪問性事件,請將ExcludeFromSemantics設置爲true。

Flutter中的手勢系統有兩個獨立的層。第一層爲原始指針(pointer)事件,它描述了屏幕上指針(例如,觸摸、鼠標和觸控筆)的位置和移動。 第二層爲手勢,描述由一個或多個指針移動組成的語義動作,如拖拽、縮放、雙擊、長按等。

手勢

在flutter中,手勢表示可以從多個單獨的指針事件(甚至可能是多個單獨的指針)識別的語義動作(例如點擊,拖拽和縮放)。 完整的一個手勢可以分發多個事件,對應於手勢的生命週期(例如,拖拽開始,拖拽更新和拖拽結束):

  • 單擊

    • onTapDown 指針已經在特定位置與屏幕接觸
    • onTapUp 指針停止在特定位置與屏幕接觸
    • onTap 單擊事件觸發
    • onTapCancel 先前指針觸發的onTapDown不會在觸發單擊事件
  • 雙擊

    • onDoubleTap 用戶快速連續兩次在同一位置輕敲屏幕.
  • 長按

    • onLongPress 指針在相同位置長時間保持與屏幕接觸
  • 垂直拖拽

    • onVerticalDragStart 指針已經與屏幕接觸並可能開始垂直移動
    • onVerticalDragUpdate 指針與屏幕接觸並已沿垂直方向移動.
    • onVerticalDragEnd 先前與屏幕接觸並垂直移動的指針不再與屏幕接觸,並且在停止接觸屏幕時以特定速度移動
  • 水平拖拽

    • onHorizontalDragStart 指針已經接觸到屏幕並可能開始水平移動
    • onHorizontalDragUpdate 指針與屏幕接觸並已沿水平方向移動
    • onHorizontalDragEnd 先前與屏幕接觸並水平移動的指針不再與屏幕接觸,並在停止接觸屏幕時以特定速度移動

繼承關係

Object —> Diagnosticable —> DiagnosticableTree —> Widget —> StatelessWidget —> GestureDetector

構造函數

GestureDetector({Key key, Widget child, GestureTapDownCallback onTapDown, GestureTapUpCallback onTapUp, GestureTapCallback onTap, GestureTapCancelCallback onTapCancel, GestureTapDownCallback onSecondaryTapDown, GestureTapUpCallback onSecondaryTapUp, GestureTapCancelCallback onSecondaryTapCancel, GestureTapCallback onDoubleTap, GestureLongPressCallback onLongPress, GestureLongPressStartCallback onLongPressStart, GestureLongPressMoveUpdateCallback onLongPressMoveUpdate, GestureLongPressUpCallback onLongPressUp, GestureLongPressEndCallback onLongPressEnd, GestureDragDownCallback onVerticalDragDown, GestureDragStartCallback onVerticalDragStart, GestureDragUpdateCallback onVerticalDragUpdate, GestureDragEndCallback onVerticalDragEnd, GestureDragCancelCallback onVerticalDragCancel, GestureDragDownCallback onHorizontalDragDown, GestureDragStartCallback onHorizontalDragStart, GestureDragUpdateCallback onHorizontalDragUpdate, GestureDragEndCallback onHorizontalDragEnd, GestureDragCancelCallback onHorizontalDragCancel, GestureForcePressStartCallback onForcePressStart, GestureForcePressPeakCallback onForcePressPeak, GestureForcePressUpdateCallback onForcePressUpdate, GestureForcePressEndCallback onForcePressEnd, GestureDragDownCallback onPanDown, GestureDragStartCallback onPanStart, GestureDragUpdateCallback onPanUpdate, GestureDragEndCallback onPanEnd, GestureDragCancelCallback onPanCancel, GestureScaleStartCallback onScaleStart, GestureScaleUpdateCallback onScaleUpdate, GestureScaleEndCallback onScaleEnd, HitTestBehavior behavior, bool excludeFromSemantics: false, DragStartBehavior dragStartBehavior: DragStartBehavior.start })

常用屬性

  • dragStartBehavior → DragStartBehavior
    確定處理拖拽開始行爲的方式

  • excludeFromSemantics → bool 是否從語義樹中排除這些手勢。

    例如,用於顯示工具提示的長按手勢被排除,因爲工具提示本身直接包含在語義樹中,因此具有顯示它的手勢將導致信息的重複。

  • onDoubleTap → GestureTapCallback 用戶已快速連續兩次在同一位置使用主按鈕輕觸屏幕。

  • onForcePressEnd → GestureForcePressEndCallback 指針不再與屏幕接觸。

  • onForcePressPeak → GestureForcePressPeakCallback 指針與屏幕接觸並以最大力按下。力量至少是 ForcePressGestureRecognizer.peakPressure。

  • onForcePressStart → GestureForcePressStartCallback 指針與屏幕接觸,並用足夠的力按壓以啓動壓力。力量至少是 ForcePressGestureRecognizer.startPressure。

  • onForcePressUpdate → GestureForcePressUpdateCallback 指針與屏幕接觸,之前已經通過了 ForcePressGestureRecognizer.startPressure,並且要麼在屏幕的平面上移動,要麼用不同的力按壓屏幕,要麼同時按兩個屏幕。

  • onHorizontalDragCancel → GestureDragCancelCallback
    先前觸發onHorizontalDragDown的指針未完成觸發了取消。

  • onHorizontalDragDown → GestureDragDownCallback
    指針已通過主按鈕與屏幕接觸,並可能開始水平移動。

  • onHorizontalDragEnd → GestureDragEndCallback
    之前與主屏幕接觸並且水平移動的指針不再與屏幕接觸,並且當它停止接觸屏幕時以特定速度移動。

  • onHorizontalDragStart → GestureDragStartCallback
    指針已通過主按鈕與屏幕接觸,並開始水平移動。

  • onHorizontalDragUpdate → GestureDragUpdateCallback
    與主按鈕接觸並且水平移動的指針在水平方向上移動。

  • onLongPress → GestureLongPressCallback
    當識別出具有主按鈕的長按手勢時調用。

  • onLongPressEnd → GestureLongPressEndCallback
    使用主按鈕觸發長按的指針已停止接觸屏幕。

  • onLongPressMoveUpdate → GestureLongPressMoveUpdateCallback
    使用主按鈕長按後,指針已被拖動。

  • onLongPressStart → GestureLongPressStartCallback
    當識別出具有主按鈕的長按手勢時調用。

  • onLongPressUp → GestureLongPressUpCallback
    使用主按鈕觸發長按的指針已停止接觸屏幕。

  • onPanCancel → GestureDragCancelCallback
    先前觸發onPanDown的指針未完成。

  • onPanDown → GestureDragDownCallback
    指針已通過主按鈕與屏幕接觸,並可能開始移動。

  • onPanEnd → GestureDragEndCallback
    先前通過主按鈕與屏幕接觸並且移動的指針不再與屏幕接觸,並且當它停止接觸屏幕時以特定速度移動。

  • onPanStart → GestureDragStartCallback
    觸摸點與屏幕接觸,並已開始移動。

  • onPanUpdate → GestureDragUpdateCallback
    屏幕上的觸摸點位置每次改變時,都會觸發該回調。

  • onScaleEnd → GestureScaleEndCallback
    指針不再與屏幕接觸。

  • onScaleStart → GestureScaleStartCallback
    與屏幕接觸的指針已建立焦點,初始比例爲1.0。

  • onScaleUpdate → GestureScaleUpdateCallback
    與屏幕接觸的指針表示新的焦點和/或比例。

  • onSecondaryTapCancel → GestureTapCancelCallback
    先前觸發onSecondaryTapDown的指針不會最終導致點擊。

  • onSecondaryTapDown → GestureTapDownCallback
    可能導致使用輔助按鈕敲擊的指針已在特定位置與屏幕聯繫。

  • onSecondaryTapUp → GestureTapUpCallback
    將觸發帶有輔助按鈕的敲擊的指針已停止在特定位置接觸屏幕。

  • onTap → GestureTapCallback
    帶主按鈕的點擊事件觸發源頭。

  • onTapCancel → GestureTapCancelCallback
    先前觸發onTapDown的指針不會導致點擊。

  • onTapDown → GestureTapDownCallback
    可能導致使用主按鈕敲擊的指針已在特定位置與屏幕聯繫。

  • onTapUp → GestureTapUpCallback
    將觸發帶主按鈕的敲擊的指針已停止在特定位置接觸屏幕。

  • onVerticalDragCancel → GestureDragCancelCallback
    先前觸發onVerticalDragDown的指針未完成。

  • onVerticalDragDown → GestureDragDownCallback
    指針已通過主按鈕與屏幕接觸,並可能開始垂直移動。

  • onVerticalDragEnd → GestureDragEndCallback
    先前與主屏幕接觸並且垂直移動的指針不再與屏幕接觸,並且當它停止接觸屏幕時以特定速度移動。

  • onVerticalDragStart → GestureDragStartCallback
    指針已通過主按鈕與屏幕接觸,並已開始垂直移動。

  • onVerticalDragUpdate → GestureDragUpdateCallback
    與主按鈕接觸並且垂直移動的指針在垂直方向上移動。

常用方法

  • build(BuildContext context) → Widget
    創建組件。
  • debugFillProperties(DiagnosticPropertiesBuilder 屬性) →void
    添加與節點關聯的其他屬性。
  • createElement() → StatelessElement
    創建StatelessElement以管理此組件在UI樹中的位置。

使用示例

1. 單擊、雙擊、長按事件

這裏通過一個demo,用GestureDetector對Container組件進行手勢識別。在觸發相應事件後,Container上顯示事件名,爲了增大點擊區域,將Container設置爲200×200,代碼如下:

import 'package:flutter/material.dart';

class GestureDetectorTestRoute extends StatefulWidget {
  @override
  _GestureDetectorTestRouteState createState() =>
      new _GestureDetectorTestRouteState();
}

class _GestureDetectorTestRouteState extends State<GestureDetectorTestRoute> {
  String _operation = "No Gesture detected!"; 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
        title: Text("GestureDetectorTest"),
      ),
      body: Center(
        child: GestureDetector(
          child: Container(
            alignment: Alignment.center,
            color: Colors.blue,
            width: 200.0,
            height: 200.0,
            child: Text(
              _operation,
              style: TextStyle(
                  color: Colors.white,
                  fontSize: 14,
                  decoration: TextDecoration.none),
            ),
          ),
          onTap: () => updateText("onTap"), //單擊
          onDoubleTap: () => updateText("onDoubleTap"), //雙擊
          onLongPress: () => updateText("onLongPress"), //長按
        ),
      ),
    );
  }

  //更新文本
  void updateText(String text) {
    setState(() {
      _operation = text;
    });
    //提示
    showSnackBar(text);
  }

  var _scaffoldKey = new GlobalKey<ScaffoldState>();

  void showSnackBar(String message) {
    var snackBar = SnackBar(
        content: Text(message),
        backgroundColor: Colors.lightGreen,
        duration: Duration(milliseconds: 400));
    _scaffoldKey.currentState.showSnackBar(snackBar);
  }
}

效果圖如下:

GestureDetector

注意: 若同時監聽onTap和onDoubleTap事件,當用戶觸發tap事件時,會有200毫秒左右的延時。這是因爲當用戶點擊完之後很可能會再次點擊以觸發雙擊事件,所以GestureDetector源碼內部會等200毫秒時間來確定是否爲雙擊事件。如果用戶只監聽了onTap(沒有監聽onDoubleTap)事件則回調沒有延時。

2. 拖拽、滑動事件

import 'package:flutter/material.dart';

class DragTest extends StatefulWidget {
  @override
  _DragTestState createState() => new _DragTestState();
}

class _DragTestState extends State<DragTest>
    with SingleTickerProviderStateMixin {
  double _top = 0.0; //距頂部的偏移
  double _left = 0.0; //距左邊的偏移

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("DragTest"),
      ),
      body: Stack(
        children: <Widget>[
          Positioned(
            top: _top,
            left: _left,
            child: GestureDetector(
              child: CircleAvatar(
                  child: Text("Draggable Text", textAlign: TextAlign.center),
                  radius: 50),
              //手指按下時會觸發此回調
              onPanDown: (DragDownDetails e) {
                //打印手指按下的位置(屏幕)
                print("用戶手指按下:${e.globalPosition}");
              },
              //手指滑動時會觸發此回調
              onPanUpdate: (DragUpdateDetails e) {
                //用戶手指滑動時,更新偏移,重新構建
                setState(() {
                  _left += e.delta.dx;
                  _top += e.delta.dy;
                });
              },
              onPanEnd: (DragEndDetails e) {
                //打印滑動結束時在x、y軸上的速度
                print(e.velocity);
              },
            ),
          )
        ],
      ),
    );
  }
}

效果圖如下:

Drag

3. 縮放事件

除了單擊/雙擊/拖拽等事件,GestureDetector可以監聽縮放事件。下面我們演示一個簡單的圖片縮放效果,示例代碼如下:

class _ScaleTestRouteState extends State<_ScaleTestRoute> {
  double _width = 240.0; //通過修改圖片寬度來達到縮放效果

  @override
  Widget build(BuildContext context) {
   return Center(
     child: GestureDetector(
        //指定寬度,高度自適應
        child: Image.asset("./images/sea.png", width: _width),
        onScaleUpdate: (ScaleUpdateDetails details) {
          setState(() {
           //縮放倍數在0.2到20倍之間
            _width = 240 * details.scale.clamp(0.5, 20.0);
          });
        },
      ),
   );
  }
}

效果圖如下:
Scale

總結

通過這篇文章,我們瞭解了flutter 所提供的GestureDetector手勢檢測器的一些基本概念及能力,它內部封裝了諸多API,讓我們可以很高效快速開發應用。通過文章的內容講述,我們知道如何去監聽單擊/雙擊/拖拽等事件及處理用戶的交互邏輯。當然,觸摸交互模型裏面不可避免地會出現事件競爭和事件衝突問題,本篇文章由於篇幅問題就沒有進行講解了,下篇文章我們將會側重講述flutter中的事件競爭及衝突問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章