这个章节将会讲解如何监听和响应 Flutter 的手势操作 gestures。典型的手势操作包括点击、拖动和缩放。
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, which describe the location and movement of pointers (e.g., touches, mice, and styli) across the screen. The second layer has gestures, which describe semantic actions that consist of one or more pointer movements.
Pointers represent raw data about the user’s interaction with the device’s screen. There are four types of pointer events:
PointerDownEventThe pointer has contacted the screen at a particular location.
PointerMoveEventThe pointer has moved from one location on the screen to another.
PointerUpEventThe pointer has stopped contacting the screen.
PointerCancelEventInput from this pointer is no longer directed towards this app.
在指针下落事件中，框架做了一个 hit test 的操作确定与屏幕发生接触的位置上有哪些组件以及分发给最内部的组件去响应。事件会沿着组件树从这个最内部的组件向组件树的根部冒泡分发。并且不存在用于取消或停止指针事件进行进一步分发的机制。
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.
Listener 可以在组件层直接监听指针事件。然而，一般情况下，请考虑使用下面的 gestures 替代。
To listen to pointer events directly from the widgets layer, use a
widget. However, generally, consider using gestures (as discussed
Gesture 代表的是语义操作（比如点击、拖动、缩放）。通常由一系列单独的指针事件组成，甚至是一系列单独的指针组成。Gesture 可以分发多种事件，对应着手势的生命周期（比如开始拖动、拖动更新、结束拖动）。
Gestures represent semantic actions (e.g., 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 (e.g., drag start, drag update, and drag end):
onTapDownA pointer that might cause a tap has contacted the screen at a particular location.
onTapUpA pointer that will trigger a tap has stopped contacting the screen at a particular location.
onTapA tap has occurred.
onTapCancelThe pointer that previously triggered the
onTapDownwill not end up causing a tap.
onDoubleTapThe user has tapped the screen at the same location twice in quick succession.
onLongPressA pointer has remained in contact with the screen at the same location for a long period of time.
onVerticalDragStartA pointer has contacted the screen and might begin to move vertically.
onVerticalDragUpdateA pointer that is in contact with the screen and moving vertically has moved in the vertical direction.
onVerticalDragEndA 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.
onHorizontalDragStartA pointer has contacted the screen and might begin to move horizontally.
onHorizontalDragUpdateA pointer that is in contact with the screen and moving horizontally has moved in the horizontal direction.
onHorizontalDragEndA 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.
onPanStartA pointer has contacted the screen and might begin to move horizontally or vertically. This callback causes a crash if
onPanUpdateA pointer that is in contact with the screen and is moving in the vertical or horizontal direction. This callback causes a crash if
onPanEndA 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 causes a crash if
To listen to gestures from the widgets layer, use a
If you’re using Material Components, many of those widgets already respond
to taps or gestures.
respond to presses (taps), and
responds to swipes to trigger scrolling.
If you are not using those widgets, but you want the “ink splash” effect on a
tap, you can use
GestureDetector widget 能够基于手势的回调是否非空决定是否应该尝试去识别该手势。
At a given location on screen, there might be multiple gesture detectors. All
of these gesture detectors listen to the stream of pointer events as they flow
past and attempt to recognize specific gestures. The
widget decides which gestures to attempt to recognize based on which of its
callbacks are non-null.
当屏幕上的指定指针有多个手势识别器时，框架会通过给每个手势识别器加入 gesture arena 来处理手势消歧。gesture arena，也称作手势竞技场，会利用下述规则确定哪个手势在竞争中胜出：
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:
At any time, a recognizer can declare defeat and leave the arena. If there’s only one recognizer left in the arena, that recognizer is the winner.
At any time, a recognizer can declare victory, which causes it to win and all 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 will declare victory and the gesture will be interpreted as a horizontal drag. Similarly, if the user moves more than a certain number of logical pixels vertically, the vertical recognizer will declare victory.
The gesture arena is beneficial when there is only a horizontal (or vertical) drag recognizer. In that case, there will be only one recognizer in the arena and the horizontal drag will be recognized immediately, which means the first pixel of horizontal movement can be treated as a drag and the user will not need to wait for further gesture disambiguation.