How To Implement JavaFX Scene Transition Animations

By Adam McQuistan in Java  09/06/2019 Comment

Introduction

In this How To article I demonstrate implementing scene transition, or view change, animations in a JavaFX application. In my opinion it is these types of finishes that help to provide a well polished and professional experience to users. As developers we are lucky in that the JavaFX UI framework makes it really easy to providing this enhanced user experience.

The code for this demo application is hosted on GitHub for you to clone and experiment with.

Contents

The Demo App and Key Concepts

To give the reader a clear idea of what will be accomplished in this article I'd like to start by showing what this looks like in the demo application.

The key ideas behind how I provide animation effects on the transition of view changes within JavaFX applications is to apply a series of descretized changes via KeyFrame and KeyValue instances over a specified time period executed with a Timeline or, in the case of fade transitions I use the FadeTransition class. These KeyValue, KeyFrame, Timeline, and FadeTransition classes all live in the javafx.animation package.

Ok, so those are the classes that I use to run descretized operations over a specified period of time but, I also need to give them visual properties to act on resulting in some visual change perceivable in the scene graph. In the case of this article I act on the opacity of the scene nodes to simulate fading in or out or, for sliding tranistions I use the translateXProperty or translateYProperty to make nodes move across the scene graph.

For the demo application I utilize a set of base layout nodes with an AnchorPane at the base (ie, root node) and a StackPane resting on top of the AnchorPane as it's only child and anchored to the four edges of the AnchorPane. For view changes I am swapping in and out different BorderPane instances containing Label's that indicate the type of transition that just transpired. Additionally, I use a ChoiceDialog<String> so a user can select the type of transition they would like to see occur which triggers a method call to generate the animated transition.

Here is the complete Application#start method overriden in App.java of the demo application.

package com.thecodinginterface.animatepages;

import javafx.application.Application;
// ... omitting imports (check GitHub repo for details)

public class App extends Application {
    static StackPane stackPane;

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        stackPane = new StackPane();

        var anchorPane = new AnchorPane(stackPane);
        anchorPane.setPrefSize(700, 500);

        AnchorPane.setTopAnchor(stackPane, 0.0);
        AnchorPane.setBottomAnchor(stackPane, 0.0);
        AnchorPane.setLeftAnchor(stackPane, 0.0);
        AnchorPane.setRightAnchor(stackPane, 0.0);

        var choiceDlg = new ChoiceDialog<String>();
        choiceDlg.getItems().addAll(
            "Slide In From Top",
            "Slide In From Right",
            "Slide In From Bottom",
            "Slide In From Left",
            "Fade In",
            "Fade Out"
        );

        choiceDlg.selectedItemProperty().addListener((obs, ov, nv) -> {
            if (nv != null) {
                switch(nv) {
                    case "Slide In From Top":
                        doSlideInFromTop();
                        break;
                    case "Slide In From Right":
                        doSlideInFromRight();
                        break;
                    case "Slide In From Bottom":
                        doSlideInFromBottom();
                        break;
                    case "Slide In From Left":
                        doSlideInFromLeft();
                        break;
                    case "Fade In":
                        doFadeIn();
                        break;
                    case "Fade Out":
                        doFadeOut();
                        break;
                }
            }
        });

        choiceDlg.setTitle("Transition Animations");
        choiceDlg.setHeaderText(null);
        choiceDlg.setGraphic(null);

        stackPane.getChildren().add(
        	  // makeSectionPane returns BorderPane with a Label of 
        	  // a specified background color
            makeSectionPane("JavaFX Scene Transition Animation", "orange")
        );

        primaryStage.setOnShown(evt -> {
            choiceDlg.showAndWait();
        });

        primaryStage.setScene(new Scene(anchorPane));
        primaryStage.show();
    }

  // ... omitting the methods described in the sections to folow
  
}

Slide In From Bottom

I start the examples with one where the new view being transitioned into is perceived as sliding into the applications viewing window from the bottom. I do this by adding the new BorderPane returned from calling makeSectionPane("Slide In From Bottom", "grey") method to the StackPane and translate it (aka slide it down) to the bottom of the visible region which is equal to the StackPane's height. Then I set up the KeyValue to interpolate the translateYProperty back to a value of zero using the EASE_IN interpolation algorithm. I specify that this should transpire over a period of 900 miliseconds using a KeyFrame instance then that is handed to a Timeline instance which manages the descretized transition. I add a event handler to the Timeline instance which removes the previous BorderPane at the end of the Timelines execution using a lambda on the setOnFinished action event handler.

static void doSlideInFromBottom() {
    var paneToRemove = stackPane.getChildren().get(0);
    var paneToAdd = makeSectionPane("Slide In From Bottom",  "grey");

    paneToAdd.translateYProperty().set(stackPane.getHeight());
    stackPane.getChildren().add(paneToAdd);
  
    var keyValue = new KeyValue(paneToAdd.translateYProperty(), 0, Interpolator.EASE_IN);
    var keyFrame = new KeyFrame(Duration.millis(900), keyValue);
    var timeline =  new Timeline(keyFrame);
    timeline.setOnFinished(evt -> {
        stackPane.getChildren().remove(paneToRemove);
    });
    timeline.play();
}

