Java tip: Use gradient backgrounds to add interest and polish to a user interface

Technologies: Java 5+

Java's Swing components have a constant background color used to fill the entire component area. To add interest, contrast, and polish to a user interface, override the component's background painting and add a gradient to smoothly vary the color across the background. This tip shows how and demonstrates the effect.

Adding gradients to a user interface

The default solid gray background of JFrame, JDialog, JPanel, and other Swing components can be rather dull. A gentle gradient adds a touch of elegance without interfering with the legibility of the content.

For example, adding a gradient to a toolbar gives it a smooth transition from the menubar (on Windows and Linux) and helps to seat the icons. A similar effect is used in many Mac OS X applications.

JToolBar withot and with a gradient backgrond

Adding a gradient to a JLabel makes a nice heading. Adding a gradient to the background of a JDialog frames the content. And adding a gradient to a plot adds a smooth PowerPoint-ish effect.

JDialog withot and with a gradient backgrond

Painting a custom component background

Every Swing component uses a paint( ) method to draw the component. Swing's default paint( ) method calls three other methods to paint different parts of the component:

  • paintComponent( ) paints the component's background and intrinsic shapes (like a checkbox or button)
  • paintBorder( ) paints the border around the component
  • paintChildren( ) paints the component's children

To add a custom background, override the paintComponent( ) method and draw whatever you like.

To get the component to draw itself on top of your new background, call super.paintComponent( ) at the end of your paint method. For a JLabel, this will draw the label text and icon. For a JCheckBox, this will draw the checkbox and label. And so on for each of the Swing components. In each case, the component uses its UI delegate to draw the label, icon, checkbox, etc. using the current look and feel.

Normally, if the component is opaque, the UI delegate starts by filling the background with a constant color. But this will cover up your custom background. To prevent this, call setOpaque(false) before calling super.paintComponent( ), then setOpaque(true) again after the component has drawn itself. This tricks the UI delegate into thinking the background should be left alone.

So, here's the shell of a paintComponent( ) method:

protected void paintComponent( Graphics g ) 
{
    if ( !isOpaque( ) )
    {
        super.paintComponent( g );
        return;
    }
 
    ... paint custom background here ...
 
    setOpaque( false );
    super.paintComponent( g );
    setOpaque( true );
}

Painting a gradient using Graphics2D and GradientPaint

Swing's paint methods are called with a java.awt.Graphics object argument that has basic methods to draw lines, fill areas, and place text. Ever since JDK 1.2, each paint argument is actually a java.awt.Graphics2D object that subclasses java.awt.Graphics, but provides much more functionality.

To get a Graphics2D object, simply cast the incoming Graphics object:

Graphics2D g2d = (Graphics2D)g;

To paint a gradient, create a java.awt.GradientPaint painter object then fill the component's background using it. GradientPaint requires two colors for the opposite ends of the gradient, and two anchor points that place those colors on a 2D plane. If you arrange those points horizontally, you'll get a gradient that goes from left to right. If the points are arranged vertically, the gradient will go from top to bottom. Or you can place the points diagonally to get a diagonal gradient.

For filling a component's background, choose anchor points that are at opposite sides of the component.

int w = getWidth( );
int h = getHeight( );
 
// Paint a gradient from top to bottom
GradientPaint gp = new GradientPaint(
    0, 0, color1,
    0, h, color2 );

g2d.setPaint( gp );
g2d.fillRect( 0, 0, w, h );

The colors can be anything you like, of course. To create a subtle gradient based upon the current look and feel's background color, use that color and a darker version.

Color color1 = getBackground( );
Color color2 = color1.darker( );

Handling opaque child components

By default, most Swing components are opaque and they'll fill their backgrounds with a constant color. For example, if you create a JPanel with a gradient background, then add a JCheckBox to it, you'll get an ugly flat rectangle behind the checkbox and atop your gradient background.

JCheckBox withot and with a gradient backgrond

To fix this for a JCheckBox, or any other Swing component, call setOpaque(false) on the component. This disables its background painting and lets the parent component's gradient show through.

JPanel panel = new JPanel( )
{
    protected void paintComponent( Graphics g ) { ... }
};
 
JCheckBox check = new JCheckBox( "Lorem ipsum?" );
check.setOpaque( false );
panel.add( check );

JList, JTable, and JTree components take special care. All of these use cell renderers to create drawable components for their content. The default renderers all create opaque components, so if you add a gradient background to a JList, JTable, or JTree, you'll get a flat rectangle behind each list, table, or tree cell.

To fix this for a JList, extend the DefaultListCellRenderer and call setOpaque(false) on its returned component if the cell isn't selected. If it is selected, leave the component opaque so that the selection background is visible.

JList withot and with a gradient backgrond

public class MyListCellRenderer
    extends DefaultListCellRenderer
{
    public Component getListCellRendererComponent(
        JList list, Object value, int index,
        boolean isSelected, boolean cellHasFocus )
    {
        Component c = super.getListCellRendererComponent(
            list, value, index, isSelected, cellHasFocus );
        ((JComponent)c).setOpaque( isSelected );
        return c;
    }
}

For a JTable, you can override the DefaultTableCellRenderer in the same way. Alternatively, extend JTable and override its prepareRenderer( ) method. That method is called each time a renderer is needed for a table cell, and it provides an opportunity to call setOpaque(false) on the cell's component if the cell isn't selected. Do the same with the JTable's prepareEditor( ) method for table cell editors.

