I have a list of JPanels that I want to display as a \"slideshow\" where one JPanel fades out and the next JPanel in the list fades in. This is the code I am fiddling with:<
So, when dealing with these types of problems, it's generally a better idea to reduce the number of Timer
s you have, as each timer will be posting multiple events to the Event Dispatching Queue (there own tick updates as well as repaint events). All this activity could reduce the performance of the system.
Animation is also the illusion of change over time, to this end, rather the trying to loop from a start point to an end point, you should decide how long you want the animation to run for and calculate the progress of time and update the values accordingly (this more of a "timeline" based animation cycle). This can help reduce the appearance of "lagging"
Normally I'd use the Timing Framework to accomplish this, but you could also have a look at the Trident framework or the Universal Tween Engine which also provide complex animation support for Swing.
This example is very tightly coupled to it's goal. Personally, I'd normally have a abstract concept of an "animatiable" object, which would probably just have the update(float)
method, which would then be expanded to support other objects, but I'll leave that you to nut out.
Another issue is making sure that the component is fully transparent to begin with (setOpaque(false)
), this allows us to fake the translucency of the component during the animation.
Normally, I'd always encourage you to override paintComponent
, but there a few times when this is not adequate, this is one of them. Basically, in order to facilitate the transition from one component to the other, we need to control the alpha level of ALL the child components within the component, this is when overriding paint
will be a better choice.
nb: The code is set to run at around 25fps, but the screen capture software captures at roughly 8fps
import java.awt.AlphaComposite;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class FadeTest {
public static void main(String[] args) {
new FadeTest();
}
public FadeTest() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
BufferedImage img1 = ImageIO.read(new File("C:\\Users\\shane\\Dropbox\\Ponies\\sillydash-small.png"));
BufferedImage img2 = ImageIO.read(new File("C:\\Users\\shane\\Dropbox\\Ponies\\SmallPony.png"));
AlphaPane pane1 = new AlphaPane();
pane1.add(new JLabel(new ImageIcon(img1)));
pane1.setAlpha(1f);
AlphaPane pane2 = new AlphaPane();
pane2.add(new JLabel(new ImageIcon(img2)));
pane2.setAlpha(0f);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 1;
gbc.gridy = 1;
gbc.weightx = 1;
gbc.weighty = 1;
gbc.fill = GridBagConstraints.BOTH;
frame.add(pane1, gbc);
frame.add(pane2, gbc);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
MouseAdapter ma = new MouseAdapter() {
private AnimationController controller;
@Override
public void mouseClicked(MouseEvent e) {
try {
if (controller != null) {
controller.stop();
}
controller = new AnimationController(4000);
boolean fadeIn = pane1.getAlpha() < pane2.getAlpha();
controller.add(controller.new AlphaRange(pane1, fadeIn));
controller.add(controller.new AlphaRange(pane2, !fadeIn));
controller.start();
} catch (InvalidStateException ex) {
ex.printStackTrace();
}
}
};
pane1.addMouseListener(ma);
pane2.addMouseListener(ma);
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
}
public class AnimationController {
private List<AlphaRange> animationRanges;
private Timer timer;
private Long startTime;
private long runTime;
public AnimationController(int runTime) {
this.runTime = runTime;
animationRanges = new ArrayList<>(25);
}
public void add(AlphaRange range) {
animationRanges.add(range);
}
public void start() throws InvalidStateException {
if (timer == null || !timer.isRunning()) {
timer = new Timer(40, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (startTime == null) {
startTime = System.currentTimeMillis();
}
long duration = System.currentTimeMillis() - startTime;
float progress = (float) duration / (float) runTime;
if (progress > 1f) {
progress = 1f;
stop();
}
System.out.println(NumberFormat.getPercentInstance().format(progress));
for (AlphaRange range : animationRanges) {
range.update(progress);
}
}
});
timer.start();
} else {
throw new InvalidStateException("Animation is running");
}
}
public void stop() {
if (timer != null) {
timer.stop();
}
}
public class AlphaRange {
private float from;
private float to;
private AlphaPane alphaPane;
public AlphaRange(AlphaPane alphaPane, boolean fadeIn) {
this.from = alphaPane.getAlpha();
this.to = fadeIn ? 1f : 0f;
this.alphaPane = alphaPane;
}
public float getFrom() {
return from;
}
public float getTo() {
return to;
}
public float getValueBasedOnProgress(float progress) {
float value = 0;
float distance = to - from;
value = (distance * progress);
value += from;
return value;
}
public void update(float progress) {
float alpha = getValueBasedOnProgress(progress);
alphaPane.setAlpha(alpha);
}
}
}
public class InvalidStateException extends Exception {
public InvalidStateException(String message) {
super(message);
}
public InvalidStateException(String message, Throwable cause) {
super(message, cause);
}
}
public class AlphaPane extends JPanel {
private float alpha;
public AlphaPane() {
setOpaque(false);
}
@Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
super.paint(g2d);
g2d.dispose();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// Fake the background
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
}
public void setAlpha(float value) {
if (alpha != value) {
this.alpha = Math.min(1f, Math.max(0, value));
repaint();
}
}
public float getAlpha() {
return alpha;
}
}
}