6 使用触摸事件
本主题描述了使用触摸屏幕与您的JavaFX应用程序进行交互的触摸事件。触摸点用于标识每个触摸的接触点。本主题将向您展示如何识别触摸点并处理触摸事件,以提供对触摸操作的复杂响应。
触摸操作由触摸屏幕上的一个或多个接触点组成。该操作可以是简单的按下和释放,也可以是在按下和释放之间进行一系列复杂的保持和移动。对于每个接触点,都会生成一系列事件,持续整个操作的时间。除了触摸事件,还会生成鼠标事件和手势事件。如果您的JavaFX应用程序不需要对触摸操作进行复杂的响应,您可能更喜欢处理鼠标或手势事件而不是触摸事件。有关处理手势事件的更多信息,请参阅使用触摸设备的事件处理。
触摸事件需要触摸屏幕和Windows 7操作系统。
触摸动作概述
触摸动作是指用户从触摸屏幕接触到所有接触点释放触摸屏幕的整个过程。在触摸动作期间生成的触摸事件类型有TOUCH_PRESSED
、TOUCH_MOVED
、TOUCH_STATIONARY
和TOUCH_RELEASED
。
每个与屏幕接触的点被视为一个触摸点。对于每个触摸点,都会生成一个触摸事件。当一个触摸动作包含多个接触点时,对于触摸动作中的每个状态,都会生成一组事件,即每个触摸点对应一个事件。
有关这些元素的更多信息,请参见章节触摸点、触摸事件和事件集。有关触摸事件在JavaFX应用程序中的使用示例,请参见触摸事件示例。
触摸点
当用户触摸触摸屏幕时,为每个接触点创建一个触摸点。触摸点由TouchPoint
类的实例表示,并包含有关接触点的位置、状态和目标的信息。触摸点的状态有按下、移动、静止和释放。
提示:
生成的触摸点数量可能受到触摸屏幕的限制。例如,如果触摸屏幕仅支持两个接触点,而用户用三个手指触摸屏幕,只会生成两个触摸点。本文假设触摸屏幕能够识别所有接触点。
每个触摸点都有一个ID,该ID在添加到触摸动作时按顺序分配。触摸点的ID从接触触摸屏幕开始到释放接触时保持不变。当一个接触点释放时,相关的触摸点不再是触摸动作的一部分。例如,如果用两个手指触摸屏幕,第一个触摸点的ID为1,第二个触摸点的ID为2。如果第二个手指从触摸屏幕上移开,只有触摸点1仍然是触摸动作的一部分。如果再添加另一个手指到触摸动作中,新触摸点的ID为3,触摸动作中有触摸点1和3。
触摸事件
触摸事件用于跟踪触摸点的操作。触摸事件由TouchEvent
类的实例表示。触摸事件仅从触摸屏幕上的触摸生成。触摸事件不会从触摸板上生成。
触摸事件与其他事件类似,具有源、目标和事件类型,进一步定义了所发生的操作。触摸事件的类型包括TOUCH_PRESSED
、TOUCH_MOVED
、TOUCH_STATIONARY
和TOUCH_RELEASED
。对于一个触摸点,可以生成多个TOUCH_MOVED
和TOUCH_STATIONARY
事件,具体取决于移动的距离和触摸点保持静止的时间。有关事件的基本信息和事件处理的基本信息,请参见处理事件。
触摸事件还具有以下内容:
-
触摸点
与此事件关联的主要触摸点
-
触摸计数
当前与触摸操作关联的触摸点数量
-
触摸点列表
当前与触摸操作关联的触摸点集合
-
事件集合ID
包含此事件的事件集合的ID
事件集
当一个触摸动作只有一个接触点时,每个动作状态都会生成一个触摸事件。当一个触摸动作有多个接触点时,每个动作状态都会生成一组触摸事件。每个事件集中的触摸事件与不同的接触点相关联。
每个事件集都有一个事件集ID。响应触摸动作时,每生成一组事件集,事件集ID就会递增一次。事件集中的事件类型可能不同,取决于与其相关联的接触点的状态。随着触摸动作中接触点的增加或移除,事件集中的事件数量也会发生变化。例如,表6-1描述了当用户用两个手指触摸触摸屏幕,移动两个手指,用第三个手指触摸触摸屏幕,移动所有手指,然后从屏幕上移除所有手指时生成的事件集。
表6-1 单点触摸操作的事件集
事件集ID | 触摸事件数量 | 每个事件的事件类型 |
---|---|---|
1 |
1 |
|
2 |
2 |
|
3 |
2 |
|
4 |
3 |
|
5 |
3 |
|
6 |
3 |
|
7 |
3 |
|
8 |
3 |
|
9 |
2 |
|
10 |
1 |
|
触摸点目标和触摸事件目标
触摸事件的目标是与事件相关联的触摸点的目标。触摸点的初始目标是与触摸屏幕接触的初始点上最顶层的节点。如果一个触摸动作有多个接触点,每个触摸点,因此每个触摸事件,都可能有不同的目标。这个特性使您能够独立处理每个触摸点,而不受其他触摸点的影响。有关示例,请参见独立处理并发触摸点。
通常,一个触摸点的所有事件都会传递给同一个目标。但是,您可以使用grab()
和ungrab()
方法来更改后续事件的目标。
grab()
方法使当前处理事件的节点成为触摸点的目标。grab(
target)
方法使另一个节点成为触摸点的目标。因为事件集中的事件可以访问集合中所有触摸点,所以可以使用grab()
方法将触摸动作的所有后续事件定向到同一个节点。grab()
方法还可以用于重置触摸点的目标,如更改触摸点的目标中所示。
ungrab()
方法用于释放触摸点的当前目标。然后,触摸动作的后续事件将发送到触摸点当前位置的最顶层节点。
从触摸生成的其他事件
当用户触摸触摸屏幕时,除了触摸事件之外,还会生成其他类型的事件:
-
鼠标事件
模拟的鼠标事件使应用程序能够在不处理触摸事件的情况下在触摸屏幕设备上运行。使用
isSynthesized()
方法确定鼠标事件是否来自触摸动作。有关示例,请参见处理鼠标事件。 -
手势事件
手势事件是为常见的滚动、滑动、旋转和缩放触摸动作生成的。如果这些是您的应用程序必须处理的唯一类型的触摸动作,您可以处理这些手势事件而不是触摸事件。有关手势事件的信息,请参见使用触摸设备的事件处理。
触摸事件示例
触摸事件示例使用四个文件夹来演示如何独立处理触摸点集合中的每个触摸点。该示例还展示了如何使用grab()
方法将圆圈从一个矩形跳转到另一个矩形。图6-1显示了示例的用户界面。
触摸事件示例可在TouchEventsExample.zip
文件中找到。解压缩NetBeans项目并在NetBeans IDE中打开。要生成触摸事件,您必须在具有触摸屏的设备上运行示例。
独立处理并发触摸点
在典型的手势中,目标是所有接触点的中心节点,并且只有一个节点受到手势响应的影响。通过单独处理每个触摸点,您可以影响所有被触摸的节点。
在触摸事件示例中,您可以通过触摸文件夹并移动手指来移动每个文件夹。您可以通过用不同的手指触摸每个文件夹并移动所有手指来同时移动多个文件夹。
每个文件夹都是TouchImage
类的一个实例。 TouchImage
类创建一个图像视图,并为TOUCH_PRESSED
、TOUCH_RELEASED
和TOUCH_MOVED
事件添加事件处理程序。 示例 6-1显示了该类的定义。
示例 6-1 TouchImage 类定义
public static class TouchImage extends ImageView { private long touchId = -1; double touchx, touchy; public TouchImage(int x, int y, Image img) { super(img); setTranslateX(x); setTranslateY(y); setEffect(new DropShadow(8.0, 4.5, 6.5, Color.DARKSLATEGRAY)); setOnTouchPressed(new EventHandler<TouchEvent>() { @Override public void handle(TouchEvent event) { if (touchId == -1) { touchId = event.getTouchPoint().getId(); touchx = event.getTouchPoint().getSceneX() - getTranslateX(); touchy = event.getTouchPoint().getSceneY() - getTranslateY(); } event.consume(); } }); setOnTouchReleased(new EventHandler<TouchEvent>() { @Override public void handle(TouchEvent event) { if (event.getTouchPoint().getId() == touchId) { touchId = -1; } event.consume(); } }); setOnTouchMoved(new EventHandler<TouchEvent>() { @Override public void handle(TouchEvent event) { if (event.getTouchPoint().getId() == touchId) { setTranslateX(event.getTouchPoint().getSceneX() - touchx); setTranslateY(event.getTouchPoint().getSceneY() - touchy); } event.consume(); } }); } }
当触摸到一个文件夹时,为每个接触点创建一个触摸点,并将触摸事件发送到文件夹。触摸ID用于确保当多个接触点在文件夹上时,文件夹只响应一次。
当接收到TOUCH_PRESSED
事件时,检查触摸ID以确定是否是此文件夹的新触摸。如果是,则将触摸ID设置为触摸点的ID,并保存触摸点的位置。
当接收到TOUCH_RELEASED
事件时,检查触摸ID以确保它与正在处理的触摸点匹配。如果是,则将触摸ID重置为表示处理完成。
当接收到TOUCH_MOVED
事件时,检查触摸ID以确保它与正在处理的触摸点匹配。如果是,则将文件夹移动到触摸点的新位置。如果触摸ID与触摸点不匹配,则可能有多个接触点在文件夹上。为了避免对同一文件夹的多次移动做出响应,忽略该事件。
更改触摸点的目标
触摸点的目标通常在触摸操作的整个过程中保持不变。然而,在某些情况下,您可能希望在触摸操作期间更改触摸点的目标。
在触摸事件示例中,通过用一根手指触摸圆圈和用第二根手指触摸矩形,圆圈从一个矩形移动到另一个矩形。在跳跃后,保持第二根手指在圆圈上,抬起第一根手指并触摸不同的矩形,以再次使圆圈跳跃。只有在更改第二个触摸点的目标时,才能实现这个操作。
圆圈是Ball
类的一个实例。 Ball
类创建一个圆圈,并为TOUCH_PRESSED
、TOUCH_RELEASED
、TOUCH_MOVED
和TOUCH_STATIONARY
事件添加事件处理程序。 示例6-2显示了该类的定义。
示例 6-2 Ball 类定义
private static class Ball extends Circle { double touchx, touchy; public Ball(int x, int y) { super(35); RadialGradient gradient = new RadialGradient(0.8, -0.5, 0.5, 0.5, 1, true, CycleMethod.NO_CYCLE, new Stop [] { new Stop(0, Color.FIREBRICK), new Stop(1, Color.BLACK) }); setFill(gradient); setTranslateX(x); setTranslateY(y); setOnTouchPressed(new EventHandler<TouchEvent>() { @Override public void handle(TouchEvent event) { if (event.getTouchCount() == 1) { touchx = event.getTouchPoint().getSceneX() - getTranslateX(); touchy = event.getTouchPoint().getSceneY() - getTranslateY(); setEffect(new Lighting()); } event.consume(); } }); setOnTouchReleased(new EventHandler<TouchEvent>() { @Override public void handle(TouchEvent event) { setEffect(null); event.consume(); } }); // 如果第一个手指触摸到球并且正在移动或静止,并且第二个手指触摸到一个矩形,则跳跃 EventHandler<TouchEvent> jumpHandler = new EventHandler<TouchEvent>() { @Override public void handle(TouchEvent event) { if (event.getTouchCount() != 2) { // 如果不是两个手指触摸,则忽略 return; } TouchPoint main = event.getTouchPoint(); TouchPoint other = event.getTouchPoints().get(1); if (other.getId() == main.getId()) { // 如果第二个手指在球内,而第一个手指在其他地方,则忽略 return; } if (other.getState() != TouchPoint.State.PRESSED || other.belongsTo(Ball.this) || !(other.getTarget() instanceof Rectangle) ){ // 只有当第二个手指刚刚按在矩形上时才跳跃 return; } // 现在跳跃 setTranslateX(other.getSceneX() - touchx); setTranslateY(other.getSceneY() - touchy); // 抓住目标手指,它现在在球内,以便跳跃可以继续而不释放手指 other.grab(); // 原始触摸点不再重要,所以调用ungrab()释放目标 main.ungrab(); event.consume(); } }; setOnTouchStationary(jumpHandler); setOnTouchMoved(jumpHandler); } }
当接收到TOUCH_PRESSED
事件时,会检查触摸点的数量,以确保只有Ball
类的实例被触摸到。如果是这样,将保存触摸点的位置,并添加一个光照效果来显示圆被选中。
当接收到TOUCH_RELEASED
事件时,将移除光照效果,以显示圆不再被选中。
当接收到TOUCH_MOVED
或TOUCH_STATIONARY
事件时,将检查以下跳跃所需的条件:
-
触摸点数量必须为两个。
与此事件相关联的触摸点被视为跳跃的起点。该事件可以访问触摸操作的所有触摸点。触摸点集合中的第二个触摸点被视为跳跃的终点。
-
第二个触摸点的状态为
PRESSED
。只有在第二个接触点产生时,才移动圆。忽略第二个触摸点的其他状态。
-
第二个触摸点的目标是一个矩形。
圆只能从一个矩形跳到另一个矩形,或者在一个矩形内部跳跃。如果第二个触摸点的目标是其他任何东西,圆将不会移动。
如果满足跳跃的条件,圆将跳到第二个触摸点的位置。要再次跳跃,需要释放第一个接触点,并触摸第三个位置,期望圆会跳到第三个位置。然而,当释放第一个接触点时,目标为圆的触摸点消失,现在圆不再接收触摸事件。如果不同时抬起两个手指并开始新的跳跃,就无法进行第二次跳跃。
为了在保持第二个手指在圆上并触摸新位置的同时实现第二次跳跃,使用grab()
方法将圆设置为第二个触摸点的目标。在抓取之后,第二个触摸点的事件将发送给圆,而不是最初的目标矩形。然后,圆可以等待新的触摸点并再次跳跃。