Flutter 交互基礎之 GestureDetector 手勢

介紹

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

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

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

手勢

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

  • 單擊less

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

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

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

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

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

繼承關係

Object —> Diagnosticable —> DiagnosticableTree —> Widget —> StatelessWidget —> GestureDetectorspa

構造函數

Draggable({Key key, 
 @required Widget child,
 @required Widget feedback,
 T data, 
 Axis axis,
 Widget childWhenDragging,
 Offset feedbackOffset: Offset.zero, 
 DragAnchor dragAnchor: DragAnchor.child, 
 Axis affinity, 
 int maxSimultaneousDrags,
 VoidCallback onDragStarted, 
 DraggableCanceledCallback onDraggableCanceled, 
 DragEndCallback onDragEnd, 
 VoidCallback onDragCompleted, 
 bool ignoringFeedbackSemantics: true }) 
複製代碼

經常使用屬性

  • 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 = 200.0; //經過修改圖片寬度來達到縮放效果

  @override
  Widget build(BuildContext context) {
   return Center(
     child: GestureDetector(
        //指定寬度,高度自適應
        child: Image.asset("./images/sea.png", width: _width),
        onScaleUpdate: (ScaleUpdateDetails details) {
          setState(() {
            //縮放倍數在0.8到10倍之間
            _width=200*details.scale.clamp(.8, 10.0);
          });
        },
      ),
   );
  }
}
複製代碼

效果圖以下:

Scale

總結

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

做者


xiaosongzeem
相關文章
相關標籤/搜索