点击、拖拽及其他手势
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-
指针已接触屏幕,且可能开始进行水平或垂直移动。如果设置了
onHorizontalDragStart或onVerticalDragStart,此回调会报错。 onPanUpdate-
一个正在与屏幕接触并沿垂直或水平方向移动的指针。如果设置了
onHorizontalDragUpdate或onVerticalDragUpdate,此回调会报错。 onPanEnd-
一个之前与屏幕接触的指针现已脱离屏幕,并在停止接触时保持着特定的速度。如果设置了
onHorizontalDragEnd或onVerticalDragEnd,此回调会报错。
为组件添加手势检测
#若要从组件层监听手势,请使用 GestureDetector。
如果您使用的是 Material 组件,其中许多组件已经能够响应点击或手势。例如,IconButton 和 TextButton 响应按下(点击)操作,而 ListView 通过滑动响应来触发滚动。如果您不使用这些组件,但希望在点击时出现“墨水飞溅 (ink splash)”效果,可以使用 InkWell。
手势消歧 (Gesture disambiguation)
#在屏幕上的特定位置,可能存在多个手势检测器。例如:
ListTile有一个响应整个ListTile的点击识别器,而在尾部的图标按钮周围还有一个嵌套的识别器。尾部图标的屏幕矩形区域现在被两个手势识别器覆盖,如果发生了点击,它们需要协商由谁来处理该手势。- 单个
GestureDetector覆盖的屏幕区域配置为处理多种手势,例如长按和点击。当用户触摸屏幕该区域时,点击识别器必须与长按识别器进行协商。根据指针后续的操作,其中一个识别器将接收该手势;如果用户执行的操作既不是点击也不是长按,则两者都不会接收该手势。
所有这些手势检测器都会监听流经的指针事件流,并尝试识别特定手势。GestureDetector 组件会根据其哪些回调函数非空(non-null),来决定尝试识别哪些手势。
当屏幕上某个指针对应多个手势识别器时,框架会通过让每个识别器加入手势竞技场 (gesture arena) 来确定用户意图,从而消除歧义。手势竞技场使用以下规则确定胜出者:
-
在任何时候,识别器都可以自我淘汰并离开竞技场。如果竞技场中只剩下一个识别器,该识别器胜出。
-
在任何时候,识别器都可以宣布自己胜出,从而导致所有剩余的识别器失败。
例如,在区分水平拖拽和垂直拖拽时,两个识别器在接收到指针按下事件时都会进入竞技场。识别器会观察指针移动事件。如果用户在水平方向上移动指针超过一定数量的逻辑像素,水平识别器就会宣布胜出,手势被解释为水平拖拽。同样,如果用户在垂直方向上移动超过一定数量的逻辑像素,垂直识别器就会宣布自己胜出。
当只有水平(或垂直)拖拽识别器时,手势竞技场也非常有用。在这种情况下,竞技场中只有一个识别器,水平拖拽会立即被识别,这意味着水平移动的第一像素就可以被视为拖拽,用户无需等待进一步的手势消歧。