Shannon, Chris and I showed many cool effects during this year’s Extreme GUI Makeover session, at JavaOne 2007. Since we cannot release the source code yet, I will explain some of the effects in detail. I will start with the blurred background displayed when a modal dialog shows up.
Blurring a dialog’s or a menu’s background is often used in video games to attract user’s attention to the front most widgets. Using this effect allows you to keep the context without suffering from the visual clutter it might induce. Adobe Flex offers a similar effect when you bring up a modal dialog in a Flex application. To better understand this effect, you can take a look at one of these two videos:
You can also take a look at the screenshot below. Note that the screenshot does not show the fade in and fade out animations used to bring up and dismiss the dialog. The background gradually blurs while the dialog progressively becomes visible.
Writing this effect is surprisingly easy with the help of the Timing Framework and SwingX. The former is used to drive the animations while the latter offers support for alpha translucency and blur.
The actual “dialog” is made of two classes, DetailsView
and DetailPanel
. The first class, DetailsView
is used as a glass pane and is responsible for managing the animations and blurring the background. The second class contains the actual dialog, the black rounded rectangle in the screenshot. While DetailPanel
contains interesting pieces of code, we will not spend any time on it in this entry. Simply note, however, that DetailPanel
extends the JXPane
class from SwingX. This class offers a public property called alpha
which can be used to change the translucency of the container and its children.
Here is what the DetailsView
class looks like:
public class DetailsView extends JPanel {
private DetailPanel detailPanel;
private BufferedImage blurBuffer;
private BufferedImage backBuffer;
private float alpha = 0.0f;
DetailsView(DetailPanel detailPanel) {
setLayout(new GridBagLayout());
this.detailPanel = detailPanel;
this.detailPanel.setAlpha(0.0f);
add(detailPanel, new GridBagConstraints());
// Should also disable key events...
addMouseListener(new MouseAdapter() { });
}
}
You can notice that we use two image buffers. They are used to create the animation of a gradually blurring background. The basic idea is to capture the frame’s content into a buffer, blur this buffer and paint it in the glass pane, behind the “dialog.” Unfortunately, doing so would be impractical for an animation. Even though SwingX provides an efficient blur filter, it would be very difficult to render a smooth animation of that size with it. Instead, we keep the frame’s content into an original buffer and blur that buffer into another buffer. At drawing time, we first paint the frame’s content, then the blurred copy. By progressively changing the opacity of the blurred copy, we can simulate an increasing blur effect.
Our first step is therefore to create our buffers:
private void createBlur() {
JRootPane root = SwingUtilities.getRootPane(this);
blurBuffer = GraphicsUtilities.createCompatibleImage(
getWidth(), getHeight());
Graphics2D g2 = blurBuffer.createGraphics();
root.paint(g2);
g2.dispose();
backBuffer = blurBuffer;
blurBuffer = GraphicsUtilities.createThumbnailFast(
blurBuffer, getWidth() / 2);
blurBuffer = new GaussianBlurFilter(5).filter(blurBuffer, null);
}
This method relies on GraphicsUtilities
and GaussianBlurFilter
from SwingX to render a good-looking blur efficiently. Notice how the blurred buffer is downscaled before the filter is applied. This trick lets us reduce the blur’s radius, thus making it a lot faster. We just need to scale this image back to its original size at drawing time.
Drawing the back buffer and the blur buffer does not involve any complicated trick. We just set the bilinear rendering hint on the graphics context and rely on an alpha composite to perform the translucent drawing:
@Override
protected void paintComponent(Graphics g) {
if (isVisible() && blurBuffer != null) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(backBuffer, 0, 0, null);
g2.setComposite(AlphaComposite.SrcOver.derive(alpha));
g2.drawImage(blurBuffer, 0, 0, getWidth(), getHeight(), null);
g2.dispose();
}
}
The most interesting part of this piece of code is the use of the field called alpha
, that you saw in the first code snippet. This field is a float which holds a value ranging from 0 (no blur, transparent dialog) to 1 (blurred background, opaque dialog.) The animated effect is generated by modifying this value over time. To do this, we will use the trusty Timing Framework:
public float getAlpha() {
return alpha;
}
public void setAlpha(float alpha) {
this.alpha = alpha;
repaint();
}
public void fadeIn() {
createBlur();
setVisible(true);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Animator animator = PropertySetter.createAnimator(
400, detailPanel, "alpha", 1.0f);
animator.setAcceleration(0.2f);
animator.setDeceleration(0.3f);
animator.addTarget(
new PropertySetter(DetailsView.this, "alpha", 1.0f));
animator.start();
}
});
}
For those of you who don’t know the Timing Framework, we are simply instructing it to animate two properties, both called “alpha,” on two different targets, this
and the DetailPanel
(remember, its alpha property is provided by its superclass JXPanel
.) These properties are animated from their current value, 0.0f, to 1.0f over 400 milliseconds. The acceleration and deceleration are used to create a non-linear interpolation of the values over time and generate a more natural rendering.
That’s all there is to it! Dismissing the dialog is done with the fadeOut()
method, whose code is almost exactly the same as fadeIn()
. Instead of changing the properties from 0.0f to 1.0f, fadeOut()
changes them from 1.0f to 0.0f. Remember to set DetailsView
as the glass pane on your frame and your effect is ready to go.