Theory...
Okay, so based on your requirement, my first suggest is to focus on fading a single image in and out. If you can understand how to do that, then fading three images in out (one after the other) is much simpler.
Animation is the illusion of change over time. So, you first need someway to change the state of the alpha
over a period of time. Because Swing is both single threaded and not thread safe, this leaves you with one basic choice, a Swing Timer
.
This generates updates at regular intervals which are triggers within the context of the Event Dispatching Thread, making it safe to use with Swing and to update the UI from within.
Because of the variances in hardware (and OS's), I'd avoid a fixed rate fade (that is, where you apply a fixed delta
to the alpha
and repeat until your reach your target
). This approach can produce undesirable results on different systems.
From my experience, a time based solution generally produces more consistent results. A time based approach states that the animation will run over a specified time period, on each tick of the Timer
, we calculate the amount progression and apply that to our state (we know we need to go from 0-1 to fade an image in, so it's easy to calculate the state based on the progression)
Base implementation...
That all sounds find in practice, but how do we actually apply it. Because the solution isn't always simple, I'd focus on making a dedicated class to perform the operation.
public class FadePane extends JPanel {
private BufferedImage source;
private Timer timer;
private float alpha = 1.0f;
private int duration = 2000; // 2 seconds
private Long startTime;
private boolean fadeOut = false;
private FadeListener fadeListener;
public FadePane(BufferedImage source) {
this.source = source;
timer = new Timer(5, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (startTime == null) {
startTime = System.currentTimeMillis();
fadeStarted();
}
long diff = System.currentTimeMillis() - startTime;
alpha = (float)diff / (float)duration;
if (alpha > 1.0) {
timer.stop();
alpha = 1.0f;
fadeCompleted();
}
if (fadeOut) {
alpha = 1.0f - alpha;
}
repaint();
}
});
}
public void setFadeListener(FadeListener listener) {
fadeListener = listener;
}
public boolean isFadeOut() {
return fadeOut;
}
protected void fadeStarted() {
if (fadeListener != null) {
fadeListener.fadeStarted(this);
}
}
protected void fadeCompleted() {
if (fadeListener != null) {
fadeListener.fadeCompleted(this);
}
}
public void setSource(BufferedImage img) {
source = img;
}
public void reset() {
timer.stop();
alpha = 0;
startTime = null;
}
public void fadeIn() {
reset();
fadeOut = false;
timer.start();
}
public void fadeOut() {
reset();
fadeOut = true;
timer.start();
}
@Override
public Dimension getPreferredSize() {
return source == null ? new Dimension(200, 200) : new Dimension(source.getWidth(), source.getHeight());
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
int x = (getWidth() - source.getWidth()) / 2;
int y = (getHeight() - source.getHeight()) / 2;
g2d.drawImage(source, x, y, this);
g2d.dispose();
}
}
The FadePane
takes a source
image and, depending on which method you call, will fade it in or out over a period of 2 seconds.
You can re-use the FadePane
by simply changing the source
image via the setSource
method and fading the new image in or out, depending on the desired result you're after.
The FadePane
also provides an observer, which is notified when the fade operation is started and completed...
public interface FadeListener {
public void fadeStarted(FadePane pane);
public void fadeCompleted(FadePane pane);
}
This could be used to change the state of the UI (disable/enable functionality) as well as switch the image when you want to
Runnable Example...
This example simple allows a user to fade the same image in and out, but it wouldn't be hard to generate a List
of images which which are changed through the FadeListener

import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
BufferedImage source = ImageIO.read(...);
FadePane fadePane = new FadePane(source);
JButton btn = new JButton("Fade");
btn.addActionListener(new ActionListener() {
private boolean fadeOut = true;
@Override
public void actionPerformed(ActionEvent e) {
if (fadeOut) {
fadePane.fadeOut();
} else {
fadePane.fadeIn();
}
fadeOut = !fadeOut;
}
});
fadePane.setFadeListener(new FadeListener() {
@Override
public void fadeStarted(FadePane pane) {
btn.setEnabled(false);
}
@Override
public void fadeCompleted(FadePane pane) {
// Set next image and start the
// fade process again
btn.setEnabled(true);
}
});
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(fadePane);
frame.add(btn, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
}
public interface FadeListener {
public void fadeStarted(FadePane pane);
public void fadeCompleted(FadePane pane);
}
public class FadePane extends JPanel {
private BufferedImage source;
private Timer timer;
private float alpha = 1.0f;
private int duration = 2000; // 2 seconds
private Long startTime;
private boolean fadeOut = false;
private FadeListener fadeListener;
public FadePane(BufferedImage source) {
this.source = source;
timer = new Timer(5, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (startTime == null) {
startTime = System.currentTimeMillis();
fadeStarted();
}
long diff = System.currentTimeMillis() - startTime;
alpha = (float)diff / (float)duration;
if (alpha > 1.0) {
timer.stop();
alpha = 1.0f;
fadeCompleted();
}
if (fadeOut) {
alpha = 1.0f - alpha;
}
repaint();
}
});
}
public void setFadeListener(FadeListener listener) {
fadeListener = listener;
}
protected void fadeStarted() {
if (fadeListener != null) {
fadeListener.fadeStarted(this);
}
}
protected void fadeCompleted() {
if (fadeListener != null) {
fadeListener.fadeCompleted(this);
}
}
public void setSource(BufferedImage img) {
source = img;
}
public void reset() {
timer.stop();
alpha = 0;
startTime = null;
}
public boolean isFadeOut() {
return fadeOut;
}
public void fadeIn() {
reset();
fadeOut = false;
timer.start();
}
public void fadeOut() {
reset();
fadeOut = true;
timer.start();
}
@Override
public Dimension getPreferredSize() {
return source == null ? new Dimension(200, 200) : new Dimension(source.getWidth(), source.getHeight());
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
int x = (getWidth() - source.getWidth()) / 2;
int y = (getHeight() - source.getHeight()) / 2;
g2d.drawImage(source, x, y, this);
g2d.dispose();
}
}
}