14.03.2013
JavaFX - Animation of Circles with a Timeline
The following article describes how to animate objects in JavaFX. In the example, a Scene
is created which contains some objects (javafx.scene.shape.Circle
-Objects) which are flying around an imaginary middlepoint. The animation is realized with a JavaFX javafx.animation.Timeline
and javafx.animation.TranslateTransition
s to move the objects from one point to the next position around the orbit. The objects are styled by a CSS file which defines a radial-gradient
to change the style (color and texture) of the Circle
.
The following screenshot shows the finished JavaFX application.
Here is the source code of the application:
package org.hameister.rotating; import java.util.ArrayList; import java.util.List; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.animation.TranslateTransition; import javafx.animation.TranslateTransitionBuilder; import javafx.application.Application; import javafx.event.Event; import javafx.event.EventHandler; import javafx.scene.Scene; import javafx.scene.layout.Pane; import javafx.scene.shape.Circle; import javafx.scene.shape.CircleBuilder; import javafx.stage.Stage; import javafx.util.Duration; import static javafx.util.Duration.millis; public class RotatingObjects extends Application { // Number of steps for a circle/ball during one orbit. private static final int MOVING_POINTS = 60; // Ball speed private static final int STEP_DURATION_IN_MILLISECONDS = 100; // Scene Size private static final int SCENE_SIZE = 800; @Override public void start(Stage primaryStage) { final int midPoint = SCENE_SIZE / 2; // Orbits of the balls (Radius) final int[] ballOrbits = {300, 200, 100, 50 ,20, 1}; // Size of the circles final int[] ballRadius = {30, 20, 10, 5, 2 ,1 }; final List<Circle> balls = new ArrayList<>(); CircleBuilder builder = CircleBuilder.create().centerX(0).centerY(0).styleClass("ball-style"); for (int i = 0; i < ballOrbits.length; i++) { balls.add(builder.radius(ballRadius[i]).build()); } final Timeline timeline = new Timeline(new KeyFrame(Duration.ZERO, new EventHandler() { int movingStep = 0; @Override public void handle(Event event) { movingStep++; double angleAlpha = movingStep * ( Math.PI / 30 ); for (int i = 0; i < balls.size(); i++) { // p(x) = x(0) + r * sin(a) // p(y) = y(y) - r * cos(a) moveBall(balls.get(i), midPoint + ballOrbits[i] * Math.sin(angleAlpha), midPoint - ballOrbits[i] * Math.cos(angleAlpha)); } // Reset after one orbit. if (movingStep == MOVING_POINTS) { movingStep = 0; } } }), new KeyFrame(Duration.millis(STEP_DURATION_IN_MILLISECONDS))); timeline.setCycleCount(Timeline.INDEFINITE); Pane root = new Pane(); root.getChildren().addAll(balls); Scene scene = new Scene(root, SCENE_SIZE, SCENE_SIZE); primaryStage.setTitle("Rotating Balls"); primaryStage.setScene(scene); primaryStage.getScene().getStylesheets().add("rotatingObjects"); primaryStage.show(); timeline.play(); } private void moveBall(Circle ball, double x, double y) { TranslateTransition move = TranslateTransitionBuilder.create() .node(ball) .toX(x) .toY(y) .duration(millis(STEP_DURATION_IN_MILLISECONDS)) .build(); move.playFromStart(); } public static void main(String[] args) { launch(args); } }
This example application has six Circle
objects which are created with a CircleBuilder
. Every Circle
has an orbit (ballOrbits
) to define the distance from the middlepoint and a radius (ballRadius
) to define the size of the rotating circle.
The method handle(Event event)
in the Timeline
is invoked every 100 milliseconds (STEP_DURATION_IN_MILLISECONDS
) and moves all Circle
objects one step further with a TranslateTranssition
in the method moveBall
.
Every Circle
object is moved 60 times (MOVING_POINTS
) for every orbit. This means that the angle between the moves is 6°. (60*6°=360° for one orbit).
A new position of an object is calculated with the formular: p(x) = x(0) + radius * sin(a)
and p(y) = y(0) - radius * cos(a)
.
Attention should be paid to the calculation of the angle a (angleAlpha
). You have to convert the angle from degrees to radians. The application uses this formular to calculate the angle angleAlpha
in Line 52
to move an object by 6°.
a(deg) a(rad) ------ = ------ 360° 2*PI a(deg) * 2 * PI a(rad) = ---------------- 360° With a(deg)=6° => 6° * 2 * PI PI ----------- = ---- 360° 30
Here is the CSS file for the styling of the Circle
objects. In the example above only the ball-style
is used. If you want to change the color of the object, try one of the other styles.
.root { -fx-background-color: radial-gradient(center 50% 50%, radius 60%, reflect, #ADFF2F, black ); } .ball-style { -fx-fill: radial-gradient(center 50% 18%, radius 50%, reflect, red, #CD2626 80% ); } .ball-style-blue { -fx-fill: radial-gradient(center 50% 18%, radius 50%, reflect, blue, #0000CD 80% ); } .ball-style-orange { -fx-fill: radial-gradient(center 50% 18%, radius 50%, reflect, orange, #CD8500 80% ); } .ball-style-yellow { -fx-fill: radial-gradient(center 50% 18%, radius 50%, reflect, yellow, #CDCD00 80% ); }
In Line 76
of the Application the CSS file is loaded. The CircleBuilder
in Line 40
uses the style ball-style
from the CSS file to set a radial-gradient
for the Circle
.