JavaFX comes with a number of animated transitions out of the box which are very easy to integrate into an application without too much effort. There are times though when the existing animations don’t cover a desired transition between UI states. Fortunately, implementing a custom animation is as easy as subclassing the javafx.animation.Transition class and providing an implementation of the interpolate() method. As an example, I had a JavaFX Parent component which I wanted to “grow” to a larger height if a large component was placed inside it. Rather than having the Parent component just jump to its new larger height, I wanted the transition to the new height to be animated over a specified amount of time. I was able to accomplish this effect by creating the ResizeHeightTranslation below.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javafx.animation.Transition; | |
import javafx.scene.layout.Region; | |
import javafx.util.Duration; | |
public class ResizeHeightTranslation extends Transition { | |
protected Region region; | |
protected double startHeight; | |
protected double newHeight; | |
protected double heightDiff; | |
public ResizeHeightTranslation( Duration duration, Region region, double newHeight ) { | |
setCycleDuration(duration); | |
this.region = region; | |
this.newHeight = newHeight; | |
this.startHeight = region.getHeight(); | |
this.heightDiff = newHeight – startHeight; | |
} | |
@Override | |
protected void interpolate(double fraction) { | |
region.setMinHeight( startHeight + ( heightDiff * fraction ) ); | |
} | |
} |
In the constructor the duration of the animation is passed to the parent class, and the amount of space the component will need to grow is calculated.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public ResizeHeightTranslation( Duration duration, Region region, double newHeight ) { | |
setCycleDuration(duration); | |
this.region = region; | |
this.newHeight = newHeight; | |
this.startHeight = region.getHeight(); | |
this.heightDiff = newHeight – startHeight; | |
} |
All that remains to do in the interpolate() method is to calculate the new height based on how far along the animation is. The interpolate() method will be called by the JavaFX runtime with values starting from 0.0 and going to 1.0 as the animation progresses.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Override | |
protected void interpolate(double fraction) { | |
region.setMinHeight( startHeight + ( heightDiff * fraction ) ); | |
} |
Example Usage
In the example below I have a JavaFX Pane (salmon color), and a Button. When the Button is clicked I would like to add a TableView to the Pane. The TableView however is taller than the Pane is so I would like the Pane to increase in height so the table fits completely within the Pane. To give this state transition a polished effect, I would like the resizing of the Pane to be animated and then I would like the TableView to fade into view once the resizing is complete. Below is a screenshot of the UI, containing the Pane and the Button components.
The FXML for the UI is below
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<?import java.lang.*?> | |
<?import java.util.*?> | |
<?import javafx.scene.*?> | |
<?import javafx.scene.control.*?> | |
<?import javafx.scene.layout.*?> | |
<AnchorPane id="AnchorPane" prefHeight="346.0" prefWidth="498.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.40" fx:controller="com.lynden.resizetest.FXMLController"> | |
<children> | |
<Button fx:id="clickMeButton" layoutX="394.0" layoutY="294.0" onAction="#handleButtonAction" text="Click Me!" /> | |
<Pane fx:id="contentPane" layoutX="25.0" layoutY="22.0" prefHeight="158.0" prefWidth="243.0" style="-fx-background-color: salmon;" /> | |
</children> | |
</AnchorPane> |
In the controller class for the UI I have implemented and ActionListener method for the Button. When the Button is clicked a new TableView is created, along with a new ResizeHeightTranslation animation and a FadeTransition. Both of these animations are set to execute one right after the other by putting them in a SequentialTransition.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.lynden.glitter.animation.ResizeHeightTranslation; | |
import java.net.URL; | |
import java.util.ResourceBundle; | |
import javafx.animation.FadeTransition; | |
import javafx.animation.SequentialTransition; | |
import javafx.event.ActionEvent; | |
import javafx.fxml.FXML; | |
import javafx.fxml.Initializable; | |
import javafx.scene.control.Button; | |
import javafx.scene.control.TableView; | |
import javafx.scene.layout.Pane; | |
import javafx.util.Duration; | |
public class FXMLController implements Initializable { | |
@FXML | |
private Button clickMeButton; | |
@FXML | |
private Pane contentPane; | |
@FXML | |
private void handleButtonAction(ActionEvent event) { | |
TableView tableView = new TableView(); | |
tableView.setMaxHeight(250); | |
tableView.setMinHeight(250); | |
ResizeHeightTranslation rht = new ResizeHeightTranslation(Duration.millis(1000), contentPane, tableView.getMinHeight() ); | |
FadeTransition ft = new FadeTransition(Duration.millis(1000), tableView); | |
ft.setFromValue(0); | |
ft.setToValue(1); | |
SequentialTransition pt = new SequentialTransition(rht, ft); | |
pt.play(); | |
contentPane.getChildren().add(tableView); | |
} | |
@Override | |
public void initialize(URL url, ResourceBundle rb) { | |
} | |
} |
Finally, a short clip illustrating the animated transition in action.
twitter: @RobTerpilowski
Pingback: Java desktop links of the week, September 7 « Jonathan Giles
Pingback: JavaFX links of the week, September 7 // JavaFX News, Demos and Insight // FX Experience