Visualisierung in Java mit JavaFX

WebView und Animation

Browserkontrolle mit WebView

In JavaFX gibt es einen Node mit der Möglichkeit, HTML zu rendern – also mit anderen Worten eine Browser Control, genannt WebView. Der WebView Node kapselt dabei eine WebKit Rendering Engine, die den meisten geläufig sein sollte. Sie ist Bestandteil von Chrome, Safari und den Browsern auf den mobilen Platformen Android und iOS. WebKit ermöglicht es, HTML5-Inhalte in einer JavaFX-Anwendung zu verwenden (zum Beispiel Google Maps). Eine Website lässt sich wie folgt in WebView laden:

WebView   browser = new WebView();
WebEngine webkit = browser.getEngine();
webkit.load("http://maps.google.de");

Stackpane root = new StackPane();
root.getChildren().add(browser);

Scene scene = new Scene(root, 300, 300);

stage.setScene(scene);
stage.show();

Die ersten drei Zeilen laden die Seite, der Rest des Listings dient lediglich zur Darstellung des WebViews in der Applikation (Abb. 4).

Beispiel: WebView mit Google Maps (Abb. 4)

Ständig in Bewegung

Damit man zeitgemäße Anwendungen erstellen kann, ist es durchaus sinnvoll, hier und da verschiedene Effekte und Animationen in seiner Anwendung einzusetzen (wobei man damit sparsam umgehen sollte). JavaFX bietet hierfür die Möglichkeit, Nodes im Scene Graph zu animieren und mit Effekten wie Schatten (InnerShadow und DropShadow) zu versehen. Möchte man also einem Node im Scene Graph einen Schatten hinzufügen, ist dazu lediglich die Methode setEffect() aufzurufen und als Parameter ein DropShadow-Objekt zu übergeben.

// Create DropShadow Object
DropShadow shadow = new DropShadow();
shadow.setOffsetX(0);
shadow.setOffsetY(2);
shadow.setRadius(5);
shadow.setColor(Color.DARKGRAY);

Button button = new Button("Click Me");
button.setEffect(shadow);
Links ein Button ohne, rechts einer mit Schattenwurf (Abb. 5)

In Abbildung 5 sieht man im Vergleich einen Button ohne (links) und einen mit (rechts) DropShadow.

Mit den Animationen verhält es sich ähnlich einfach wie mit den Effekten. Animationen sind fester Bestandteil der JavaFX API und lassen sich über sogenannte Timeline-Objekte verwenden. Dabei kommt das Konzept sogenannter KeyFrames zum Einsatz, das im folgenden kurz erläutert werden soll.

Zunächst legt man ein Timeline-Objekt an, das man später verwenden möchte. Es ist nicht notwendig für jede Animation eine neue Timeline anzulegen, lediglich das Timeline-Objekt ist mit neuen KeyFrames zu bestücken. Möchte man beispielsweise ein Rechteck von links nach rechts über den Bildschirm bewegen, würde man folgendermaßen vorgehen:

  1. Neues Timeline-Objekt erzeugen.
  2. Neues KeyValue-Objekt für die xProperty des Rechtecks erzeugen und mit Zielwert versehen.
  3. Neues KeyFrame-Objekt erzeugen, den Zeitpunkt und den KeyValue festlegen.
  4. Die Timeline abspielen.

Man erzeugt also ein sogenanntes KeyValue-Objekt für die xProperty des Rechtecks. Bei dem Objekt gibt man nun den Zielwert für die ausgewählte Eigenschaft an (in vorliegenden Fall ließe sich zum Beispiel 300 für die Zielposition von x festlegen) und falls gewünscht zusätzlich noch die zu verwendende Interpolation für die Animation (zum Beispiel linear, ease in, ease both etc.). Mit diesem KeyValue-Objekt legt man nun ein KeyFrame-Objekt an, in dem man angibt, nach welcher Zeit das entsprechende KeyValue-Objekt seinen Wert erreichen soll. Danach fügt man den KeyFrame zum Timeline-Objekt hinzu und kann die Timeline abspielen. Der Code für dieses Beispiel sähe wie folgt aus:

@Override public void start(Stage stage) {
Rectangle rect = new Rectangle(10, 50, 25, 25);

// Create the KeyValue for the xProperty and value of 300px
KeyValue keyValue = new KeyValue(rect.xProperty(), 300,
Interpolator.EASE_BOTH);

// Create the KeyFrame with the KeyValue and the time of 2 sec
KeyFrame keyFrame = new KeyFrame(Duration.millis(2000),
keyValue);

// Create the timeline and add the KeyFrame
Timeline timeline = new Timeline();
timeline.getKeyFrames().add(keyFrame);

// Add the rectangle to a layout container and create a scene
Pane pane = new Pane();
pane.getChildren().add(rect);

Scene scene = new Scene(pane, 335, 125);

stage.setTitle("Animation");
stage.setScene(scene);
stage.show();

// Play the timeline
timeline.play();
}
Beispiel für eine Animation mit AnimationTimer (Abb. 6)


Aus Swing-Zeiten ist es bekannt, mithilfe eines javax.swing.Timer-Objekts intervalgesteuerte Aufrufe auf dem Event Dispatch Thread auszuführen. Eine ähnliche Funktion steht auch unter JavaFX mit dem AnimationTimer zur Verfügung, der durch den sogenannten Pulse (getriggert mit 60 FPS) aufgerufen wird.

Da der AnimationTimer aus dem FX Application Thread aufgerufen wird, kann man in seiner Behandlungsroutine eigene Berechnungen für zeitlich gesteuerte Aufrufe erstellen. Die zeitliche Auflösung liegt im Nanosekundenbereich, im Vergleich zu Mikrosekunden beim Swing Timer. Möchte man also beispielsweise alle zwei Sekunden den Zustand einer Shape ändern (zum Beispiel Blinken eines Kreises), würde der benötigte Code folgendermaßen aussehen:

private static final long INTERVAL      = 2_000_000_000l;
private Circle circle = new Circle(50);
private long lastTimerCall = System.nanoTime();
private boolean toggle = false;
private AnimationTimer timer = new AnimationTimer() {
@Override public void handle(long now) {
if(now > lastTimerCall + INTERVAL) {
toggle ^= true;
circle.setVisible(toggle);
lastTimerCall = now;
}
}
};

@Override public void start(Stage stage) {
StackPane pane = new StackPane();
pane.setPadding(new Insets(10, 10, 10, 10));
pane.getChildren().add(circle);

Scene scene = new Scene(pane);

stage.setScene(scene);
stage.show();

// Start the timer
timer.start();
}

Startet man das Programm, blinkt alle zwei Sekunden ein schwarzer Kreis.

Auch zeitlich gesteuerte Animationen sind mit AnimationTimer möglich (Abb. 7).

Damit stehen dem Entwickler alle Möglichkeiten zur Verfügung, die man benötigt, um grafische Ausgaben zeitgesteuert zu verändern.