google-site-verification: google61fe8ba583a51912.html
Creating Fluid Experiences: Transition and Parallel Animation in Java

Animation brings applications to life, providing visual feedback, guiding user attention, and creating polished user experiences. In Java, animations can range from simple property transitions to complex parallel sequences. This article explores the fundamental concepts and practical implementation of transitions and parallel animations across Java's UI toolkits.


Part 1: Animation in Swing with Java2D

While Swing doesn't have built-in animation support like JavaFX, we can create smooth animations using timers and property interpolation.

Basic Animation Concepts

Key Animation Principles:

  • Interpolation: Calculating intermediate values between start and end states
  • Frame Rate: Controlling update frequency (typically 30-60 FPS)
  • Easing: Non-linear animation for more natural movement

Simple Transition with Swing Timer

import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class BasicSwingAnimation extends JPanel { private int circleX = 50; private int circleY = 100; private int targetX = 300; private final int circleSize = 40; private Timer timer; private float progress = 0.0f; private final int animationDuration = 1000; // milliseconds private long startTime; public BasicSwingAnimation() { setPreferredSize(new Dimension(400, 200)); // Create animation timer (60 FPS) timer = new Timer(16, new ActionListener() { // ~60 FPS @Override public void actionPerformed(ActionEvent e) { updateAnimation(); repaint(); } }); // Start button JButton startButton = new JButton("Start Animation"); startButton.addActionListener(e -> startAnimation()); setLayout(new BorderLayout()); add(this, BorderLayout.CENTER); add(startButton, BorderLayout.SOUTH); } private void startAnimation() { progress = 0.0f; startTime = System.currentTimeMillis(); circleX = 50; // Reset position timer.start(); } private void updateAnimation() { long currentTime = System.currentTimeMillis(); long elapsed = currentTime - startTime; progress = Math.min(1.0f, (float) elapsed / animationDuration); // Linear interpolation circleX = (int) (50 + (targetX - 50) * progress); // Add easing (cubic ease-out) float easedProgress = 1 - (1 - progress) * (1 - progress) * (1 - progress); circleY = (int) (100 + 50 * Math.sin(easedProgress * Math.PI)); if (progress >= 1.0f) { timer.stop(); } } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; // Enable anti-aliasing for smoother graphics g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Draw background g2d.setColor(Color.LIGHT_GRAY); g2d.fillRect(0, 0, getWidth(), getHeight()); // Draw animated circle g2d.setColor(Color.BLUE); g2d.fillOval(circleX, circleY, circleSize, circleSize); // Draw progress text g2d.setColor(Color.BLACK); g2d.drawString(String.format("Progress: %.1f%%", progress * 100), 10, 20); } public static void main(String[] args) { SwingUtilities.invokeLater(() -> { JFrame frame = new JFrame("Swing Animation Demo"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(new BasicSwingAnimation()); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); }); } }

Advanced Transition with Multiple Properties

import javax.swing.*; import java.awt.*; import java.awt.geom.Ellipse2D; public class MultiPropertyAnimation extends JPanel { private float progress = 0.0f; private Timer timer; private long startTime; private final int duration = 2000; // Animated properties private Color color = Color.RED; private float scale = 1.0f; private float rotation = 0.0f; private float opacity = 1.0f; public MultiPropertyAnimation() { setPreferredSize(new Dimension(500, 300)); timer = new Timer(16, e -> { updateAnimation(); repaint(); }); JButton startBtn = new JButton("Start Complex Animation"); startBtn.addActionListener(e -> startAnimation()); setLayout(new BorderLayout()); add(this, BorderLayout.CENTER); add(startBtn, BorderLayout.SOUTH); } private void startAnimation() { progress = 0.0f; startTime = System.currentTimeMillis(); timer.start(); } private void updateAnimation() { long currentTime = System.currentTimeMillis(); long elapsed = currentTime - startTime; progress = Math.min(1.0f, (float) elapsed / duration); // Apply different easing to different properties float easeOut = 1 - (1 - progress) * (1 - progress); float easeIn = progress * progress; float elastic = (float) (1 + Math.sin(progress * Math.PI * 5) * (1 - progress) * 0.3); // Animate multiple properties scale = 0.5f + easeOut * 1.5f; // Scale from 0.5x to 2x rotation = easeIn * 360; // Rotate 360 degrees opacity = 0.3f + easeOut * 0.7f; // Fade in // Color transition from red to blue to green if (progress < 0.5f) { float colorProgress = progress * 2; color = interpolateColor(Color.RED, Color.BLUE, colorProgress); } else { float colorProgress = (progress - 0.5f) * 2; color = interpolateColor(Color.BLUE, Color.GREEN, colorProgress); } if (progress >= 1.0f) { timer.stop(); } } private Color interpolateColor(Color start, Color end, float progress) { int r = (int) (start.getRed() + (end.getRed() - start.getRed()) * progress); int g = (int) (start.getGreen() + (end.getGreen() - start.getGreen()) * progress); int b = (int) (start.getBlue() + (end.getBlue() - start.getBlue()) * progress); return new Color(r, g, b); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Draw background g2d.setColor(Color.WHITE); g2d.fillRect(0, 0, getWidth(), getHeight()); // Set composite for opacity g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity)); // Save original transform AffineTransform originalTransform = g2d.getTransform(); // Apply transformations at center int centerX = getWidth() / 2; int centerY = getHeight() / 2; g2d.translate(centerX, centerY); g2d.scale(scale, scale); g2d.rotate(Math.toRadians(rotation)); // Draw shape Shape shape = new Ellipse2D.Float(-50, -50, 100, 100); g2d.setColor(color); g2d.fill(shape); // Restore transform g2d.setTransform(originalTransform); g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f)); // Draw info g2d.setColor(Color.BLACK); g2d.drawString(String.format("Scale: %.2f, Rotation: %.0f°", scale, rotation), 10, 20); } public static void main(String[] args) { SwingUtilities.invokeLater(() -> { JFrame frame = new JFrame("Multi-Property Animation"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(new MultiPropertyAnimation()); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); }); } }

Part 2: Parallel Animations in JavaFX

JavaFX provides a comprehensive animation framework that makes parallel and sequential animations much easier to implement.

Basic JavaFX Transition

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 JavaFXBasicTransition extends Application { @Override public void start(Stage primaryStage) { Pane root = new Pane(); root.setPrefSize(400, 200); Circle circle = new Circle(50, 100, 20, Color.BLUE); root.getChildren().add(circle); // Create basic translation transition TranslateTransition transition = new TranslateTransition(Duration.seconds(2), circle); transition.setFromX(0); transition.setToX(300); transition.setCycleCount(1); transition.setAutoReverse(false); // Add easing (bounce effect at the end) transition.setInterpolator(Interpolator.EASE_BOTH); Scene scene = new Scene(root); primaryStage.setTitle("JavaFX Basic Transition"); primaryStage.setScene(scene); primaryStage.show(); // Start animation after short delay PauseTransition delay = new PauseTransition(Duration.seconds(1)); delay.setOnFinished(e -> transition.play()); delay.play(); } }

Parallel Animations in JavaFX

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 JavaFXParallelAnimation extends Application { @Override public void start(Stage primaryStage) { Pane root = new Pane(); root.setPrefSize(600, 400); Rectangle rect = new Rectangle(50, 50, 60, 40); rect.setFill(Color.CRIMSON); root.getChildren().add(rect); // Create multiple parallel transitions TranslateTransition moveTransition = new TranslateTransition(Duration.seconds(3), rect); moveTransition.setToX(400); moveTransition.setToY(200); RotateTransition rotateTransition = new RotateTransition(Duration.seconds(3), rect); rotateTransition.setByAngle(720); // Two full rotations ScaleTransition scaleTransition = new ScaleTransition(Duration.seconds(3), rect); scaleTransition.setToX(2.0); scaleTransition.setToY(2.0); FillTransition colorTransition = new FillTransition(Duration.seconds(3), rect); colorTransition.setFromValue(Color.CRIMSON); colorTransition.setToValue(Color.DARKBLUE); // Create parallel animation group ParallelTransition parallelTransition = new ParallelTransition(); parallelTransition.getChildren().addAll( moveTransition, rotateTransition, scaleTransition, colorTransition ); parallelTransition.setCycleCount(2); parallelTransition.setAutoReverse(true); Scene scene = new Scene(root); primaryStage.setTitle("JavaFX Parallel Animation"); primaryStage.setScene(scene); primaryStage.show(); parallelTransition.play(); } }

Complex Sequential and Parallel 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.Circle; import javafx.stage.Stage; import javafx.util.Duration; public class ComplexAnimationSequence extends Application { @Override public void start(Stage primaryStage) { Pane root = new Pane(); root.setPrefSize(600, 400); Circle circle1 = new Circle(50, 100, 25, Color.RED); Circle circle2 = new Circle(50, 200, 25, Color.BLUE); Circle circle3 = new Circle(50, 300, 25, Color.GREEN); root.getChildren().addAll(circle1, circle2, circle3); // Animation for circle 1: Move right while fading TranslateTransition move1 = new TranslateTransition(Duration.seconds(2), circle1); move1.setToX(400); FadeTransition fade1 = new FadeTransition(Duration.seconds(2), circle1); fade1.setToValue(0.3); ParallelTransition anim1 = new ParallelTransition(move1, fade1); // Animation for circle 2: Move with bounce and color change TranslateTransition move2 = new TranslateTransition(Duration.seconds(1.5), circle2); move2.setToX(400); move2.setInterpolator(Interpolator.EASE_OUT); FillTransition color2 = new FillTransition(Duration.seconds(1.5), circle2); color2.setToValue(Color.PURPLE); ParallelTransition anim2 = new ParallelTransition(move2, color2); // Animation for circle 3: Complex path with rotation PathTransition path3 = new PathTransition(); path3.setDuration(Duration.seconds(2)); path3.setNode(circle3); path3.setPath(new javafx.scene.shape.Line(50, 300, 400, 100)); RotateTransition rotate3 = new RotateTransition(Duration.seconds(2), circle3); rotate3.setByAngle(360); ParallelTransition anim3 = new ParallelTransition(path3, rotate3); // Create sequential timeline with delays SequentialTransition sequence = new SequentialTransition( anim1, new PauseTransition(Duration.seconds(0.5)), anim2, new PauseTransition(Duration.seconds(0.5)), anim3 ); sequence.setCycleCount(Animation.INDEFINITE); sequence.setAutoReverse(true); Scene scene = new Scene(root); primaryStage.setTitle("Complex Animation Sequence"); primaryStage.setScene(scene); primaryStage.show(); sequence.play(); } }

Custom Timeline Animation in JavaFX

For maximum control, use the Timeline class:

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 CustomTimelineAnimation extends Application { @Override public void start(Stage primaryStage) { Pane root = new Pane(); root.setPrefSize(400, 400); Circle circle = new Circle(200, 200, 50, Color.ORANGE); root.getChildren().add(circle); // Create custom timeline with keyframes Timeline timeline = new Timeline( new KeyFrame(Duration.ZERO, new KeyValue(circle.centerXProperty(), 100), new KeyValue(circle.centerYProperty(), 100), new KeyValue(circle.fillProperty(), Color.ORANGE) ), new KeyFrame(Duration.seconds(1), new KeyValue(circle.centerXProperty(), 300), new KeyValue(circle.centerYProperty(), 100), new KeyValue(circle.fillProperty(), Color.RED) ), new KeyFrame(Duration.seconds(2), new KeyValue(circle.centerXProperty(), 300), new KeyValue(circle.centerYProperty(), 300), new KeyValue(circle.fillProperty(), Color.PURPLE) ), new KeyFrame(Duration.seconds(3), new KeyValue(circle.centerXProperty(), 100), new KeyValue(circle.centerYProperty(), 300), new KeyValue(circle.fillProperty(), Color.BLUE) ), new KeyFrame(Duration.seconds(4), new KeyValue(circle.centerXProperty(), 100), new KeyValue(circle.centerYProperty(), 100), new KeyValue(circle.fillProperty(), Color.ORANGE) ) ); timeline.setCycleCount(Timeline.INDEFINITE); timeline.setAutoReverse(false); Scene scene = new Scene(root); primaryStage.setTitle("Custom Timeline Animation"); primaryStage.setScene(scene); primaryStage.show(); timeline.play(); } }

Animation Best Practices

Performance Optimization

  1. Swing:
  • Use repaint() only on visible regions when possible
  • Consider double buffering for complex animations
  • Use VolatileImage for frequently changing graphics
  1. JavaFX:
  • Leverage hardware acceleration
  • Use Cache and CacheHint for static parts of animated nodes
  • Avoid frequent layout passes during animations

Smooth Animation Tips

// Frame rate management in Swing public class SmoothAnimationTimer { private static final int TARGET_FPS = 60; private static final long OPTIMAL_TIME = 1000000000 / TARGET_FPS; public void startAnimation() { new Thread(() -> { long lastLoopTime = System.nanoTime(); while (true) { long now = System.nanoTime(); long updateLength = now - lastLoopTime; lastLoopTime = now; double delta = updateLength / ((double)OPTIMAL_TIME); // Update game state updateAnimation(delta); // Repaint on EDT SwingUtilities.invokeLater(() -> repaint()); try { long sleepTime = (lastLoopTime - System.nanoTime() + OPTIMAL_TIME) / 1000000; if (sleepTime > 0) { Thread.sleep(sleepTime); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } }).start(); } }

Easing Functions

public class EasingFunctions { // Linear interpolation public static float linear(float t) { return t; } // Quadratic ease-in public static float easeInQuad(float t) { return t * t; } // Quadratic ease-out public static float easeOutQuad(float t) { return 1 - (1 - t) * (1 - t); } // Cubic ease-in-out public static float easeInOutCubic(float t) { return t < 0.5 ? 4 * t * t * t : 1 - (float)Math.pow(-2 * t + 2, 3) / 2; } // Elastic ease-out public static float easeOutElastic(float t) { float c4 = (float)(2 * Math.PI) / 3; return t == 0 ? 0 : t == 1 ? 1 : (float)Math.pow(2, -10 * t) * (float)Math.sin((t * 10 - 0.75) * c4) + 1; } // Bounce ease-out public static float easeOutBounce(float t) { float n1 = 7.5625f; float d1 = 2.75f; if (t < 1 / d1) { return n1 * t * t; } else if (t < 2 / d1) { return n1 * (t -= 1.5f / d1) * t + 0.75f; } else if (t < 2.5 / d1) { return n1 * (t -= 2.25f / d1) * t + 0.9375f; } else { return n1 * (t -= 2.625f / d1) * t + 0.984375f; } } }

Conclusion

Transition and parallel animations in Java can be implemented through different approaches:

  1. Swing/Java2D: Provides full control through manual animation loops with Timer and property interpolation
  2. JavaFX: Offers built-in transition classes and timeline-based animation with better performance

Key takeaways:

  • Use easing functions for natural-looking animations
  • Parallel animations create complex effects by running multiple transitions simultaneously
  • JavaFX is generally preferred for animation-heavy applications due to its hardware acceleration
  • Performance optimization is crucial for smooth animations, especially in Swing
  • Consider user experience - animations should enhance, not hinder, usability

Whether building traditional desktop applications with Swing or modern UIs with JavaFX, mastering animation techniques enables creating engaging, professional-quality Java applications that provide excellent user experiences.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper