JavaFX animations playing simultaneously in timeline

100 Views Asked by At

I'm pretty new to JavaFX. I am trying to make this timeline where each keyframe happens after the previous one has finished. So far, each of the steps happen one after the other like I want it to. However, the rotateBack keyframe starts rotating my object as soon as I play the animation. Any tips on how to make rotateBack only play right after moveToRight? Also, I would like for rotateBack to finish its rotation before moveToTop plays. Thanks.

Pane selectedPane = select_pane.getValue();
                    
    double rootWidth = root_pane.getWidth();
    double rootHeight = root_pane.getHeight();
    double sixthWidth = rootWidth / 6;

    KeyValue moveTopLeftX = new KeyValue(selectedPane.layoutXProperty(), 0);
    KeyValue moveDownY = new KeyValue(selectedPane.layoutYProperty(), root_pane.getHeight()                   -    selectedPane.getHeight());

    KeyValue moveRightX = new KeyValue(selectedPane.layoutXProperty(), sixthWidth);
    KeyValue rotate180 = new KeyValue(selectedPane.rotateProperty(), 180);
    KeyValue moveToTopY = new KeyValue(selectedPane.layoutYProperty(), 0);

    KeyFrame startFrame = new KeyFrame(Duration.seconds(3), moveTopLeftX, moveToTopY);
    KeyFrame moveToRight = new KeyFrame(Duration.seconds(2), moveRightX);
    KeyFrame rotateBack = new KeyFrame(Duration.seconds(3), rotate180);
    KeyFrame moveToBottom = new KeyFrame(Duration.seconds(6), moveDownY);
    KeyFrame moveToTop = new KeyFrame(Duration.seconds(6), moveToTopY);

    Timeline timeline = new Timeline(startFrame, moveToBottom, moveToRight, rotateBack, moveToTop);

    timeline.play();

What I've tried so far is shown in the code above. Any tips very much appreciated.

2

There are 2 best solutions below

0
Thomas Liné On BEST ANSWER

To play a keyframe only after one is finished you can create different Timeline and in the timeline.setOnFinished() function you can play the next one.
For your example it would look like this :

Pane selectedPane = select_pane.getValue();
                
double rootWidth = root_pane.getWidth();
double rootHeight = root_pane.getHeight();
double sixthWidth = rootWidth / 6;

KeyValue moveTopLeftX = new KeyValue(selectedPane.layoutXProperty(), 0);
KeyValue moveDownY = new KeyValue(selectedPane.layoutYProperty(), root_pane.getHeight()                   -    selectedPane.getHeight());

KeyValue moveRightX = new KeyValue(selectedPane.layoutXProperty(), sixthWidth);
KeyValue rotate180 = new KeyValue(selectedPane.rotateProperty(), 180);
KeyValue moveToTopY = new KeyValue(selectedPane.layoutYProperty(), 0);

KeyFrame startFrame = new KeyFrame(Duration.seconds(3), moveTopLeftX, moveToTopY);
KeyFrame moveToRight = new KeyFrame(Duration.seconds(2), moveRightX);
KeyFrame rotateBack = new KeyFrame(Duration.seconds(3), rotate180);
KeyFrame moveToBottom = new KeyFrame(Duration.seconds(6), moveDownY);
KeyFrame moveToTop = new KeyFrame(Duration.seconds(6), moveToTopY);

Timeline timelineStart = new Timeline(startFrame, moveToBottom, moveToRight);

Timeline timelineRotateBack = new Timeline(rotateBack);

Timeline timelineMoveToTop = new Timeline(moveToTop);

timelineStart.setOnFinished(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                timelineRotateBack.play();
            }
        });

timelineRotateBack.setOnFinished(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                timelineMoveToTop.play();
            }
        });

timelineStart.play();

Here it will play all these KeyFrames (startFrame, moveToBottom, moveToRight) first and when all animations are finished it will play rotateBack KeyFrame and when this one is finished it will play moveToTop

0
jewelsea On

A Transition is a good fit for this problem

An example is provided which is based on a SequentialTransition, rather than a Timeline. You could use a Timeline instead, as noted in other comments or answers. However, for the functionality you describe, using transitions seems to be a better fit.

The example sequences various transactions of types TranslateTransition and RotateTransition to perform the required animation.

If you wanted parts of the sequence to be performed in parallel, then you can use a ParallelTransition for those portions (parallel and sequential transitions can be combined if desired).

Use translate properties for animation

The translateX/Y/Z properties should be used when performing animation rather than the layoutX/Y properties. While you can animate layout changes, that is an advanced topic and not recommended for somebody pretty new to JavaFX. The example provided here is based on translate properties and not layout properties.

Choose an appropriate Interpolator

The default interpolater for key frames is LINEAR, and for transitions is EASE_BOTH. Choose an interpolator appropriate for your application by setting the interpolator on your key frames or transitions. For this example, I felt that the default EASE_BOTH interpolation for transitions was the best fit, resulting in a smoother animation feel.

Example Code

import javafx.animation.*;
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

import java.io.IOException;

public class TransitionApp extends Application {
    private static final Color INDIA_INK = Color.web("#3d3f4a");
    private static final double W = 400, H = 400;
    private static final double BOX_W = 20, BOX_H = 20;
    private static final double BOX_TOP_SIDE_INDICATOR_H = 3;
    private static final double MOVE_STEP = 50;
    private static final Duration TIME_STEP = Duration.seconds(3);

    public void start(Stage stage) throws IOException {
        Group box = createBox(BOX_W, BOX_H, BOX_TOP_SIDE_INDICATOR_H);
        box.setTranslateX(W / 2 - BOX_W / 2);
        box.setTranslateY(H / 2 - BOX_H / 2);

        Pane root = new Pane(box);
        root.setBackground(Background.fill(INDIA_INK));
        root.setPrefSize(W, H);

        stage.setResizable(false);
        stage.setScene(new Scene(root));
        stage.show();

        SequentialTransition transition = createTransition(box);
        transition.play();
    }

    private SequentialTransition createTransition(Group box) {
        TranslateTransition moveToTopLeft = new TranslateTransition(TIME_STEP);
        moveToTopLeft.setToX(0);
        moveToTopLeft.setToY(0);

        TranslateTransition moveToBottom = new TranslateTransition(TIME_STEP.multiply(2));
        moveToBottom.setToY(H - BOX_H);

        TranslateTransition moveRight = new TranslateTransition(TIME_STEP);
        moveRight.setToX(MOVE_STEP);

        RotateTransition rotate180 = new RotateTransition(TIME_STEP);
        rotate180.setByAngle(180);

        TranslateTransition moveToTop = new TranslateTransition(TIME_STEP.multiply(2));
        moveToTop.setToY(0);

        SequentialTransition sequentialTransition = new SequentialTransition(
                box,
                moveToTopLeft, moveToBottom, moveRight, rotate180, moveToTop
        );
        sequentialTransition.setAutoReverse(true);
        sequentialTransition.setCycleCount(Transition.INDEFINITE);
        
        return sequentialTransition;
    }

    private static Group createBox(double w, double h, double topSideIndicatorH) {
        Rectangle rectangle = new Rectangle(
                w, h, Color.GREEN
        );

        Rectangle topLine = new Rectangle(
                w, topSideIndicatorH, Color.RED
        );

        return new Group(
                rectangle, topLine
        );
    }

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