Chapter 18
In This Chapter
Discovering gestures on touch devices
Adding listeners that respond to gesture events
Creating a program that lets the user manipulate a shape with gestures
Touch devices are everywhere nowadays. Tablets and smartphones are the most common types of touch devices, but even some desktop users have installed touch-capable monitors. At one time, touch devices ran primarily non-Microsoft operating systems, such as Apple’s iOS or Google’s Android. But now, with Windows 8, many touch devices even run Windows.
Fortunately, JavaFX can run on all those platforms. In this chapter, you discover how to develop JavaFX applications that take advantage of the unique user interactions that are possible with touch devices, including basic touch and swiping and scrolling as well as multi-touch gestures such as zooming and rotating.
Before I get into the details of working with gestures and touch events, I want to point out that the support for basic touch devices for JavaFX controls is already built-in and requires no programming to enable. For example, Button controls respond to taps on a touch device just as they respond to mouse clicks. So no special programming is needed to enable the basic operation of JavaFX controls on a touch device.
The good news is that the programming required to handle more advanced touch gestures, such as zooming or rotating, is pretty straightforward. JavaFX handles the difficult tasks of figuring out what gestures the user is making when he touches the screen. For example, when JavaFX sees that the user has touched the screen with two fingers and then rotated the fingers in opposite directions, the user is attempting to rotate an object on the screen. JavaFX figures out which object is the target of the rotation, figures out how much the user has rotated the object, and then sends a ROTATE event to the target. All you have to do is provide an event listener for the ROTATE event.
JavaFX provides event handling for four distinct types of touch gestures:
All rotation events are represented by a RotateEvent object, which defines two key methods:
Zoom events are defined by the ZoomEvent class, which has two important methods:
The zoom factor is a multiplier you can use to determine the new size of the zoomed object. For example, if the user doubles the size of the target object, the zoom factor will be 2.0.
Scroll events are represented by the ScrollEvent class, which provides several methods to retrieve the scroll amount:
Swipe events are represented by the SwipeEvent class.
Swipe events and scroll events are difficult to distinguish because a swipe is simply a fast scroll in either a horizontal or vertical direction. Thus, when the user performs a swipe gesture, scroll events are generated in addition to the swipe event.
You can install a listener for any gesture on any node object by calling one of the methods listed in Table 18-1. As with all JavaFX events, these events use functional interfaces, so you can easily implement the event listeners with Lambda expressions.
Table 18-1 Node Methods for Installing Gesture Event Listeners
Method |
Explanation |
void setOnRotate(RotateEvent listener) |
Creates a listener for the ROTATE event. |
void setOnRotateStarted(RotateEvent listener) |
Creates a listener for the ROTATE_STARTED event. |
void setOnRotateFinished(RotateEvent listener) |
Creates a listener for the ROTATE_FINISHED event. |
void setOnZoom(ZoomEvent listener) |
Creates a listener for the ZOOM event. |
void setOnZoomStarted(ZoomEvent listener) |
Creates a listener for the ZOOM_STARTED event. |
void setOnZoomFinished(ZoomEvent listener) |
Creates a listener for the ZOOM_FINISHED event. |
void setOnScroll(ScrollEvent listener) |
Creates a listener for the SCROLL event. |
void setOnScrollStarted(ScrollEvent listener) |
Creates a listener for the SCROLL_STARTED event. |
void setOnScrollFinished(ScrollEvent listener) |
Creates a listener for the SCROLL_FINISHED event. |
void setOnSwipeLeft(SwipeEvent listener) |
Creates a listener for the SWIPE_LEFT event. |
void setOnSwipeRight(SwipeEvent listener) |
Creates a listener for the SWIPE_RIGHT event. |
void setOnSwipeUp(SwipeEvent listener) |
Creates a listener for the SWIPE_UP event. |
void setOnSwipeDown(SwipeEvent listener) |
Creates a listener for the SWIPE_DOWN event. |
Here’s an example that creates a rectangle, and then installs a listener for the rotate event and adjusts the rectangles rotation when the rotate event is fired:
Rectangle r = new Rectangle(150, 150, 200, 200);
r.setFill(Color.DARKGRAY);
r.setOnRotate(e ->
{
r.setRotate(r.getRotate() + e.getAngle());
e.consume();
});
Here, the rotation of the rectangle is modified by first retrieving the current rotation and then adding the angle retrieved from the rotate event.
You can call the gesture event’s isInertia method to determine whether an event was created as a result of inertia. Then, if you want to suppress the effect of inertia, you can ignore the event if isInertia returns true. For example:
Rectangle r = new Rectangle(150, 150, 200, 200);
r.setFill(Color.DARKGRAY);
r.setOnRotate(e ->
{
if (!isInertia())
{
r.setRotate(r.getRotate() + e.getAngle());
e.consume();
}
});
Here, the rectangle is rotated only if the event is not generated as a result of inertia.
In this section, you look at a sample program — the Gesturator. The Gesturator program displays a simple rectangle on the screen and allows the user to manipulate the rectangle using gestures. Figure 18-1 shows the scene displayed by the Gesturator program.
Here are the key features of the Gesturator program:
Listing 18-1 provides the complete source code for the Gesturator program.
Listing 18-1: The Gesturator Program
import javafx.application.*;
import javafx.scene.*;
import javafx.stage.*;
import javafx.scene.control.*;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.scene.input.*;
import javafx.event.*;
public class Gesturator extends Application
{
public static void main(String[] args)
{
launch(args);
}
private final double RECT_X = 200; →18
private final double RECT_Y = 200;
private final double SCENE_X = 600;
private final double SCENE_Y = 600;
@Override public void start(Stage primaryStage)
{
Group root = new Group();
Rectangle r = →27
new Rectangle((SCENE_X - RECT_X)/2,
(SCENE_Y - RECT_Y)/2,
RECT_X,
RECT_Y);
r.setFill(Color.DARKGRAY);
r.setStroke(Color.BLACK);
r.setStrokeWidth(2);
r.setOnZoom(e -> →36
{
r.setScaleX(r.getScaleX() * e.getZoomFactor());
r.setScaleY(r.getScaleY() * e.getZoomFactor());
e.consume();
});
r.setOnRotate(e -> →43
{
r.setRotate(r.getRotate() + e.getAngle());
e.consume();
});
r.setOnScroll(e -> →49
{
if (!e.isInertia()) →51
{
double newX = r.getX() + e.getDeltaX(); →53
if ( (newX >= 0) &&
(newX <= SCENE_X - r.getWidth()) )
{
r.setX(newX);
}
double newY = r.getY() + e.getDeltaY(); →59
if ( (newY >= 0) &&
(newY <= SCENE_Y - r.getHeight()) )
{
r.setY(newY);
}
}
e.consume();
});
r.setOnSwipeLeft(e -> →69
{
r.setX(0);
e.consume();
});
r.setOnSwipeRight(e -> →75
{
r.setX(SCENE_X - r.getWidth());
e.consume();
});
r.setOnSwipeUp(e -> →81
{
r.setY(0);
e.consume();
});
r.setOnSwipeDown(e -> →87
{
r.setY(SCENE_Y - r.getHeight());
e.consume();
});
root.getChildren().add(r); →93
Scene scene = new Scene(root, SCENE_X, SCENE_Y);
primaryStage.setTitle("The Gesturator");
primaryStage.setScene(scene);
primaryStage.show();
}
}
The following paragraphs explain the key points of the Gesturator program:
→ 18: Private fields are used to set the size of the rectangle and the size of the scene. These constants are referred to several times throughout the program.
→ 27: The rectangle is created using the size specified by RECT_X and RECT_Y. The initial center position is calculated using the size of the scene and the size of the rectangle.
→ 36: The zoom event listener adjusts the size of the rectangle by multiplying its current size by the zoom factors provided by the ZoomEvent object.
→ 43: The rotate event listener rotates the rectangle by adding the rotation angle provided by the RotateEvent object to the current rotation value of the rectangle.
→ 49: The scroll event handler is a little more complicated than the other event handlers because it imposes several constraints on the position of the rectangle.
→ 51: The scroll event is ignored if it is the result of inertia. That way, the rectangle stops its movement immediately when the user stops the scroll gesture.
→ 53: To determine the new x position, the scroll event listener first calculates the proposed new x position by adding the amount of horizontal movement (getDeltaX) to the current x position. Then, it sets the rectangles x position to the proposed position only if the proposed position is greater than zero and less than the width of the screen minus the current width of the rectangle. The if statement prevents the user from moving the rectangle past the left or right edge of the scene.
→ 59: Similar logic is used to change the y position. First, the proposed y position is calculated; then, the proposed y position is applied only if it does not move the rectangle past the top or bottom edges of the scene.
→ 69: The swipe left listener moves the rectangle to the left edge of the screen.
→ 75: The swipe right listener moves the rectangle to the right edge of the screen.
→ 81: The swipe up listener moves the rectangle to the top edge of the screen.
→ 87: The swipe down listener moves the rectangle to the bottom edge of the screen.
→ 93: The rectangle is added to the root node. Then, the scene is created and displayed on the stage.