Simple Frame Transitions without Complex Code
by
, 03-22-2012 at 07:18 PM (13543 Views)
I've spent the past few days looking up Java window transitions that work with Swing components & containers (JFrame, JPanel, etc.).
Yes, there are packages such as JavaFX which includes a Timeline & Transition framework. I also came across these:
Trident:
Project Kenai — We're More Than Just a Forge
FRC:
Filthy Rich Clients (find the Animation jar (org.jdesktop.animation))
However, I've been impatient lately and didn't feel like cluttering my code with framework-code I barely understood. I wanted something fast and I didn't care if it was dirty, so long as I didn't have to set up and learn another package.
Besides, those packages were written to include many other features which I didn't want to know about, and I like doing things my way sometimes.
In the end, I arrived with this solution:
1. Create a CardLayout with the many JPanels I wanted to switch between.
2. Get ready to show the next Card
2.i) capture the graphics of the JPanel i'm about to hide
2.ii) capture the graphics of the JPanel i'm about to show
2.iii) merge the two images into one
2.iv) add the image to a new intermediary Card using JScrollPane/something scrollable
2.v) temporarily add the intermediary Card to the CardLayout
2.v.a) show it
2.v.b) scroll it to the end
2.v.c) remove it
2.vi) finally, show the next Card
Here's how I did it:
I started off with the ImageHelper class to perform some functions:
Java Code:/** * * @author Ozzy */ public class ImageHelper { // ImageHelper to help me capture images of Swing Components and // merge them into a single image. // Capture a Swing Component and return as a BufferedImage public static BufferedImage captureComponent(Component component) { BufferedImage image = new BufferedImage(component.getWidth(), component.getHeight(), BufferedImage.TYPE_INT_RGB); component.paint(image.getGraphics()); return image; } // Some constant to define how I'd like to merge my images public static final int SIDE_BY_SIDE = 0; // Helper method to combine two images, in the format I specify public static BufferedImage combineImages(BufferedImage img1, BufferedImage img2, int renderHint) { switch (renderHint) { default: case SIDE_BY_SIDE: // Create a new image that is the width of img1+img2 // Take the height of the taller image // Paint the two images side-by-side BufferedImage combined = new BufferedImage(img1.getWidth()+img2.getWidth(), Math.max(img1.getHeight(), img2.getHeight()), BufferedImage.TYPE_INT_RGB); Graphics g = combined.getGraphics(); g.drawImage(img1, 0, 0, null); g.drawImage(img2, img1.getWidth(), 0, null); return combined; } } }
Now I wanted some way to Scroll my Image left-to-right or right-to-left depending on my input parameter. It would be a custom TimerTask that would sit in my helper class to do the animation. I called it the Framinator class (Frame+Animator).
Java Code:/** * * @author Ozzy */ public class Framinator { // The rest will be revealed below ... // Some constants which define the transition direction public static final int TRANSITION_LEFT = 0; public static final int TRANSITION_RIGHT = 1; private static class ScrollTransition extends TimerTask { // TimerTask to scroll a JScrollPane in a given direction // and then call a callback TimerTask. // Usage: // JScrollPane scrollPane = ... // ... // Timer t = new Timer(); // TimerTask callback = new TimerTask() { // public void run() { // ... // } // } // t.scheduleAtFixedRate(t, callback, scrollPane, 20, TRANSITION_LEFT) private Timer timer; private TimerTask callback; private JScrollPane scroller; private int step; private int direction; private int $offset = 0; private int $max = 0; private boolean $runOnce = true; ScrollTransition(Timer timer, TimerTask callback, JScrollPane scroller, int step, int direction) { this.timer = timer; this.callback = callback; this.scroller = scroller; this.step = step; this.direction = direction; $max = scroller.getHorizontalScrollBar().getMaximum(); } @Override public void run() { switch(direction) { default: case TRANSITION_LEFT: // Going Right-to-Left // Start at the end, scroll backwards if ($runOnce) { scroller.getHorizontalScrollBar().setValue($max); $offset = $max; $runOnce = false; } // If there is space for a step, scroll left one step // Else scroll right to the left if ($offset > (0+step)) { $offset -= step; scroller.getHorizontalScrollBar().setValue($offset); } else { scroller.getHorizontalScrollBar().setValue(0); // Cancel the current ScrollTransition cancel(); timer.schedule(callback, 0); } break; case TRANSITION_RIGHT: // Going Left-to-Right // If there is space for a step, scroll right one step // Else scroll right to the end if ($offset < ($max-step)) { $offset += step; scroller.getHorizontalScrollBar().setValue($offset); } else { scroller.getHorizontalScrollBar().setValue($max); // Cancel the current ScrollTransition cancel(); timer.schedule(callback, 0); } break; } } } }
Finally I just needed a helper method in my Framinator class to actually perform the Tasks.
Here's how it went:
Java Code:/** * * @author Ozzy */ class Framinator { public static void transitionComponents(final Container parent, Component from, final Component to, int direction) { // First check we have a CardLayout, if not: return final CardLayout cl; if (parent.getLayout() instanceof CardLayout) { cl = (CardLayout) parent.getLayout(); } else return; // Create the combined image depending on the direction BufferedImage combined; switch(direction) { default: case TRANSITION_LEFT: combined = ImageHelper.combineImages( ImageHelper.captureComponent(to), ImageHelper.captureComponent(from), ImageHelper.SIDE_BY_SIDE); break; case TRANSITION_RIGHT: combined = ImageHelper.combineImages( ImageHelper.captureComponent(from), ImageHelper.captureComponent(to), ImageHelper.SIDE_BY_SIDE); break; } // Create the intermediary transition panel JPanel transition = new JPanel(new GridLayout()); transition.add(new JLabel(new ImageIcon(combined))); final JScrollPane scroller = new JScrollPane(transition); // Hide the scroll bars scroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); // Add the scroller to the CardLayout and show it parent.add(scroller, "transitionCard"); cl.show(parent, "transitionCard"); // Prepare the Timer and the callback task Timer scrollTimer = new Timer(); TimerTask callback = new TimerTask() { @Override public void run() { // Once the ScrollTransition has finished: // Remove the intermediary transition Card and show the next Card parent.remove(scroller); parent.add(to, "newCard"); cl.show(parent, "newCard"); } }; // Start the ScrollTransition ScrollTransition scrollTask = new ScrollTransition(scrollTimer, callback, scroller, 20, direction); scrollTimer.scheduleAtFixedRate(scrollTask, 0, 30); } // ScrollTransition nested class as above ... }
Now everything is ready, we can use this class like this:
Java Code:public class TransitionPanel extends JPanel { TransitionPanel() { super(new CardLayout()); } public void transitionLeft(Component from, Component to) { Framinator.transitionComponents(this, from, to, Framinator.TRANSITION_LEFT); } public void transitionRight(Component from, Component to) { Framinator.transitionComponents(this, from, to, Framinator.TRANSITION_RIGHT); } }
And that's it. Of course you could choose to be more imaginative with the image effect/scrolling.
I'm using a Gesture listener so that I can transition the JPanels depending on a user swipe Gesture (for touch screen PCs).
Please leave your comments, complaints and suggestions.