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.TranslateTransitions 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.
