Repaint Swing Button with Different Gradient

Change JButton gradient color, but only for one button, not all

You can override the paintComponent method of the JButton instance and paint its Graphics object with one of the following classes that implement the Paint interface:

  • GradientPaint.
  • LinearGradientPaint
  • MultipleGradientPaint
  • RadialGradientPaint

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public final class JGradientButtonDemo {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
createAndShowGUI();
}
});
}

private static void createAndShowGUI() {
final JFrame frame = new JFrame("Gradient JButton Demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new FlowLayout());
frame.add(JGradientButton.newInstance());
frame.setSize(new Dimension(300, 150)); // used for demonstration
//frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}

private static class JGradientButton extends JButton {
private JGradientButton() {
super("Gradient Button");
setContentAreaFilled(false);
setFocusPainted(false); // used for demonstration
}

@Override
protected void paintComponent(Graphics g) {
final Graphics2D g2 = (Graphics2D) g.create();
g2.setPaint(new GradientPaint(
new Point(0, 0),
Color.WHITE,
new Point(0, getHeight()),
Color.PINK.darker()));
g2.fillRect(0, 0, getWidth(), getHeight());
g2.dispose();

super.paintComponent(g);
}

public static JGradientButton newInstance() {
return new JGradientButton();
}
}
}

Sample Image

Gradient problems in Java

The problem with the code is this line:

GradientPaint pGradient = new GradientPaint(0, 0, new Color(255, 255, 255, iDegreeWhite), 0, getHeight(), new Color(0, 0, 0, iDegreeBlack));

should be this:

GradientPaint pGradient = new GradientPaint(0, 0, new Color(255, 255, 255, iDegreeWhite), 0, getHeight(), new Color(255, 245, 62, iDegreeWhite));

Looking back at your question, I see you've basically found the solution - but it's a little different. Here's why:

When blending the colors in the gradient, your blending all aspects of the color: RBGA

You see, until you reach the full second color, you are mixing black into the color gradient and that mix won't be at the full transparency. So 20% of the way down the page, you'll have this color: 204,204,204,144 (that's 80% white, 20% black, and 56% opaque).

The easiest solution is to avoid translucency completely if you're not using it - just blend from the light yellow at the top to the dark yellow at the bottom. It takes less resources this way too.

But since you're using transparency, the solution I've provided uses transparency as well. You'll be blending from the white to the yellow using a consistent transparency.

If you blend from white to white (transparent), you'll have the same problem as before only with white (which will be less noticeable since it's one of the colors you're using): The gradient will have a white "streak" until the second color reaches full transparency.

As far as why it acts different on different JVMs, I'd guess that Oracle may have changed the way alpha's are blended. Better alpha support seems to be something they've been working on for a while, and this is a logical step in that direction. I don't have any proof on this statement though - it's just based on other changes I've seen with alpha's (like transparent windowing).

EDIT
This SSCCE demos both the problem and the solution:

import java.awt.*;
import javax.swing.*;
import javax.swing.UIManager.LookAndFeelInfo;

public class TransparencyDemo extends Box{

protected JPanel pGradientPane;

//Interface gradient specification
private Color pInterfaceColour = new Color(255, 245, 62);
protected int iDegreeWhite = 180;
protected int iDegreeBlack = 0;

public TransparencyDemo() {
super(BoxLayout.X_AXIS);
setOpaque(true);

//Incorrect Solution
pGradientPane = new JPanel(new GridBagLayout())
{
private static final long serialVersionUID = 1L;

protected void paintComponent(Graphics pGraphics)
{
Graphics2D pGraphicsGradientRender = (Graphics2D) pGraphics;
pGraphicsGradientRender.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
GradientPaint pGradient = new GradientPaint(0, 0, new Color(255, 255, 255, iDegreeWhite), 0, getHeight(), new Color(0, 0, 0, iDegreeBlack));
pGraphicsGradientRender.setPaint(pGradient);
pGraphicsGradientRender.fillRect(0, 0, getWidth(), getHeight());
super.paintComponent(pGraphics);
}
};
pGradientPane.setOpaque(false);
add(pGradientPane);

//Correct Solution
JPanel pGradientPane2 = new JPanel(new GridBagLayout())
{
private static final long serialVersionUID = 1L;

protected void paintComponent(Graphics pGraphics)
{
Graphics2D pGraphicsGradientRender = (Graphics2D) pGraphics;
pGraphicsGradientRender.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
GradientPaint pGradient = new GradientPaint(0, 0, new Color(255, 255, 255, iDegreeWhite), 0, getHeight(), new Color(255, 245, 62, iDegreeWhite));
pGraphicsGradientRender.setPaint(pGradient);
pGraphicsGradientRender.fillRect(0, 0, getWidth(), getHeight());
super.paintComponent(pGraphics);
}
};
pGradientPane2.setOpaque(false);
add(pGradientPane2);

setBackground(pInterfaceColour);

}

public static void main(String[] args){
try {
for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}

final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TransparencyDemo());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}

How to add gradient color to Jbuttons placed in jtoolbar

The button has a contentAreaFilled attribute which determines if the look and feel should paint the content area of the button. When you call super.paintComponent, the look and feel delegate will paint over what you have done.

You can set this property to false and it should then work.

For example...

RoundButton

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.RenderingHints;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class GradientButtons {

public static void main(String[] args) {
new GradientButtons();
}

public GradientButtons() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}

JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridBagLayout());
frame.add(new RoundButton("Click me"));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}

