The JLayer
class is a flexible and powerful decorator for Swing
components. It enables you to draw on components and respond to component
events without modifying the underlying component directly.
The JLayer
class in Java SE 7 is similar in spirit to the
JxLayer project project at
java.net. The JLayer
class was initially based on
the JXLayer
project, but its API evolved separately.
This document describes examples that show the power of the JLayer
class.
Full source code is available.
JLayer
Class
LayerUI
Class
For a brief introduction to the material on this page, watch the following video.
A JavaScript-enabled web browser and an Internet connection are required for the video. If you cannot see the video, try viewing it at YouTube.
JLayer
Class
The javax.swing.JLayer
class is half of a team. The other half
is the javax.swing.plaf.LayerUI
class.
Suppose you want to do some custom
drawing atop a JButton
object (decorate the
JButton
object). The component you want to decorate is the target.
LayerUI
subclass to do the drawing.JLayer
object that wraps the target and the
LayerUI
object.JLayer
object in your user interface just as you would use the
target component.
For example, to add an instance of a JPanel
subclass to a
JFrame
object, you would do something similar to this:
JFrame f = new JFrame(); JPanel panel = createPanel(); f.add (panel);
To decorate the JPanel
object, do something
similar to this instead:
JFrame f = new JFrame(); JPanel panel = createPanel(); LayerUI<JPanel> layerUI = new MyLayerUISubclass(); JLayer<JPanel> jlayer = new JLayer<JPanel>(panel, layerUI); f.add (jlayer);
Use generics to ensure that the JPanel
object and the LayerUI
object
are for compatible types. In the previous example, both the
JLayer
object and the LayerUI
object are used with
the JPanel
class.
The JLayer
class is usually generified with the exact type of its
view component, while the LayerUI
class is designed to be used with
JLayer
classes of its generic parameter or any of its ancestors.
For example, a LayerUI<JComponent>
object can be used with a
JLayer<AbstractButton>
object.
A LayerUI
object is responsible for custom decoration and event handling
for a JLayer
object. When you create an instance of a LayerUI
subclass,
your custom behavior can be applicable to every JLayer
object with an
appropriate generic type. That is why the JLayer
class is final
; all
custom behavior is encapsulated in your LayerUI
subclass, so there
is no need to make a JLayer
subclass.
LayerUI
Class
The LayerUI
class inherits most of its behavior from
the ComponentUI
class. Here are the most commonly overridden methods:
paint(Graphics g, JComponent c)
method is called when the target
component needs to be drawn. To render the
component in the same way that Swing renders it, call the
super.paint(g, c)
method.
installUI(JComponent c)
method is called when an instance of your
LayerUI
subclass is associated with a component. Perform any
necessary initializations here. The component that is passed in is the
corresponding JLayer
object. Retrieve the target component with
the JLayer
class' getView()
method.
uninstallUI(JComponent c)
method is called when an
instance of your
LayerUI
subclass is no longer associated with the given
component. Clean up here if necessary.
To use the JLayer
class, you need a good LayerUI
subclass. The simplest kinds of LayerUI
classes change how components are
drawn. Here is one, for example, that paints a transparent color gradient on a
component.
class WallpaperLayerUI extends LayerUI<JComponent> { @Override public void paint(Graphics g, JComponent c) { super.paint(g, c); Graphics2D g2 = (Graphics2D) g.create(); int w = c.getWidth(); int h = c.getHeight(); g2.setComposite(AlphaComposite.getInstance( AlphaComposite.SRC_OVER, .5f)); g2.setPaint(new GradientPaint(0, 0, Color.yellow, 0, h, Color.red)); g2.fillRect(0, 0, w, h); g2.dispose(); } }
The paint()
method is where the custom drawing takes place.
The call to the
super.paint()
method draws the contents of the JPanel
object.
After setting up a 50% transparent composite, the color gradient is drawn.
After the LayerUI
subclass is defined, using it is simple.
Here is some source code that uses the WallpaperLayerUI
class:
import java.awt.*; import javax.swing.*; import javax.swing.plaf.LayerUI; public class Wallpaper { public static void main(String[] args) { javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { createUI(); } }); } public static void createUI() { JFrame f = new JFrame("Wallpaper"); JPanel panel = createPanel(); LayerUI<JComponent> layerUI = new WallpaperLayerUI(); JLayer<JComponent> jlayer = new JLayer<JComponent>(panel, layerUI); f.add (jlayer); f.setSize(300, 200); f.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); f.setLocationRelativeTo (null); f.setVisible (true); } private static JPanel createPanel() { JPanel p = new JPanel(); ButtonGroup entreeGroup = new ButtonGroup(); JRadioButton radioButton; p.add(radioButton = new JRadioButton("Beef", true)); entreeGroup.add(radioButton); p.add(radioButton = new JRadioButton("Chicken")); entreeGroup.add(radioButton); p.add(radioButton = new JRadioButton("Vegetable")); entreeGroup.add(radioButton); p.add(new JCheckBox("Ketchup")); p.add(new JCheckBox("Mustard")); p.add(new JCheckBox("Pickles")); p.add(new JLabel("Special requests:")); p.add(new JTextField(20)); JButton orderButton = new JButton("Place Order"); p.add(orderButton); return p; } }
Here is the result:
Source code:
Run with Java Web Start:
The LayerUI
class' paint()
method gives you complete control
over how a component is drawn. Here is another LayerUI
subclass that shows how the entire contents of a panel can be modified using Java 2D
image processing:
class BlurLayerUI extends LayerUI<JComponent> { private BufferedImage mOffscreenImage; private BufferedImageOp mOperation; public BlurLayerUI() { float ninth = 1.0f / 9.0f; float[] blurKernel = { ninth, ninth, ninth, ninth, ninth, ninth, ninth, ninth, ninth }; mOperation = new ConvolveOp( new Kernel(3, 3, blurKernel), ConvolveOp.EDGE_NO_OP, null); } @Override public void paint (Graphics g, JComponent c) { int w = c.getWidth(); int h = c.getHeight(); if (w == 0 || h == 0) { return; } // Only create the offscreen image if the one we have // is the wrong size. if (mOffscreenImage == null || mOffscreenImage.getWidth() != w || mOffscreenImage.getHeight() != h) { mOffscreenImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); } Graphics2D ig2 = mOffscreenImage.createGraphics(); ig2.setClip(g.getClip()); super.paint(ig2, c); ig2.dispose(); Graphics2D g2 = (Graphics2D)g; g2.drawImage(mOffscreenImage, mOperation, 0, 0); } }
In the paint()
method, the panel is rendered into an offscreen
image. The offscreen image is processed with a convolution operator, then drawn
to the screen.
The entire user interface is still live, just blurry:
Source code:
Run with Java Web Start:
Your LayerUI
subclass can also receive all of the events of its
corresponding component. However, the JLayer
instance must register its
interest in specific types of events. This happens with the JLayer
class'
setLayerEventMask()
method. Typically, however, this call is made
from initialization performed in the LayerUI
class'
installUI()
method.
For example, the following excerpt shows a portion of a LayerUI
subclass that
registers to receive mouse and mouse motion events.
public void installUI(JComponent c) { super.installUI(c); JLayer jlayer = (JLayer)c; jlayer.setLayerEventMask( AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK ); }
All events going to your JLayer
subclass get routed to an
event handler method whose name matches the event type. For example, you can
respond to mouse and
mouse motion events by overriding corresponding methods:
protected void processMouseEvent(MouseEvent e, JLayer l) { // ... } protected void processMouseMotionEvent(MouseEvent e, JLayer l) { // ... }
The following is a LayerUI
subclass that draws a translucent circle
wherever the mouse moves inside a panel.
class SpotlightLayerUI extends LayerUI<JPanel> { private boolean mActive; private int mX, mY; @Override public void installUI(JComponent c) { super.installUI(c); JLayer jlayer = (JLayer)c; jlayer.setLayerEventMask( AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK ); } @Override public void uninstallUI(JComponent c) { JLayer jlayer = (JLayer)c; jlayer.setLayerEventMask(0); super.uninstallUI(c); } @Override public void paint (Graphics g, JComponent c) { Graphics2D g2 = (Graphics2D)g.create(); // Paint the view. super.paint (g2, c); if (mActive) { // Create a radial gradient, transparent in the middle. java.awt.geom.Point2D center = new java.awt.geom.Point2D.Float(mX, mY); float radius = 72; float[] dist = {0.0f, 1.0f}; Color[] colors = {new Color(0.0f, 0.0f, 0.0f, 0.0f), Color.BLACK}; RadialGradientPaint p = new RadialGradientPaint(center, radius, dist, colors); g2.setPaint(p); g2.setComposite(AlphaComposite.getInstance( AlphaComposite.SRC_OVER, .6f)); g2.fillRect(0, 0, c.getWidth(), c.getHeight()); } g2.dispose(); } @Override protected void processMouseEvent(MouseEvent e, JLayer l) { if (e.getID() == MouseEvent.MOUSE_ENTERED) mActive = true; if (e.getID() == MouseEvent.MOUSE_EXITED) mActive = false; l.repaint(); } @Override protected void processMouseMotionEvent(MouseEvent e, JLayer l) { Point p = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), l); mX = p.x; mY = p.y; l.repaint(); } }
The mActive
variable indicates whether or not the mouse is inside the
coordinates of the panel. In the installUI()
method, the
setLayerEventMask()
method is called to indicate the
LayerUI
subclass' interest in receiving mouse and mouse motion
events.
In the processMouseEvent()
method, the mActive
flag is set
depending on the position of the mouse. In the
processMouseMotionEvent()
method, the coordinates of the mouse movement
are stored in the mX
and mY
member variables
so that they can be used later
in the paint()
method.
The paint()
method shows the default appearance of the panel, then
overlays a radial gradient for a spotlight effect:
Source code:
Run with Java Web Start:
This example is an animated busy indicator. It demonstrates animation in a
LayerUI
subclass and features a fade-in and fade-out. It
is more complicated that the previous examples, but it is based on the same
principle of defining a paint()
method for custom drawing.
Click the Place Order button to see the busy indicator for 4 seconds. Notice how the panel is grayed out and the indicator spins. The elements of the indicator have varying levels of transparency.
The LayerUI
subclass, the WaitLayerUI
class, shows how to
fire property change events to update the component. The WaitLayerUI
class uses a
Timer
object to update its state 24 times a second. This happens in the
timer's target method, the actionPerformed()
method.
The actionPerformed()
method uses the firePropertyChange()
method to
indicate that the internal state was updated. This triggers a call to
the applyPropertyChange()
method, which repaints the JLayer
object:
Source code:
Run with Java Web Start:
The final example in this document shows how the JLayer
class
can be used to
decorate text fields to show if they contain valid data. While the other
examples use the JLayer
class to wrap panels or general
components, this example shows how to wrap a JFormattedTextField
component
specifically. It also demonstrates that a single LayerUI
subclass
implementation can be used for multiple JLayer
instances.
The JLayer
class is used to provide a visual indication for fields that have
invalid data. When the ValidationLayerUI
class paints the text field, it
draws a red X if the field contents cannot be parsed. Here is an example:
Source code:
Run with Java Web Start: