Search code examples
javaswinguser-interfacejlabelgraphics2d

Creating a JLabel with a Gradient


I'm new to Java and I'm trying to create a heading using a JLabel and for its fill to be a gradient. I cannot get it to work and I've been trying for a while. I've been grabbing bits of come from here and other websites and cannot seem to make this work, nor make sense of other peoples more complex code that does work. Here are my two classes so far:

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EtchedBorder;
public class Test {
    public static void main(String[] args) {
        new Test().setupGUI();
    }
    public void setupGUI() {
        //set up frames and buttons etc.
            JFrame theFrame = new JFrame ("Crystal Ball");
            theFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            JPanel background = new JPanel();
            background.setBackground(Color.BLUE);
            background.setLayout(new BoxLayout(background, BoxLayout.PAGE_AXIS));
            theFrame.setSize(500,1000);
            DLabel heading = new DLabel("Guess a Number");
            heading.setText("GUESS A NUMBER");
            heading.setPreferredSize(new Dimension(theFrame.getWidth(),100));
            heading.setFont(new Font("Serif", Font.PLAIN, 40));
            heading.setAlignmentX(Component.CENTER_ALIGNMENT);
            //heading.setBackground(Color.YELLOW);
            heading.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
            background.add(heading);
            theFrame.getContentPane().add(background);
            theFrame.pack();
            theFrame.setVisible(true);
            //startGame();
        }
}

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.BorderFactory;
import javax.swing.JLabel;

public class DLabel extends JLabel
{

    Dimension size = new Dimension(70, 80);

    public DLabel(String name)
    {
        this.setPreferredSize(size);
        this.setText(name);
        this.setBorder(BorderFactory.createBevelBorder(TOP, Color.white, Color.black));
        this.setOpaque(true);
    }

    public void paintComponent(Graphics g) {
      // super.paintComponent(g);  // *** commented
      Graphics2D g2d = (Graphics2D) g;
      Color color1 = Color.YELLOW;
      Color color2 = color1.brighter();
      int w = getWidth();
      int h = getHeight();
      GradientPaint gp = new GradientPaint(0, 0, color1, 0, h, color2);
      g2d.setPaint(gp);
      g2d.fillRect(0, 0, w, h);
      super.paintComponent(g); // *** added
    }

}

Solution

  • 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