Adding Animation with JavaFX: Spicing Up a Clear

We return to the same drawing program we described in two previous posts, Sketch Pad: Custom Binding and JavaFX Sketch Pad: Version 2. In this third version, we’re going to spice up the Clear button by animating a fade. That is, when a user clicks Clear, the drawing elements fade out, one at a time in the reverse order in which they were drawn. So, in the following screenshot with a wonderful hand-drawn greeting (“Hi JavaFX!”) on a dark blue background, the exclamation point fades first, then the ‘X’, followed by the ‘F’, and so on. The last to fade are the dark blue background strokes.

drawFade1

Here is the original Clear button event handler, which simply removes all of the Path nodes in Group lineGroup.

Button btnClear = new Button();
btnClear.setText("Clear");
btnClear.setOnAction(new EventHandler<ActionEvent>() {
    public void handle(ActionEvent event) {
        lineGroup.getChildren().removeAll(
              lineGroup.getChildren());
    }
});

In order to construct an animated fade, we’ll use JavaFX Transitions. Transitions are high-level animation constructs that build timelines, key frames, and key values for you. Transitions are defined to animate key JavaFX node properties. For example, the FadeTransition animates opacity, TranslateTransition animates translateX, translateY, and translateZ, FillTransition animates fill, and ScaleTransition animates the scaleX, scaleY, and scaleZ values.

Furthermore, JavaFX lets you group animations into parallel or sequential transitions. Parallel transitions manage a collection of animations that execute in parallel, while sequential transitions manage animations that execute one after the other. For our application, we’ll use a FadeTransition to fade the nodes and a sequential transition to fade the Path nodes one at a time.

// Class variable
private SequentialTransition seqtran = null;
. . .

Recall that Group lineGroup holds a collection of Path nodes. Each node contains one or more PathElements that form the drawing element. Let’s build a FadeTransition for each Path node. A FadeTransition animates a node’s opacityProperty. To fade out a node (have it disappear), set FadeTransition’s toValue to 0.0. To fade in a node (make it appear), set toValue to 1.0. If you don’t provide a value for fromValue, FadeTransition uses the node opacityProperty‘s current value.

So let’s see how our new Clear button event handler works with Transitions. First, if the drawing canvas is empty, we don’t do anything and just return. If the sequential transition (seqtran) isn’t null, then we’ll stop it. We instantiate a new SequentialTransition and go through the collection of nodes in Group lineGroup to construct a FadeTransition for each node. We specify its duration to be 1,000 milliseconds, set toValue to 0 (making the node fade out), and set interpolator to Interpolator.EASE_BOTH. The default interpolator is LINEAR, providing a constant rate of change for the animation. EASE_IN provides a slower rate of change at the onset of animation, whereas EASE_OUT is a slower rate of change at the end. EASE_BOTH provides slowing at both ends of the animation.

After configuring the FadeTransition, we add it to the beginning of the SequentialTransition’s collection of animations, providing the reverse ordering.

When everything is configured, we initiate the sequential animation with method playFromStart().

Button btnClear = new Button();
btnClear.setText("Clear");
btnClear.setOnAction(new EventHandler<ActionEvent>() {
    public void handle(ActionEvent event) {
        if (lineGroup.getChildren().size() == 0) {
            return;
        }
        if (seqtran != null) {
            seqtran.stop();
        }
        seqtran = new SequentialTransition();
        for (final Node node : lineGroup.getChildren()) {
            FadeTransition ft = new FadeTransition(
                     Duration.millis(1000), node);
            ft.setToValue(0.0);
            ft.setInterpolator(Interpolator.EASE_BOTH);
            // fade in reverse order
            seqtran.getChildren().add(0, ft);
        }

        // Code here to remove the nodes from Group lineGroup
        . . .
        seqtran.playFromStart();
    }
});

Ah, but we aren’t quite finished yet. We don’t want to just fade out the nodes, we also want to remove them from the scene graph. To do this, we must make sure the fade animation is finished. No problem.  We provide an onFinished function for the sequential transition.

Here is the rest of the Clear button’s event handler. Note that we use a lambda expression.


. . .
// inside btnClear event handler
// Code here to remove the nodes from Group lineGroup
. . .
        seqtran.setOnFinished((ActionEvent event1) -> {
            lineGroup.getChildren().removeAll(lineGroup.getChildren());
        });

Download the Sketch Pad with Animated Clear JavaFX source code here.

3 Comments

  1. Pingback: Java desktop links of the week, November 7 | Jonathan Giles

  2. I’m asking same suoqtien.Suppose you’re designing a fancy window (node), form, etc. in JavaFX, but your customer want to add a jtable not javafx table inside it. Is it still possible?Here, JTable stands for a legacy component. you’ve a special jtable component doing a lot of jobs. With many special renderers, editors. You want to reuse it.Another suoqtien, the current approach does not meet some needs such as:You’re using a swing component listening mouse events at the background. On top of it, you’re placing a javafx container occupying whole screen (or swing component). Above picture shows the same example. Normally, you cannot click swing button, because jfxpanel will hangle events. If you delegate the event, when you click javafx button, both javafx and swing button will fire. One approach to do this, listening javafx scene and delagate the events. But the events should be converted javafx MouseEvents to awt MouseEvents. There is no easy mechanism for this. I think, this should be included in javafx.

    • It is now easy to embed JavaFX scene graph inside a Swing component using JFXPanel. Inside the JavaFX you can process mouse events as JavaFX events. Outside the JFXPanel, these will be Swing events. You do have to pay attention to threading issues, since JavaFX events and changes to the JavaFX scene graph must be processed in the JavaFX application thread. So, if you’re in a Swing thread, use Platform.runLater() to process in the JavaFX application thread. Conversely, use SwingUtilities.invokeLater() to make sure you’re in the Swing Event Dispatch Thread (EDT). See this documentation.

Comments are closed.