跳到主内容

点击、拖拽及其他手势

Flutter 中点击和拖拽等手势的工作原理。

本文档介绍了如何在 Flutter 中监听并响应手势。常见手势包括点击、拖拽和缩放。

Flutter 的手势系统分为两个独立的层级。第一层是原始指针事件,用于描述指针(例如触摸、鼠标和触控笔)在屏幕上的位置和移动。第二层是手势,用于描述由一个或多个指针移动组成的语义动作。

指针 (Pointers)

#

指针代表用户与设备屏幕交互的原始数据。共有四种类型的指针事件:

PointerDownEvent

指针已在特定位置接触屏幕。

PointerMoveEvent

指针已从屏幕上的一个位置移动到另一个位置。

PointerUpEvent

指针已停止与屏幕接触。

PointerCancelEvent

来自该指针的输入不再指向此应用。

当指针按下时,框架会对应用进行命中测试 (hit test),以确定指针接触屏幕的位置存在哪些组件。指针按下事件(以及该指针随后的事件)将分发给命中测试找到的最内层组件。随后,事件会沿组件树向上冒泡,并分发给从最内层组件到树根路径上的所有组件。目前没有机制可以取消或停止指针事件的后续分发。

若要直接从组件层监听指针事件,请使用 Listener 组件。但通常建议优先使用下文讨论的“手势”。

手势

#

手势代表语义动作(例如点击、拖拽和缩放),这些动作通过多个独立的指针事件识别,甚至可能涉及多个独立的指针。手势可以分发多个事件,对应于手势的生命周期(例如,拖拽开始、拖拽更新和拖拽结束)。

点击

onTapDown

可能导致点击的指针已在特定位置接触屏幕。

onTapUp

触发点击的指针已在特定位置停止接触屏幕。

onTap

之前触发 onTapDown 的指针也触发了 onTapUp,最终导致了一次点击。

onTapCancel

之前触发 onTapDown 的指针最终并未导致点击。

双击

onDoubleTap

用户在同一位置短时间内连续点击了两次屏幕。

长按

onLongPress

指针在同一位置长时间保持与屏幕接触。

垂直拖动

onVerticalDragStart

指针已接触屏幕并可能开始垂直移动。

onVerticalDragUpdate

一个正在与屏幕接触并进行垂直移动的指针,已经在垂直方向上发生了位移。

onVerticalDragEnd

之前与屏幕接触并垂直移动的指针不再与屏幕接触,并且在停止接触屏幕时以特定速度移动。

水平拖动

onHorizontalDragStart

指针已接触屏幕并可能开始水平移动。

onHorizontalDragUpdate

一个正在与屏幕接触并进行水平移动的指针,已经在水平方向上发生了位移。

onHorizontalDragEnd

一个之前正在与屏幕接触并进行水平移动的指针,现在已脱离屏幕,并在停止接触时保持着特定的速度。

平移 (Pan)

onPanStart

指针已接触屏幕,且可能开始进行水平或垂直移动。如果设置了 onHorizontalDragStartonVerticalDragStart,此回调会报错。

onPanUpdate

一个正在与屏幕接触并沿垂直或水平方向移动的指针。如果设置了 onHorizontalDragUpdateonVerticalDragUpdate,此回调会报错。

onPanEnd

一个之前与屏幕接触的指针现已脱离屏幕,并在停止接触时保持着特定的速度。如果设置了 onHorizontalDragEndonVerticalDragEnd,此回调会报错。

为组件添加手势检测

#

若要从组件层监听手势,请使用 GestureDetector

如果您使用的是 Material 组件,其中许多组件已经能够响应点击或手势。例如,IconButtonTextButton 响应按下(点击)操作,而 ListView 通过滑动响应来触发滚动。如果您不使用这些组件,但希望在点击时出现“墨水飞溅 (ink splash)”效果,可以使用 InkWell

手势消歧 (Gesture disambiguation)

#

在屏幕上的特定位置,可能存在多个手势检测器。例如:

  • ListTile 有一个响应整个 ListTile 的点击识别器,而在尾部的图标按钮周围还有一个嵌套的识别器。尾部图标的屏幕矩形区域现在被两个手势识别器覆盖,如果发生了点击,它们需要协商由谁来处理该手势。
  • 单个 GestureDetector 覆盖的屏幕区域配置为处理多种手势,例如长按和点击。当用户触摸屏幕该区域时,点击识别器必须与长按识别器进行协商。根据指针后续的操作,其中一个识别器将接收该手势;如果用户执行的操作既不是点击也不是长按,则两者都不会接收该手势。

所有这些手势检测器都会监听流经的指针事件流,并尝试识别特定手势。GestureDetector 组件会根据其哪些回调函数非空(non-null),来决定尝试识别哪些手势。

当屏幕上某个指针对应多个手势识别器时,框架会通过让每个识别器加入手势竞技场 (gesture arena) 来确定用户意图,从而消除歧义。手势竞技场使用以下规则确定胜出者:

  • 在任何时候,识别器都可以自我淘汰并离开竞技场。如果竞技场中只剩下一个识别器,该识别器胜出。

  • 在任何时候,识别器都可以宣布自己胜出,从而导致所有剩余的识别器失败。

例如,在区分水平拖拽和垂直拖拽时,两个识别器在接收到指针按下事件时都会进入竞技场。识别器会观察指针移动事件。如果用户在水平方向上移动指针超过一定数量的逻辑像素,水平识别器就会宣布胜出,手势被解释为水平拖拽。同样,如果用户在垂直方向上移动超过一定数量的逻辑像素,垂直识别器就会宣布自己胜出。

当只有水平(或垂直)拖拽识别器时,手势竞技场也非常有用。在这种情况下,竞技场中只有一个识别器,水平拖拽会立即被识别,这意味着水平移动的第一像素就可以被视为拖拽,用户无需等待进一步的手势消歧。