JavaFX provides a powerful animation framework with the Timeline class being one of the most versatile tools for creating complex animations. This guide covers everything from basic animations to advanced timeline techniques.
Basic Timeline Concepts
Example 1: Simple Property Animation
import javafx.animation.*;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class BasicTimelineDemo extends Application {
@Override
public void start(Stage primaryStage) {
Pane root = new Pane();
Scene scene = new Scene(root, 800, 600);
// Create a rectangle to animate
Rectangle rect = new Rectangle(50, 50, 100, 100);
rect.setFill(Color.BLUE);
root.getChildren().add(rect);
// Create a basic timeline for movement
Timeline timeline = new Timeline();
// KeyFrames define property values at specific times
KeyFrame startFrame = new KeyFrame(Duration.ZERO,
new KeyValue(rect.xProperty(), 50),
new KeyValue(rect.yProperty(), 50),
new KeyValue(rect.fillProperty(), Color.BLUE)
);
KeyFrame middleFrame = new KeyFrame(Duration.seconds(1),
new KeyValue(rect.xProperty(), 300),
new KeyValue(rect.yProperty(), 200),
new KeyValue(rect.fillProperty(), Color.RED)
);
KeyFrame endFrame = new KeyFrame(Duration.seconds(2),
new KeyValue(rect.xProperty(), 600),
new KeyValue(rect.yProperty(), 400),
new KeyValue(rect.fillProperty(), Color.GREEN)
);
timeline.getKeyFrames().addAll(startFrame, middleFrame, endFrame);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.setAutoReverse(true);
// Start the animation
timeline.play();
primaryStage.setTitle("Basic Timeline Animation");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Example 2: Multiple Animations with Interpolators
import javafx.animation.*;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class InterpolatorDemo extends Application {
@Override
public void start(Stage primaryStage) {
Pane root = new Pane();
Scene scene = new Scene(root, 800, 600);
// Create multiple circles with different interpolators
Circle circle1 = createCircle(100, 100, Color.RED);
Circle circle2 = createCircle(100, 200, Color.BLUE);
Circle circle3 = createCircle(100, 300, Color.GREEN);
Circle circle4 = createCircle(100, 400, Color.ORANGE);
root.getChildren().addAll(circle1, circle2, circle3, circle4);
// Different interpolators for different effects
createAnimation(circle1, Interpolator.LINEAR, "Linear");
createAnimation(circle2, Interpolator.EASE_BOTH, "Ease Both");
createAnimation(circle3, Interpolator.EASE_IN, "Ease In");
createAnimation(circle4, Interpolator.EASE_OUT, "Ease Out");
primaryStage.setTitle("Interpolator Comparison");
primaryStage.setScene(scene);
primaryStage.show();
}
private Circle createCircle(double x, double y, Color color) {
Circle circle = new Circle(x, y, 30, color);
circle.setStroke(Color.BLACK);
circle.setStrokeWidth(2);
return circle;
}
private void createAnimation(Circle circle, Interpolator interpolator, String label) {
Timeline timeline = new Timeline();
KeyValue startX = new KeyValue(circle.centerXProperty(), 100, interpolator);
KeyValue endX = new KeyValue(circle.centerXProperty(), 700, interpolator);
KeyFrame start = new KeyFrame(Duration.ZERO, startX);
KeyFrame end = new KeyFrame(Duration.seconds(3), endX);
timeline.getKeyFrames().addAll(start, end);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.setAutoReverse(true);
timeline.play();
}
public static void main(String[] args) {
launch(args);
}
}
Advanced Timeline Techniques
Example 3: Complex Multi-Property Animation
import javafx.animation.*;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.*;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ComplexAnimationDemo extends Application {
@Override
public void start(Stage primaryStage) {
Pane root = new Pane();
Scene scene = new Scene(root, 800, 600);
Rectangle rect = new Rectangle(100, 100, 200, 200);
// Create a gradient fill
Stop[] stops = new Stop[] {
new Stop(0, Color.RED),
new Stop(1, Color.BLUE)
};
LinearGradient gradient = new LinearGradient(0, 0, 1, 1, true, CycleMethod.NO_CYCLE, stops);
rect.setFill(gradient);
rect.setStroke(Color.BLACK);
rect.setStrokeWidth(3);
root.getChildren().add(rect);
// Complex timeline with multiple properties
Timeline timeline = new Timeline();
// Frame 0: Initial state
KeyFrame frame0 = new KeyFrame(Duration.ZERO,
new KeyValue(rect.xProperty(), 100),
new KeyValue(rect.yProperty(), 100),
new KeyValue(rect.widthProperty(), 200),
new KeyValue(rect.heightProperty(), 200),
new KeyValue(rect.rotateProperty(), 0),
new KeyValue(rect.opacityProperty(), 1.0),
new KeyValue(rect.scaleXProperty(), 1.0),
new KeyValue(rect.scaleYProperty(), 1.0)
);
// Frame 1: Scale and rotate
KeyFrame frame1 = new KeyFrame(Duration.seconds(1),
new KeyValue(rect.xProperty(), 200),
new KeyValue(rect.yProperty(), 150),
new KeyValue(rect.widthProperty(), 150),
new KeyValue(rect.heightProperty(), 150),
new KeyValue(rect.rotateProperty(), 45),
new KeyValue(rect.opacityProperty(), 0.8),
new KeyValue(rect.scaleXProperty(), 1.2),
new KeyValue(rect.scaleYProperty(), 1.2)
);
// Frame 2: Different transformation
KeyFrame frame2 = new KeyFrame(Duration.seconds(2),
new KeyValue(rect.xProperty(), 400),
new KeyValue(rect.yProperty(), 300),
new KeyValue(rect.widthProperty(), 100),
new KeyValue(rect.heightProperty(), 100),
new KeyValue(rect.rotateProperty(), 90),
new KeyValue(rect.opacityProperty(), 0.6),
new KeyValue(rect.scaleXProperty(), 0.8),
new KeyValue(rect.scaleYProperty(), 0.8)
);
// Frame 3: Return to similar to original
KeyFrame frame3 = new KeyFrame(Duration.seconds(3),
new KeyValue(rect.xProperty(), 500),
new KeyValue(rect.yProperty(), 400),
new KeyValue(rect.widthProperty(), 200),
new KeyValue(rect.heightProperty(), 200),
new KeyValue(rect.rotateProperty(), 0),
new KeyValue(rect.opacityProperty(), 1.0),
new KeyValue(rect.scaleXProperty(), 1.0),
new KeyValue(rect.scaleYProperty(), 1.0)
);
timeline.getKeyFrames().addAll(frame0, frame1, frame2, frame3);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.setAutoReverse(true);
timeline.play();
primaryStage.setTitle("Complex Multi-Property Animation");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Example 4: Timeline Events and Controls
import javafx.animation.*;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class TimelineControlsDemo extends Application {
private Timeline timeline;
private Label statusLabel;
private Circle ball;
@Override
public void start(Stage primaryStage) {
VBox root = new VBox(20);
root.setPadding(new Insets(20));
Scene scene = new Scene(root, 800, 600);
// Create control panel
HBox controls = createControlPanel();
// Create status label
statusLabel = new Label("Status: Stopped");
// Create animation area
Pane animationPane = new Pane();
animationPane.setPrefSize(760, 400);
animationPane.setStyle("-fx-border-color: black; -fx-border-width: 1px;");
// Create animated ball
ball = new Circle(50, 200, 30, Color.BLUE);
animationPane.getChildren().add(ball);
// Create timeline
createTimeline();
root.getChildren().addAll(controls, statusLabel, animationPane);
primaryStage.setTitle("Timeline Controls Demo");
primaryStage.setScene(scene);
primaryStage.show();
}
private HBox createControlPanel() {
HBox controls = new HBox(10);
Button playBtn = new Button("Play");
Button pauseBtn = new Button("Pause");
Button stopBtn = new Button("Stop");
Button reverseBtn = new Button("Reverse");
Button speedUpBtn = new Button("Speed Up");
Button slowDownBtn = new Button("Slow Down");
playBtn.setOnAction(e -> {
timeline.play();
statusLabel.setText("Status: Playing");
});
pauseBtn.setOnAction(e -> {
timeline.pause();
statusLabel.setText("Status: Paused");
});
stopBtn.setOnAction(e -> {
timeline.stop();
statusLabel.setText("Status: Stopped");
});
reverseBtn.setOnAction(e -> {
timeline.setRate(-timeline.getRate());
statusLabel.setText("Status: " + (timeline.getRate() > 0 ? "Forward" : "Reverse"));
});
speedUpBtn.setOnAction(e -> {
timeline.setRate(timeline.getRate() * 1.5);
statusLabel.setText(String.format("Status: Playing (Rate: %.1fx)", timeline.getRate()));
});
slowDownBtn.setOnAction(e -> {
timeline.setRate(timeline.getRate() / 1.5);
statusLabel.setText(String.format("Status: Playing (Rate: %.1fx)", timeline.getRate()));
});
controls.getChildren().addAll(playBtn, pauseBtn, stopBtn, reverseBtn, speedUpBtn, slowDownBtn);
return controls;
}
private void createTimeline() {
timeline = new Timeline();
// Create a bouncing ball animation
KeyFrame start = new KeyFrame(Duration.ZERO,
new KeyValue(ball.centerXProperty(), 50),
new KeyValue(ball.centerYProperty(), 200),
new KeyValue(ball.fillProperty(), Color.BLUE)
);
KeyFrame middle = new KeyFrame(Duration.seconds(1),
new KeyValue(ball.centerXProperty(), 400),
new KeyValue(ball.centerYProperty(), 50),
new KeyValue(ball.fillProperty(), Color.RED)
);
KeyFrame end = new KeyFrame(Duration.seconds(2),
new KeyValue(ball.centerXProperty(), 750),
new KeyValue(ball.centerYProperty(), 200),
new KeyValue(ball.fillProperty(), Color.GREEN)
);
timeline.getKeyFrames().addAll(start, middle, end);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.setAutoReverse(true);
// Add event handlers
timeline.setOnFinished(e -> {
statusLabel.setText("Status: Finished Cycle");
});
}
public static void main(String[] args) {
launch(args);
}
}
Timeline with Custom Interpolators
Example 5: Custom Interpolation and KeyFrames
import javafx.animation.*;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class CustomInterpolatorDemo extends Application {
@Override
public void start(Stage primaryStage) {
Pane root = new Pane();
Scene scene = new Scene(root, 800, 600);
// Create circles with different custom interpolators
Circle circle1 = createCircle(100, 100, Color.RED);
Circle circle2 = createCircle(100, 200, Color.BLUE);
Circle circle3 = createCircle(100, 300, Color.GREEN);
root.getChildren().addAll(circle1, circle2, circle3);
// Custom interpolators
createBounceAnimation(circle1);
createElasticAnimation(circle2);
createCustomEaseAnimation(circle3);
primaryStage.setTitle("Custom Interpolator Demo");
primaryStage.setScene(scene);
primaryStage.show();
}
private Circle createCircle(double x, double y, Color color) {
Circle circle = new Circle(x, y, 20, color);
circle.setStroke(Color.BLACK);
return circle;
}
private void createBounceAnimation(Circle circle) {
Interpolator bounceInterpolator = new Interpolator() {
@Override
protected double curve(double t) {
// Bounce effect
if (t < 1/2.75) {
return 7.5625 * t * t;
} else if (t < 2/2.75) {
return 7.5625 * (t -= 1.5/2.75) * t + 0.75;
} else if (t < 2.5/2.75) {
return 7.5625 * (t -= 2.25/2.75) * t + 0.9375;
} else {
return 7.5625 * (t -= 2.625/2.75) * t + 0.984375;
}
}
};
Timeline timeline = new Timeline();
KeyValue startX = new KeyValue(circle.centerXProperty(), 100, bounceInterpolator);
KeyValue endX = new KeyValue(circle.centerXProperty(), 700, bounceInterpolator);
timeline.getKeyFrames().addAll(
new KeyFrame(Duration.ZERO, startX),
new KeyFrame(Duration.seconds(2), endX)
);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.setAutoReverse(true);
timeline.play();
}
private void createElasticAnimation(Circle circle) {
Interpolator elasticInterpolator = new Interpolator() {
@Override
protected double curve(double t) {
// Elastic effect
return Math.sin(13 * Math.PI / 2 * t) * Math.pow(2, 10 * (t - 1));
}
};
Timeline timeline = new Timeline();
KeyValue startX = new KeyValue(circle.centerXProperty(), 100, elasticInterpolator);
KeyValue endX = new KeyValue(circle.centerXProperty(), 700, elasticInterpolator);
timeline.getKeyFrames().addAll(
new KeyFrame(Duration.ZERO, startX),
new KeyFrame(Duration.seconds(2), endX)
);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.setAutoReverse(true);
timeline.play();
}
private void createCustomEaseAnimation(Circle circle) {
Interpolator customInterpolator = new Interpolator() {
@Override
protected double curve(double t) {
// Custom ease-in-out with overshoot
if (t < 0.5) {
return 2 * t * t;
} else {
return -2 * t * t + 4 * t - 1;
}
}
};
Timeline timeline = new Timeline();
KeyValue startX = new KeyValue(circle.centerXProperty(), 100, customInterpolator);
KeyValue endX = new KeyValue(circle.centerXProperty(), 700, customInterpolator);
timeline.getKeyFrames().addAll(
new KeyFrame(Duration.ZERO, startX),
new KeyFrame(Duration.seconds(2), endX)
);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.setAutoReverse(true);
timeline.play();
}
public static void main(String[] args) {
launch(args);
}
}
Sequential and Parallel Timelines
Example 6: Complex Timeline Sequencing
import javafx.animation.*;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class SequentialTimelineDemo extends Application {
@Override
public void start(Stage primaryStage) {
Pane root = new Pane();
Scene scene = new Scene(root, 800, 600);
Rectangle rect1 = new Rectangle(50, 50, 80, 80);
rect1.setFill(Color.RED);
Rectangle rect2 = new Rectangle(50, 150, 80, 80);
rect2.setFill(Color.BLUE);
Rectangle rect3 = new Rectangle(50, 250, 80, 80);
rect3.setFill(Color.GREEN);
root.getChildren().addAll(rect1, rect2, rect3);
// Create individual timelines
Timeline timeline1 = createMoveTimeline(rect1, Color.RED, Color.ORANGE);
Timeline timeline2 = createScaleTimeline(rect2, Color.BLUE, Color.PURPLE);
Timeline timeline3 = createRotateTimeline(rect3, Color.GREEN, Color.YELLOW);
// Sequential animation - one after another
SequentialTransition sequential = new SequentialTransition(
timeline1, timeline2, timeline3
);
sequential.setCycleCount(Timeline.INDEFINITE);
// Parallel animation - all at once
ParallelTransition parallel = new ParallelTransition(
createMoveTimeline(rect1, Color.RED, Color.ORANGE),
createScaleTimeline(rect2, Color.BLUE, Color.PURPLE),
createRotateTimeline(rect3, Color.GREEN, Color.YELLOW)
);
parallel.setCycleCount(Timeline.INDEFINITE);
parallel.setAutoReverse(true);
// Start sequential first, then switch to parallel
sequential.play();
// Switch to parallel after sequential completes one cycle
sequential.setOnFinished(e -> {
sequential.stop();
parallel.play();
});
primaryStage.setTitle("Sequential and Parallel Timelines");
primaryStage.setScene(scene);
primaryStage.show();
}
private Timeline createMoveTimeline(Rectangle rect, Color startColor, Color endColor) {
Timeline timeline = new Timeline();
KeyFrame start = new KeyFrame(Duration.ZERO,
new KeyValue(rect.xProperty(), 50),
new KeyValue(rect.fillProperty(), startColor)
);
KeyFrame end = new KeyFrame(Duration.seconds(2),
new KeyValue(rect.xProperty(), 600),
new KeyValue(rect.fillProperty(), endColor)
);
timeline.getKeyFrames().addAll(start, end);
timeline.setAutoReverse(true);
return timeline;
}
private Timeline createScaleTimeline(Rectangle rect, Color startColor, Color endColor) {
Timeline timeline = new Timeline();
KeyFrame start = new KeyFrame(Duration.ZERO,
new KeyValue(rect.scaleXProperty(), 1.0),
new KeyValue(rect.scaleYProperty(), 1.0),
new KeyValue(rect.fillProperty(), startColor)
);
KeyFrame end = new KeyFrame(Duration.seconds(2),
new KeyValue(rect.scaleXProperty(), 1.5),
new KeyValue(rect.scaleYProperty(), 1.5),
new KeyValue(rect.fillProperty(), endColor)
);
timeline.getKeyFrames().addAll(start, end);
timeline.setAutoReverse(true);
return timeline;
}
private Timeline createRotateTimeline(Rectangle rect, Color startColor, Color endColor) {
Timeline timeline = new Timeline();
KeyFrame start = new KeyFrame(Duration.ZERO,
new KeyValue(rect.rotateProperty(), 0),
new KeyValue(rect.fillProperty(), startColor)
);
KeyFrame end = new KeyFrame(Duration.seconds(2),
new KeyValue(rect.rotateProperty(), 360),
new KeyValue(rect.fillProperty(), endColor)
);
timeline.getKeyFrames().addAll(start, end);
timeline.setAutoReverse(true);
return timeline;
}
public static void main(String[] args) {
launch(args);
}
}
Timeline with Dynamic Properties
Example 7: Animating Based on User Input
import javafx.animation.*;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class DynamicTimelineDemo extends Application {
private Timeline timeline;
private Circle ball;
private Slider durationSlider;
private Slider cycleCountSlider;
private CheckBox autoReverseCheck;
private ComboBox<String> interpolatorCombo;
@Override
public void start(Stage primaryStage) {
VBox root = new VBox(20);
root.setPadding(new Insets(20));
Scene scene = new Scene(root, 800, 600);
// Create control panel
GridPane controls = createControlPanel();
// Create animation area
Pane animationPane = new Pane();
animationPane.setPrefSize(760, 400);
animationPane.setStyle("-fx-border-color: black; -fx-border-width: 1px;");
// Create animated ball
ball = new Circle(100, 200, 30, Color.BLUE);
animationPane.getChildren().add(ball);
// Create initial timeline
createTimeline();
root.getChildren().addAll(controls, animationPane);
primaryStage.setTitle("Dynamic Timeline Controls");
primaryStage.setScene(scene);
primaryStage.show();
}
private GridPane createControlPanel() {
GridPane grid = new GridPane();
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(10));
// Duration slider
durationSlider = new Slider(0.5, 5.0, 2.0);
durationSlider.setShowTickLabels(true);
durationSlider.setShowTickMarks(true);
durationSlider.setMajorTickUnit(0.5);
// Cycle count slider
cycleCountSlider = new Slider(1, 10, 3);
cycleCountSlider.setShowTickLabels(true);
cycleCountSlider.setShowTickMarks(true);
cycleCountSlider.setMajorTickUnit(1);
cycleCountSlider.setSnapToTicks(true);
// Auto-reverse checkbox
autoReverseCheck = new CheckBox("Auto Reverse");
autoReverseCheck.setSelected(true);
// Interpolator combo box
interpolatorCombo = new ComboBox<>();
interpolatorCombo.getItems().addAll("LINEAR", "EASE_IN", "EASE_OUT", "EASE_BOTH");
interpolatorCombo.setValue("EASE_BOTH");
// Apply button
Button applyBtn = new Button("Apply Changes");
applyBtn.setOnAction(e -> applyTimelineChanges());
// Layout
grid.add(new Label("Duration (s):"), 0, 0);
grid.add(durationSlider, 1, 0);
grid.add(new Label("Cycle Count:"), 0, 1);
grid.add(cycleCountSlider, 1, 1);
grid.add(autoReverseCheck, 0, 2);
grid.add(new Label("Interpolator:"), 0, 3);
grid.add(interpolatorCombo, 1, 3);
grid.add(applyBtn, 0, 4, 2, 1);
return grid;
}
private void createTimeline() {
timeline = new Timeline();
KeyFrame start = new KeyFrame(Duration.ZERO,
new KeyValue(ball.centerXProperty(), 100),
new KeyValue(ball.centerYProperty(), 200),
new KeyValue(ball.fillProperty(), Color.BLUE)
);
KeyFrame middle = new KeyFrame(Duration.seconds(1),
new KeyValue(ball.centerXProperty(), 400),
new KeyValue(ball.centerYProperty(), 100),
new KeyValue(ball.fillProperty(), Color.RED)
);
KeyFrame end = new KeyFrame(Duration.seconds(2),
new KeyValue(ball.centerXProperty(), 700),
new KeyValue(ball.centerYProperty(), 200),
new KeyValue(ball.fillProperty(), Color.GREEN)
);
timeline.getKeyFrames().addAll(start, middle, end);
applyTimelineChanges();
timeline.play();
}
private void applyTimelineChanges() {
if (timeline != null) {
timeline.stop();
// Update duration
double duration = durationSlider.getValue();
updateKeyFrameDurations(duration);
// Update cycle count
int cycleCount = (int) cycleCountSlider.getValue();
timeline.setCycleCount(cycleCount);
// Update auto reverse
timeline.setAutoReverse(autoReverseCheck.isSelected());
// Update interpolator
String interpolatorType = interpolatorCombo.getValue();
updateInterpolator(interpolatorType);
timeline.play();
}
}
private void updateKeyFrameDurations(double totalDuration) {
// Adjust keyframe times proportionally
double middleTime = totalDuration / 2;
double endTime = totalDuration;
timeline.getKeyFrames().set(1, new KeyFrame(Duration.seconds(middleTime),
new KeyValue(ball.centerXProperty(), 400),
new KeyValue(ball.centerYProperty(), 100),
new KeyValue(ball.fillProperty(), Color.RED)
));
timeline.getKeyFrames().set(2, new KeyFrame(Duration.seconds(endTime),
new KeyValue(ball.centerXProperty(), 700),
new KeyValue(ball.centerYProperty(), 200),
new KeyValue(ball.fillProperty(), Color.GREEN)
));
}
private void updateInterpolator(String interpolatorType) {
Interpolator interpolator = switch (interpolatorType) {
case "LINEAR" -> Interpolator.LINEAR;
case "EASE_IN" -> Interpolator.EASE_IN;
case "EASE_OUT" -> Interpolator.EASE_OUT;
case "EASE_BOTH" -> Interpolator.EASE_BOTH;
default -> Interpolator.LINEAR;
};
// Update all key values with new interpolator
for (KeyFrame frame : timeline.getKeyFrames()) {
for (KeyValue keyValue : frame.getValues()) {
// Note: KeyValue interpolator is final, so we'd need to recreate KeyFrames
// This is a simplified demonstration
}
}
}
public static void main(String[] args) {
launch(args);
}
}
Performance Optimization Tips
Example 8: Efficient Animation Techniques
import javafx.animation.*;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class EfficientAnimationDemo extends Application {
private static final int NUM_OBJECTS = 50;
@Override
public void start(Stage primaryStage) {
Pane root = new Pane();
Scene scene = new Scene(root, 800, 600);
// Create multiple animated objects
for (int i = 0; i < NUM_OBJECTS; i++) {
Rectangle rect = createAnimatedRectangle(i);
root.getChildren().add(rect);
}
primaryStage.setTitle("Efficient Multiple Animations");
primaryStage.setScene(scene);
primaryStage.show();
}
private Rectangle createAnimatedRectangle(int index) {
Rectangle rect = new Rectangle(30, 30);
rect.setFill(Color.hsb((index * 360.0 / NUM_OBJECTS), 0.8, 0.8));
rect.setStroke(Color.BLACK);
// Position based on index
double startX = 50 + (index % 10) * 70;
double startY = 50 + (index / 10) * 70;
rect.setX(startX);
rect.setY(startY);
// Create efficient animation
createEfficientAnimation(rect, startX, startY);
return rect;
}
private void createEfficientAnimation(Rectangle rect, double startX, double startY) {
Timeline timeline = new Timeline();
// Use fewer keyframes for better performance
KeyFrame start = new KeyFrame(Duration.ZERO,
new KeyValue(rect.xProperty(), startX),
new KeyValue(rect.yProperty(), startY),
new KeyValue(rect.rotateProperty(), 0)
);
KeyFrame middle = new KeyFrame(Duration.seconds(1),
new KeyValue(rect.xProperty(), startX + 200),
new KeyValue(rect.yProperty(), startY + 100),
new KeyValue(rect.rotateProperty(), 180)
);
KeyFrame end = new KeyFrame(Duration.seconds(2),
new KeyValue(rect.xProperty(), startX),
new KeyValue(rect.yProperty(), startY),
new KeyValue(rect.rotateProperty(), 360)
);
timeline.getKeyFrames().addAll(start, middle, end);
// Stagger start times for better performance
timeline.setDelay(Duration.millis(Math.random() * 1000));
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}
public static void main(String[] args) {
launch(args);
}
}
Key Best Practices
- Use Appropriate Interpolators: Choose interpolators that match your animation needs
- Optimize KeyFrames: Use fewer keyframes when possible for better performance
- Manage Timeline Lifecycle: Always stop timelines when they're no longer needed
- Use Sequential/Parallel: Combine timelines for complex animations
- Consider Performance: Be mindful of animating many properties simultaneously
- Handle Exceptions: Implement proper error handling for animation failures
Conclusion
JavaFX Timeline provides a powerful and flexible framework for creating complex animations. Key takeaways:
- KeyFrames and KeyValues define property changes over time
- Interpolators control the pacing of animations
- Timeline controls allow precise animation management
- Sequential and parallel transitions enable complex animation sequences
- Performance optimization is crucial for smooth animations
With these techniques, you can create everything from simple property animations to complex, interactive animation sequences in your JavaFX applications.