A friendly reminder is that UI graphics systems such as that persent in the JavaFX UI framework have their origin at the top left of the viewing area in contrast to the traditional Cartesian system.

Slide In From Top

Sliding in from the top is just the inverse of sliding in from the bottom. I translate it up by a the negative amount of the height then make it transition back to the origin during the duration of the Timeline execution.

static void doSlideInFromTop() {
    var paneToRemove = stackPane.getChildren().get(0);
    var paneToAdd = makeSectionPane("Slide In From Top", "brown");

    paneToAdd.translateYProperty().set(-1 * stackPane.getHeight());

    stackPane.getChildren().add(paneToAdd);

    var keyValue = new KeyValue(paneToAdd.translateYProperty(), 0, Interpolator.EASE_IN);
    var keyFrame = new KeyFrame(Duration.millis(900), keyValue);
    var timeline = new Timeline(keyFrame);
    timeline.setOnFinished(evt -> {
        stackPane.getChildren().remove(paneToRemove);
    });
    timeline.play();
}

Slide In From Right

Sliding in from the right requires initially translating the X coordinates to the right by the width of the StackPane then interpolating the X coordinate back to the origin over the duration of the Timeline execution before finally removing the previously loaded pane.

static void doSlideInFromRight() {
    var paneToRemove = stackPane.getChildren().get(0);
    var paneToAdd = makeSectionPane("Slide in From Right", "green");

    paneToAdd.translateXProperty().set(stackPane.getWidth());
    stackPane.getChildren().add(paneToAdd);

    var keyValue = new KeyValue(paneToAdd.translateXProperty(), 0, Interpolator.EASE_IN);
    var keyFrame = new KeyFrame(Duration.millis(900), keyValue);
    var timeline = new Timeline(keyFrame);
    timeline.setOnFinished(evt -> {
        stackPane.getChildren().remove(paneToRemove);
    });
    timeline.play();
}

Slide In From Left

Again, sliding in from the left is just the inverse of sliding in from the right. I translate the X coordinate by a negative amount of the StackPane width and transition it back to zero over the duration of the Timeline then remove the previously shown pane.

static void doSlideInFromLeft() {
    var paneToRemove = stackPane.getChildren().get(0);
    var paneToAdd = makeSectionPane("Slide in From Left", "pink");

    paneToAdd.translateXProperty().set(-1 * stackPane.getWidth());
    stackPane.getChildren().add(paneToAdd);

    var keyValue = new KeyValue(paneToAdd.translateXProperty(), 0, Interpolator.EASE_IN);
    var keyFrame = new KeyFrame(Duration.millis(900), keyValue);
    var timeline = new Timeline(keyFrame);
    timeline.setOnFinished(evt -> {
        stackPane.getChildren().remove(paneToRemove);
    });
    timeline.play();
}

Fade In

The fade animations involve changing the opacity of the top most layout node which is a BorderPane in this example. For the fade in transition I add the new pane to the top of the StackPane then create a FadeTransition that takes 600 miliseconds and run the transition from an opacity of 0 to 1 before removing the previous BorderPane resulting in the new pane fading into appearance.

static void doFadeIn() {
    var paneToRemove = stackPane.getChildren().get(0);
    var paneToAdd = makeSectionPane("Fade In", "red");

    stackPane.getChildren().add(paneToAdd);
    var fadeInTransition = new FadeTransition(Duration.millis(600));

    fadeInTransition.setOnFinished(evt -> {
        stackPane.getChildren().remove(paneToRemove);
    });
    fadeInTransition.setNode(paneToAdd);
    fadeInTransition.setFromValue(0);
    fadeInTransition.setToValue(1);
    fadeInTransition.play();
}

Fade Out

Once again, the inverse is also possible which results in a fading out of the existing pane before removing it completely from the scene graph.

static void doFadeOut() {
    var paneToRemove = stackPane.getChildren().get(0);
    stackPane.getChildren().add(0, makeSectionPane("Fade Out", "blue"));

    var fadeOutTransition = new FadeTransition(Duration.millis(600));

    fadeOutTransition.setOnFinished(evt -> {
        stackPane.getChildren().remove(paneToRemove);
    });

    fadeOutTransition.setNode(paneToRemove);
    fadeOutTransition.setFromValue(1);
    fadeOutTransition.setToValue(0);
    fadeOutTransition.play();
}

Learn More About Java and JavaFX

Conclusion

In this How To article I have demonstrated the implementation of page, or view, transitions in a JavaFX application using a couple of simple animations.

As always, thanks for reading and don't be shy about commenting or critiquing below.

Share with friends and colleagues

[[ likes ]] likes

Community favorites for Java

theCodingInterface