public class RoundButton extends JButton {

public RoundButton(String text) {
super(text);
setBorderPainted(false);
setContentAreaFilled(false);
setFocusPainted(false);
setOpaque(false);
}

@Override
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
int radius = Math.max(size.width, size.height);
size.width = radius;
size.height = radius;
return size;
}

@Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
GradientPaint gp = new GradientPaint(0, 0,
Color.RED, 0, getHeight(),
Color.YELLOW);
g2d.setPaint(gp);
g2d.fillOval(0, 0, getWidth(), getHeight());
super.paintComponent(g);
}

}

}

You may want to check the ButtonModel's armed and/or pressed state so you can also change the way that the button is painted when clicked, as a suggestion...

Custom Gradient Button - can't see Text

as Stan already answered, one part of the solution is to

button.setOpaque(false)

buttons are a bit crazy, they want to be huddled into really not painting the background

button.setContentAreaFilled(false)

Beware: the exact outcome might still be highly LAF dependent - f.i. looking really bad. For synth-based (as f.i. Nimbus) you might consider to install a custom Painter configured with the gradient/colors as choosen by the user

Edit

just double-checked:

// tell ui to not paint the background
button.setOpaque(false);
button.setContentAreaFilled(false);

// override paintComponent
protected void paintComponent(...) {
// do custom backgroudn painting
...
// let ui handle the foreground (it wont touch the background due to the false settings above)
super.paintComponent()
}

works fine for all core LAFs (on win)

How to set the button color of a JButton (not background color)

You'll have to decide if it's worth the effort, but you can always create youe own ButtonUI, as shown in this example due to @mKorbel.

Creating a JLabel with a Gradient

There is one little "trick" you can actually do, by leaving the label transparent, you can actually paint under the text by painting BEFORE you call super.paintComponent, for example...

Raindbow

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.LinearGradientPaint;
import java.awt.Point;
import java.awt.Rectangle;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class TestLabel101 {

public static void main(String[] args) {
new TestLabel101();
}

public TestLabel101() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}

JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}

public class TestPane extends JLabel {

public TestPane() {
setText("Happy, Happy");
setForeground(Color.WHITE);
setHorizontalAlignment(CENTER);
}

@Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
LinearGradientPaint lgp = new LinearGradientPaint(
new Point(0, 0),
new Point(0, getHeight()),
new float[]{0.142f, 0.284f, 0.426f, 0.568f, 0.71f, 0.852f, 1f},
new Color[]{Color.PINK, Color.MAGENTA, Color.BLUE, Color.GREEN, Color.YELLOW, Color.ORANGE, Color.RED});
g2d.setPaint(lgp);
g2d.fill(new Rectangle(0, 0, getWidth(), getHeight()));
g2d.dispose();
super.paintComponent(g);
}

}

}

nb: I should point out that this process is inefficient, as the RepaintManager will still want to paint under the component

There is another trick, but my two year old daughter wants to check to see if Santa is here ;)

Updated

The other trick involves understanding how the paint process actually works. When you call super.paintComponent, it calls the update method on the ComponentUI (look and feel delegate), this is actually the method that fills the background if the component is opaque, this method then calls the look and feels delegate's paint method, which actually does the base painting...

We can circumvent the process slightly and instead of calling super.paintComponent, we can call the look and feels delegate's paint method directly...

public class TestPane extends JLabel {

public TestPane() {
setText("Happy, Happy");
setForeground(Color.WHITE);
setHorizontalAlignment(CENTER);
setOpaque(true);
}

@Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
LinearGradientPaint lgp = new LinearGradientPaint(
new Point(0, 0),
new Point(0, getHeight()),
new float[]{0.142f, 0.284f, 0.426f, 0.568f, 0.71f, 0.852f, 1f},
new Color[]{Color.PINK, Color.MAGENTA, Color.BLUE, Color.GREEN, Color.YELLOW, Color.ORANGE, Color.RED});
g2d.setPaint(lgp);
g2d.fill(new Rectangle(0, 0, getWidth(), getHeight()));
g2d.dispose();
getUI().paint(g, this);
}

}

This is more efficient then the previous example, as it doesn't require the RepaintManager to paint the area underneath this component, but it might not work with all look and feels



Related Topics



Leave a reply



Submit