When a JTable is in a scroll pane, if the table is shorter than the viewport the viewport's own background fills the blank area after the bottom of the table. This will look odd if the table's background is a gradient. To fix this, override JTable's getScrollableTracksViewportHeight( ) method and return true. Thereafter, the table's background will extend to the bottom of the viewport.

JTable withot and with a gradient backgrond

public class MyTable
    extends JTable
{

    public Component prepareRenderer( TableCellRenderer ren,
        int row, int col )
    {
        Component c = super.prepareRenderer( ren, row, col );
        ((JComponent)c).setOpaque( isCellSelected( row, col ) );
        return c;
    }
    public Component prepareEditor( TableCellEditor ed,
        int row, int col )
    {
        Component c = super.prepareEditor( ed, row, col );
        ((JComponent)c).setOpaque( isCellSelected( row, col ) );
        return c;
    }
    public boolean getScrollableTracksViewportHeight( )
    {
        return true;
    }
}

For a JTree, the DefaultTreeCellRenderer is an optimized JLabel that, unfortunately, no longer conforms to the contract of a JComponent. The renderer uses its own paint( ) method that ignores component opacity and standard color settings. To avoid solid rectangle backgrounds behind tree cell components, you'll have to create your own tree cell renderer. You could override DefaultTreeCellRenderer's paint( ) method or start from scratch. But perhaps the easiest approach is to extend DefaultTreeCellRenderer and substitute a different JLabel that copies the tree icon and text from the DefaultTreeCellRenderer, but uses a normal paint( ) method. Set this label's opacity to false if the cell isn't selected.

JTree withot and with a gradient backgrond

public class MyTreeCellRenderer
    extends DefaultTreeCellRenderer
{
    private JLabel label = new JLabel( );
 
    public Component getTreeCellRendererComponent(
        JTree tree, Object value, boolean isSelected,
        boolean isExpanded, boolean isLeaf,
        int row, boolean hasFocus )
    {
        JLabel default = (JLabel)super.getTreeCellRendererComponent(
            tree, value, isSelected, isExpanded,
            isLeaf, row, hasFocus );
 
        label.setIcon( default.getIcon( ) );
        label.setText( default.getText( ) );
        label.setIconTextGap( default.getIconTextGap( ) );
        label.setFont( default.getFont( ) );
        label.setBackground( isSelected ?
            getBackgroundSelectionColor( ) :
            getBackgroundNonSelectionColor( ) );
        label.setForeground( isSelected ?
            getTextSelectionColor( ) :
            getTextNonSelectionColor( ) );
        label.setOpaque( isSelected );
        return label;
    }
}

For better performance, you may need to optimize your customized cell renderer along the same lines as the DefaultTreeCellRenderer. Override property change reporting and customize the paint( ) method. Also remember to do the same with the tree's cell editor.

For large JList, JTable, and JTree content, optimize your paintComponent( ) method to only paint the area within the current clip rectangle (use getClipBounds( ) on the Graphics object). Also cache the GradientPaint and Color objects, and recreate them only when the component size or colors change.

Working with different look-and-feels

Gradients work fine with the Metal, Windows, and Mac OS X look-and-feels, with some caveats:

  • On Windows using the Windows Classic look-and-feel, buttons have a thin white outline that changes color when the button has the focus. This looks odd atop a gradient background, or any non-white background. To remove the outline, call setFocusPainted(false) on the button.
  • On Mac OS X, panels using a default TitledBorder automatically use the rounded corner inset appearance of standard Mac OS X group panels. This will look odd when either the titled panel, or the parent component has a gradient background. Either skip the gradient, or construct the TitledBorder with a child LineBorder instead of the Mac OS X default.
  • On Linux, the GTK look-and-feel has been badly broken for several years. Many of the components are drawn incorrectly and ignore Java settings for borders and colors. Gradient backgrounds have random miss-colored stripes due to a buggy color interpolation algorithm. In any case, the entire GTK look-and-feel is nearly unusable at this time, whether or not you use gradients.
  • On Linux, the Motif look-and-feel is so appallingly ugly that adding a gradient background isn't going to make a Motif user interface look professional. On Linux, use the Metal look-and-feel instead. Or one of the plug-in look-and-feels.

Further reading

Related tips

Other articles and specifications

Comments

Thank for the tip. That is

Thank for the tip. That is extremely useful.

Great tips

Great tips

thanks

Thanks a lot, my UI looks a lot nicer now and it was dead easy :)

Thanks a lot, very usefull

Thanks a lot, very usefull tutorial!

Gradients

Hello, this tutorial seems to be exactly what I'm trying to figure out how to do. However, I was unable to recreate the gradients you were able to. For example, I'm unsure where exactly to override the paintComponent() method. I found one that seems similar called paintComponents(), but I couldn't get it to work. Would it be possible for you to post a working .java file, so we could see the context of all of these methods? Thanks!

Re: Gradients

The paintComponent method is on the JComponent class that is the parent class of all Swing components.

You can override this method on any Swing component, but an easy place to experiment is with JPanel. Subclass JPanel and provide your own paintComponent method, then draw anything you like. When you add an instance of your new panel, you'll see your drawing as the background for the panel.

it helped me a lot.. thank

it helped me a lot.. thank you so much.

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • Web page addresses and e-mail addresses turn into links automatically.

More information about formatting options

Nadeau software consulting
Nadeau software consulting