# 点击、拖动和其他手势

> Flutter 是如何实现点击和拖动这样类似的手势识别的。



This document explains how to listen for, and respond to,
_gestures_ in Flutter.
Examples of gestures include taps, drags, and scaling.

这个章节将会讲解如何监听和响应 Flutter 的手势操作 **gestures**。 
典型的手势操作包括点击、拖动和缩放。

The gesture system in Flutter has two separate layers.
The first layer has raw pointer events that describe
the location and movement of pointers (for example,
touches, mice, and styli) across the screen.
The second layer has _gestures_ that describe semantic
actions that consist of one or more pointer movements.

Flutter 中的手势有两个不同的层次：第一层是原始的指针指向事件，
描述了屏幕上由触摸板、鼠标、指示笔等触发的指针的位置和移动。
第二层包含 **gestures**，描述了由上述一个或多个指针移动组成的具有特殊语义的操作。


## Pointers

## 指针

Pointers represent raw data about the user's interaction
with the device's screen.
There are four types of pointer events:

Pointer 代表的是人机界面交互的原始数据。
一共有四种指针事件：

[`PointerDownEvent`][]
<br> The pointer has contacted the screen at a particular location.

[`PointerDownEvent`][]
<br>指针在特定位置与屏幕接触

[`PointerMoveEvent`][]
<br>The pointer has moved from one location on the screen to another.
 
[`PointerMoveEvent`][]
<br>指针从屏幕的一个位置移动到另外一个位置 

[`PointerUpEvent`][]
<br>The pointer has stopped contacting the screen.

[`PointerUpEvent`][]
<br>指针与屏幕停止接触

[`PointerCancelEvent`][]
<br>Input from this pointer is no longer directed towards this app.

[`PointerCancelEvent`][]
<br>指针的输入已经不再指向此应用

On pointer down, the framework does a _hit test_ on your app
to determine which widget exists at the location where the
pointer contacted the screen. The pointer down event
(and subsequent events for that pointer) are then dispatched
to the innermost widget found by the hit test.
From there, the events bubble up the tree and are dispatched
to all the widgets on the path from the innermost
widget to the root of the tree. There is no mechanism for
canceling or stopping pointer events from being dispatched further.

在指针下落事件中，框架做了一个 **hit test** 
的操作确定与屏幕发生接触的位置上有哪些组件以及分发给最内部的组件去响应。
事件会沿着组件树从这个最内部的组件向组件树的根部冒泡分发。
并且不存在用于取消或停止指针事件进行进一步分发的机制。

To listen to pointer events directly from the widgets layer, use a
[`Listener`][] widget. However, generally,
consider using gestures (as discussed below) instead.

使用 [`Listener`](https://api.flutter-io.cn/flutter/widgets/Listener-class.html) 
可以在组件层直接监听指针事件。然而，一般情况下，请考虑使用下面的 gestures 替代。

[`Listener`]: https://api.flutter-io.cn/flutter/widgets/Listener-class.html
[`PointerCancelEvent`]: https://api.flutter-io.cn/flutter/gestures/PointerCancelEvent-class.html
[`PointerDownEvent`]: https://api.flutter-io.cn/flutter/gestures/PointerDownEvent-class.html
[`PointerMoveEvent`]: https://api.flutter-io.cn/flutter/gestures/PointerMoveEvent-class.html
[`PointerUpEvent`]: https://api.flutter-io.cn/flutter/gestures/PointerUpEvent-class.html

## Gestures

## 手势

Gestures represent semantic actions (for example, tap, drag,
and scale) that are recognized from multiple individual pointer
events, potentially even multiple individual pointers.
Gestures can dispatch multiple events, corresponding to the
lifecycle of the gesture (for example, drag start,
drag update, and drag end):

Gesture 代表的是语义操作（比如点击、拖动、缩放）。
通常由一系列单独的指针事件组成，甚至是一系列单独的指针组成。
Gesture 可以分发多种事件，
对应着手势的生命周期（比如开始拖动、拖动更新、结束拖动）。

**Tap**

**点击**

`onTapDown`
<br> A pointer that might cause a tap has contacted the screen at a
 particular location.

`onTapDown`
<br> 指针在发生接触的屏幕的特定位置可能引发点击事件。

`onTapUp`
<br> A pointer that triggers a tap has stopped contacting
  the screen at a particular location.

`onTapUp`
<br> 触发点击事件的触点已经在某个点停止与屏幕交互。

`onTap`
<br> The pointer that previously triggered the `onTapDown`
  has also triggered `onTapUp` which ends up causing a tap.

`onTap`
<br> 触发 `onTapDown` 的触点此时已触发了 `onTapUp`，即产生了点击事件。

`onTapCancel`
<br> The pointer that previously triggered the `onTapDown`
  won't end up causing a tap.

`onTapCancel`
<br> 指针已经触发了 `onTapDown`，但是最终不会形成一个点击事件。

**Double tap**

**双击** 

`onDoubleTap`
<br> The user has tapped the screen at the same location twice in
  quick succession.

`onDoubleTap`
<br> 用户在屏幕的相同位置上快速点击了两次。

**Long press**

**长按**

`onLongPress`
<br> A pointer has remained in contact with the
  screen at the same location for a long period of time.

`onLongPress`
<br> 指针在屏幕的相同位置上保持接触持续一长段时间。

**Vertical drag**

**纵向拖动**

`onVerticalDragStart`
<br> A pointer has contacted the screen and might begin to
  move vertically.

`onVerticalDragStart`
<br> 指针和屏幕产生接触并可能开始纵向移动。

`onVerticalDragUpdate`
<br> A pointer that is in contact with the screen and
    moving vertically has moved in the vertical direction.

`onVerticalDragUpdate`
<br> 指针和屏幕产生接触，在纵向上发生移动并保持移动。

`onVerticalDragEnd`
<br> A pointer that was previously in contact with the screen
    and moving vertically is no longer in contact with the
    screen and was moving at a specific velocity when it
    stopped contacting the screen.

`onVerticalDragEnd`
<br> 指针先前和屏幕产生了接触，以特定速度纵向移动，并且此后不会在屏幕接触上发生纵向移动。

**Horizontal drag**

**横向拖动**

`onHorizontalDragStart`
<br> A pointer has contacted the screen and might begin to
  move horizontally.

`onHorizontalDragStart`
<br> 指针和屏幕产生接触并可能开始横向移动。

`onHorizontalDragUpdate`
<br> A pointer that is in contact with the screen and
  moving horizontally has moved in the horizontal direction.

`onHorizontalDragUpdate`
<br> 指针和屏幕产生接触，在横向上发生移动并保持移动。

`onHorizontalDragEnd`
<br> A pointer that was previously in contact with the
  screen and moving horizontally is no longer in contact
  with the screen and was moving at a specific velocity
  when it stopped contacting the screen.

`onHorizontalDragEnd`
<br> 指针先前和屏幕产生了接触，以特定速度横向移动，并且此后不会在屏幕接触上发生横向移动。

**Pan**

**移动**

`onPanStart`
<br> A pointer has contacted the screen and might begin to move
  horizontally or vertically. This callback crashes if
  `onHorizontalDragStart` or `onVerticalDragStart` is set.

`onPanStart`
<br> 指针和屏幕产生接触并可能开始横向移动或者纵向移动。
  如果设置了 `onHorizontalDragStart` 或者 `onVerticalDragStart`，
  该回调方法会引发崩溃。

`onPanUpdate`
<br> A pointer that is in contact with the screen and is moving
  in the vertical or horizontal direction. This callback
  crashes if `onHorizontalDragUpdate` or `onVerticalDragUpdate`
  is set.

`onPanUpdate`
<br> 指针和屏幕产生接触，在横向或者纵向上发生移动并保持移动。
  如果设置了 `onHorizontalDragUpdate` 或者 `onVerticalDragUpdate`，
  该回调方法会引发崩溃。

`onPanEnd`
<br> A pointer that was previously in contact with screen
  is no longer in contact with the screen and is moving
  at a specific velocity when it stopped contacting the screen.
  This callback crashes if `onHorizontalDragEnd` or
  `onVerticalDragEnd` is set.

`onPanEnd`
<br> 指针先前和屏幕产生了接触，并且以特定速度移动，
  此后不再在屏幕接触上发生移动。
  如果设置了 `onHorizontalDragEnd` 或者 `onVerticalDragEnd`，
  该回调方法会引发崩溃。

### Adding gesture detection to widgets

### 为 widgets 添加手势检测

To listen to gestures from the widgets layer,
use a [`GestureDetector`][].

从组件层监听手势，需要用到 [`GestureDetector`][]。

:::note

To learn more, watch this short
Widget of the Week video on the `GestureDetector` widget:

了解更多，请参考下方「每周 Widget」的里关于 GestureDetector 的短视频：

<YouTubeEmbed id="WhVXkCFPmK4" title="GestureDetector - Flutter widget of the week"></YouTubeEmbed>

:::

If you're using Material Components,
many of those widgets already respond to taps or gestures.
For example, [`IconButton`][] and [`TextButton`][]
respond to presses (taps), and [`ListView`][]
responds to swipes to trigger scrolling.
If you aren't using those widgets, but you want the
"ink splash" effect on a tap, you can use [`InkWell`][].

如果使用 Material 风格的组件，其中的许多组件都能够支持响应点击或者手势事件。
比如 [`IconButton`][] 和 [`TextButton`][] 响应了按压事件（点击事件），
[`ListView`][] 响应了滚动事件。如果使用了上述组件，
你也可以使用 [`InkWell`][] 来实现点击的“水波纹”效果。

[`GestureDetector`]: https://api.flutter-io.cn/flutter/widgets/GestureDetector-class.html
[`IconButton`]: https://api.flutter-io.cn/flutter/material/IconButton-class.html
[`InkWell`]: https://api.flutter-io.cn/flutter/material/InkWell-class.html
[`ListView`]: https://api.flutter-io.cn/flutter/widgets/ListView-class.html
[`TextButton`]: https://api.flutter-io.cn/flutter/material/TextButton-class.html

### Gesture disambiguation

### 手势消歧处理

At a given location on screen,
there might be multiple gesture detectors.
For example:

在屏幕的指定位置上，可能有多个手势捕捉器。例如：

* A `ListTile` has a tap recognizer that responds
  to the entire `ListTile`, and a nested one around
  a trailing icon button. The screen rect of the
  trailing icon is now covered by two gesture
  recognizers that need to negotiate for who handles
  the gesture if it turns out to be a tap.
* A single `GestureDetector` covers a screen area
  configured to handle multiple gestures,
  such as a long press and a tap.
  The `tap` recognizer must now negotiate
  with the `long press` recognizer when
  the user touches that part of the screen.
  Depending on what happens next with that pointer,
  one of the two recognizers receives the gesture,
  or neither receives the gesture if the user
  performs something that's neither a tap nor a long press.

All of these gesture detectors listen to the stream
of pointer events as they flow past and attempt to recognize
specific gestures. The [`GestureDetector`] widget decides
which gestures to attempt to recognize based on which of its
callbacks are non-null.

所有的手势捕捉器监听了指针输入流事件并判断出特定手势。
[`GestureDetector`][] widget
能够基于手势的回调是否非空决定是否应该尝试去识别该手势。

When there is more than one gesture recognizer for a given
pointer on the screen, the framework disambiguates which
gesture the user intends by having each recognizer join
the _gesture arena_. The gesture arena determines which
gesture wins using the following rules:

当屏幕上的指定指针有多个手势识别器时，
框架会通过给每个手势识别器加入 **gesture arena** 来处理手势消歧。
gesture arena，也称作手势竞技场，会利用下述规则确定哪个手势在竞争中胜出：

* At any time, a recognizer can eliminate itself and leave the
  arena. If there's only one recognizer left in the arena,
  that recognizer wins.

  在任何时候，识别器都可以宣告失败并离开竞技场。
  如果竞技场中只有一个识别器，那么这个识别器就是胜者。

* At any time, a recognizer can declare itself the winner,
  causing all of the remaining recognizers to lose.

  在任何时候，任何识别器都可以宣告胜利，
  这将导致这个识别器胜出，其他识别器失败。

For example, when disambiguating horizontal and vertical dragging,
both recognizers enter the arena when they receive the pointer
down event. The recognizers observe the pointer move events.
If the user moves the pointer more than a certain number of
logical pixels horizontally, the horizontal recognizer declares
the win and the gesture is interpreted as a horizontal drag.
Similarly, if the user moves more than a certain number of logical
pixels vertically, the vertical recognizer declares itself the winner.

比如，当纵向拖动和横向拖动需要处理消歧，
当指针下落事件发生时，纵向和横向识别器都会进入竞技场，
观测指针移动事件。如果用户在横向上移动超过了特定像素，
横向识别器会宣告胜利，手势也会被当作横向拖动处理。
同样的，如果用户在纵向上移动超过了特定的像素，纵向识别器会宣告胜利。

The gesture arena is beneficial when there is only a horizontal
(or vertical) drag recognizer. In that case, there is only one
recognizer in the arena and the horizontal drag is recognized
immediately, which means the first pixel of horizontal movement
can be treated as a drag and the user won't need to wait for
further gesture disambiguation.

