介绍可触摸设备上各种不同类型手势产生的事件,例如触摸事件、缩放事件、旋转事件和轻扫事件。将会向你展示如何在JavaFX程序中处理这些类型的事件。
从JavaFX2.2版开始,用户可以在可触摸设备上使用触摸和手势来与你的JavaFX程序交互。触摸和手势可能涉及到单点或多点触摸。生成的事件的类型取决于用户产生的触摸或者手势的类型。
触摸和手势事件的处理过程与其它事件的处理过程是一样的。
手势和触摸事件
当JavaFX程序运行在带有触摸屏或者带有可识别手势的触摸板的设备上时就会产生手势事件。在可识别手势的各种平台上,会调用原生的识别机制来确定所执行的手势。表5-1描述了支持的手势以及产生的对应事件类型。
表5-1 支持的手势和产生的事件类型
手势 | 描述 | 产生的事件 |
旋转 | 两根手指做旋转动作,一根手指绕另一根手指做顺时针运动来让该对象顺时针旋转,反之亦然。 | · ROTATION_STARTED· ROTATE· ROTATION_FINISHED |
滚动 | 滑动动作,向上或向下滑动来做竖直滚动,向左或向右滑动来做水平滚动 | · SCROLL_STARTED· SCROLL· SCROLL_FINISHED如果用鼠标的滚轮来滚动,则只会产生SCROLL事件 |
轻扫 | 通过屏幕或者触摸板向上、下、左、右方向的轻扫动作。对角线运动不会被识别为轻扫动作。 | · SWIPE_LEFT· SWIPE_RIGHT· SWIPE_UP· SWIPE_DOWN
每个轻扫手势只会生成一个轻扫事件,但也会生成SCROLL_STARTED、SCROLL和SCROLL_FINISHED事件。 |
缩放 | 两支手指做捏的动作,捏合在一起表示缩小,分开表示放大。 | · ZOOM_STARTED· ZOOM· ZOOM_FINISHED |
当JavaFX程序运行在有触摸屏的设备上并且用户用一根或多根手指触摸屏幕时就会产生触摸事件。这些事件可被用来提供对触摸或者手势中的每个单独触摸点进行更低水平的跟踪。
手势的目标
大多数手势的目标都是手势起始处所有触摸点中心处的节点。轻扫手势的目标是所有手指的完整移动路径中心处的节点。
如果在目标点处有多个节点,最顶层的节点会被当做目标。所有的单一手势、持续性手势、包括手势的惯性所产生的事件都会被传递到手势开始时选定的节点上。
生成的其它事件
除了手势和触摸的执行事件之外,手势和触摸会产生其它类型的事件。轻扫手势除了轻扫事件之外还会产生滚动事件。根据轻扫的距离,轻扫和滚动事件可能会有不同的目标。滚动事件的目标是手势开始处的节点。轻扫事件的目标是手势整个移动路径的中心处的节点。
触摸屏上的触摸动作也会产生相应的鼠标事件。例如,触摸屏幕上的一个点会产生TOUCH_PRESSED和MOUSE_PRESSED事件。移动屏幕上的一个点会产生滚动事件和拖拽事件。即使你的程序并不直接处理触摸和手势事件,也可以通过响应触摸所产生的鼠标事件来在触摸设备上运行并尽可能少地进行改动。
如果你的程序处理触摸、手势和鼠标事件,请确保你没有对一个动作处理多次。例如,如果一个手势产生了滚动事件和拖拽事件,而你对两种事件采用了相同的事件处理方式,那么屏幕上的动作就可能会是预期的两倍。你可以对鼠标事件使用isSynthesized()方法来判断该事件是由鼠标动作产生还是由触摸屏上的动作产生并且对该事件仅处理一次。
手势事件(Gesture Events)样例
Gesture Events样例展示了一个长方形、一个椭圆形和一个事件日志,如图5-1所示。
图5-1 手势事件样例
日志中包含了被处理的事件记录。本样例允许你尝试不同的手势并能看到每个手势产生了什么事件。
要产生手势事件,你需要在有触摸屏或者有支持手势的触摸板的设备上运行该样例程序。要产生触摸事件,你需要在有触摸屏的设备上运行该样例程序。
创建图形
Gesture Events样例程序展示了一个长方形和一个椭圆形。例5-1展示了创建每个图形和包含图形的布局面板的代码。
例5-1 创建图形
// 创建对手势进行响应的图形并使用一个VBox来组织它们
VBox shapes = new VBox();
shapes.setAlignment(Pos.CENTER);
shapes.setPadding(new Insets(15.0));
shapes.setSpacing(30.0);
shapes.setPrefWidth(500);
shapes.getChildren().addAll(createRectangle(), createEllipse());
...
private Rectangle createRectangle() {
final Rectangle rect = new Rectangle(100, 100, 100, 100);
rect.setFill(Color.DARKMAGENTA);
...
return rect;
}
private Ellipse createEllipse() {
final Ellipse oval = new Ellipse(100, 50);
oval.setFill(Color.STEELBLUE);
...
return oval;
}
你可以用手势来移动、旋转或者放大缩小这些对象。
处理事件
总的来说,Gesture Events样例程序中的图形对象的EventHandler为其所处理的各种类型的事件执行了类似的操作。为各种事件类型都会在事件日志中插入一条记录。
在支持手势惯性的平台上,可能会在“事件类型_FINISHED”事件之后再产生附加的事件。例如,如果有任何与滚动手势相关联的惯性,在SCROLL_FINISHED事件之后可能就会产生SCROLL事件。可以用isInertia()方法来判断该事件是否是由手势的惯性产生。如果该方法返回true,就表示该事件是在手势完成以后产生的。
事件是通过触摸屏或者触摸板上的手势产生的。SCROLL事件也可以通过鼠标滚轮产生。使用isDirect()方法来标识该事件的来源。如果该方法返回true,就表示该事件是由触摸屏上的手势产生的。否则,该方法就会返回false。你可以根据此信息来对事件的不同来源提供不同的处理方式。
触摸屏上的触摸事件也会产生相应的鼠标事件。例如,触摸一个对象会同时产生TOUCHE_PRESSED和MOUSE_PRESSED事件。用isSynthesized()方法来判断鼠标事件的来源。如果该方法返回true,就表示该事件是通过触摸产生的而不是鼠标。
Gesture Events样例程序中的inc()和dec()方法用来为手势事件的目标提供一个视觉提示。正在进行的手势数量是被追踪的,并且当前活动的手势的数量从0变到1或者再变回0时目标节点的外观会产生变化。
在Gesture Events样例程序中,长方形和椭圆形的Event Handler比较相似。因此,后续中的样例代码只展示了长方形的Event Handler。椭圆形的事件处理器请参考GestureEvents.java。
处理滚动事件
当滚动手势被执行以后,会产生SCROLL_STARTED、SCROLL和SCROLL_FINISHED事件。当鼠标滚轮滚动时,只会产生SCROLL事件。例5-2展示了Gesture Events样例程序中长方形的滚动事件的Event Handler。椭圆形的Event Handler与此类似。
图5-2 为滚动事件定义Event Handler
rect.setOnScroll(new EventHandler<ScrollEvent>() {
@Override public void handle(ScrollEvent event) {
if (!event.isInertia()) {
rect.setTranslateX(rect.getTranslateX() + event.getDeltaX());
rect.setTranslateY(rect.getTranslateY() + event.getDeltaY());
}
log("Rectangle: Scroll event" +
", inertia: " + event.isInertia() +
", direct: " + event.isDirect());
event.consume();
}
});
rect.setOnScrollStarted(new EventHandler<ScrollEvent>() {
@Override public void handle(ScrollEvent event) {
inc(rect);
log("Rectangle: Scroll started event");
event.consume();
}
});
rect.setOnScrollFinished(new EventHandler<ScrollEvent>() {
@Override public void handle(ScrollEvent event) {
dec(rect);
log("Rectangle: Scroll finished event");
event.consume();
}
});
除了在前面“处理事件”介绍的通用事件处理方式之外,SCROLL事件的处理中还会沿滚动手势的方向移动该节点。如果滚动手势在窗体外部停止,图形就会被移到窗体外面。长方形的Event Handler忽略了由惯性产生的SCROLL事件。椭圆形的Event Handler会继续移动椭圆形来响应惯性产生的SCROLL事件,并且可能会导致即使手势在窗体内停止也有可能会将椭圆形移出窗体。
处理缩放事件
当执行缩放手势时,会产生ZOOM_SATRTED、ZOOM和ZOOM_FINISHED事件。例5-3展示了Gesture Events样例中长方形的缩放事件的Event Handler。椭圆形的Event Handler与之类似。
例5-3 定义缩放事件的Event Handler
rect.setOnZoom(new EventHandler<ZoomEvent>() {
@Override public void handle(ZoomEvent event) {
rect.setScaleX(rect.getScaleX() * event.getZoomFactor());
rect.setScaleY(rect.getScaleY() * event.getZoomFactor());
log("Rectangle: Zoom event" +
", inertia: " + event.isInertia() +
", direct: " + event.isDirect());
event.consume();
}
});
rect.setOnZoomStarted(new EventHandler<ZoomEvent>() {
@Override public void handle(ZoomEvent event) {
inc(rect);
log("Rectangle: Zoom event started");
event.consume();
}
});
rect.setOnZoomFinished(new EventHandler<ZoomEvent>() {
@Override public void handle(ZoomEvent event) {
dec(rect);
log("Rectangle: Zoom event finished");
event.consume();
}
});
除了在前面“处理事件”章节中介绍的通用事件处理方式之外,ZOOM事件的处理中还会根据手势的动作来缩放对应的对象。长方形和椭圆形的Event Handler对于所有ZOOM事件的处理都相同,无论事件的惯性或来源情况如何。
处理旋转事件
当执行旋转手势时,会产生ROTATE_SARTED、ROTATE和ROTATE_FINISHED事件。例5-4展示了Gesture Events样例程序中长方形的旋转事件的Event Handler。椭圆形的Event Handler与之类似。
例5-4 定义旋转事件的处理器
rect.setOnRotate(new EventHandler<RotateEvent>() {
@Override public void handle(RotateEvent event) {
rect.setRotate(rect.getRotate() + event.getAngle());
log("Rectangle: Rotate event" +
", inertia: " + event.isInertia() +
", direct: " + event.isDirect());
event.consume();
}
});
rect.setOnRotationStarted(new EventHandler<RotateEvent>() {
@Override public void handle(RotateEvent event) {
inc(rect);
log("Rectangle: Rotate event started");
event.consume();
}
});
rect.setOnRotationFinished(new EventHandler<RotateEvent>() {
@Override public void handle(RotateEvent event) {
dec(rect);
log("Rectangle: Rotate event finished");
event.consume();
}
});
除了在前面“处理事件”章节中介绍的通用事件处理方式,ROTATE事件的处理中还会根据手势的动作来旋转对应的对象。长方形和椭圆形对于所有的ROTATE事件的Event Handler都相同,无论事件的惯性或来源情况如何。
处理轻扫事件
当执行轻扫手势时,会产生SWIPE_DWON、SWIPE_LEFT、SWIPE_RIGHT或者SWIPE_UP事件中的某一种事件,具体取决于轻扫的方向。例5-5展示了Gesture Events样例程序中长方形的SWIPE_RIGHT和SWIPE_LEFT事件的Event Handler。椭圆形未处理轻扫事件。
例5-5 定义轻扫事件的Event Handler
rect.setOnSwipeRight(new EventHandler<SwipeEvent>() {
@Override public void handle(SwipeEvent event) {
log("Rectangle: Swipe right event");
event.consume();
}
});
rect.setOnSwipeLeft(new EventHandler<SwipeEvent>() {
@Override public void handle(SwipeEvent event) {
log("Rectangle: Swipe left event");
event.consume();
}
});
对轻扫事件的处理仅仅是在日志中记录了该事件。然而,轻扫事件也会产生滚动事件。轻扫事件的目标是手势路径中心处的最顶层节点,该目标可能与滚动事件的目标不一样,滚动事件的目标是手势开始处的最顶层节点。长方形和椭圆形在自己是由轻扫手势产生的滚动事件的目标时响应了该滚动事件。
处理触摸事件
当一块触摸屏被触摸时,会为每一个触摸点产生TOUCH_MOVED、TOUCHE_PRESSED、TOUCH_RELEASED或者TOUCH_STATIONARY事件。触摸事件会包含该触摸动作的所有触摸点的信息。例5-6展示了Gesture Events样例程序中长方形处理TOUCHE_PRESSED和TOUCH_RELEASED事件的Event Handler。椭圆形未处理触摸事件。
例5-6 定义触摸事件的Event Handler
rect.setOnTouchPressed(new EventHandler<TouchEvent>() {
@Override public void handle(TouchEvent event) {
log("Rectangle: Touch pressed event");
event.consume();
}
});
rect.setOnTouchReleased(new EventHandler<TouchEvent>() {
@Override public void handle(TouchEvent event) {
log("Rectangle: Touch released event");
event.consume();
}
});
对触摸事件的处理仅仅是在日志中记录了该事件。触摸事件可被用来提供对触摸或者手势中每个单独的触摸点进行更低水平的跟踪。
处理鼠标事件
鼠标的动作或者触摸触摸屏的动作均会产生鼠标事件。例5-7展示了Gesture Events样例程序中的椭圆形对MOUSE_PRESSED和MOUSE_RELEASED事件进行处理的Event Handler。
例5-7 定义鼠标事件的Event Handler
oval.setOnMousePressed(new EventHandler<MouseEvent>() {
@Override public void handle(MouseEvent event) {
if (event.isSynthesized()) {
log("Ellipse: Mouse pressed event from touch" +
", synthesized: " + event.isSynthesized());
}
event.consume();
}
});
oval.setOnMouseReleased(new EventHandler<MouseEvent>() {
@Override public void handle(MouseEvent event) {
if (event.isSynthesized()) {
log("Ellipse: Mouse released event from touch" +
", synthesized: " + event.isSynthesized());
}
event.consume();
}
只有当鼠标按下和释放的事件是由触摸触摸屏产生的时候,椭圆形才会处理对应的鼠标事件。长方形的鼠标事件Event Handler在日志中记录了所有的鼠标按下和释放的事件。
管理日志
Gesture Events样例程序展示了由屏幕上的图形所处理的事件的日志。一个ObervableList对象被用来记录每个图形的事件,一个ListView对象被用来显示事件的列表。该日志最多可展示50条记录。最新的记录会添加到列表的最上方,而最旧的记录会从底部移除。
管理日志的代码:
/**
* Creates a log that shows the events.
*/
private ListView<String> createLog(ObservableList<String> messages){
final ListView<String> log = new ListView<String>();
log.setPrefSize(500, 200);
log.setItems(messages);
return log;
}
/**
* Adds a message to the log.
*
* @param message Message to be logged
*/
private void log(String message) {
// Limit log to 50 entries, delete from bottom and add to top
if (events.size() == 50) {
events.remove(49);
}
events.add(0, message);
}