How to Overlap Panels in Swing?

Solution 1:

So yes, a JLayeredPane would allow easy overlap of Swing components such as JPanels, and there are also layouts others have created that allow this, one called "overlay layout", but that's not what you want to for your currently stated problem.

Yours is an XY Problem type question where you ask "how do I solve X problem" when the best solution is not to solve it in this way, but rather to do Y, something completely different. Here, to paint multiple different images, your best solution is not to create and overlap heavier-weight Swing components such as JPanels, but rather to draw in one single JPanel and overlap sprite images. Otherwise you're just making things unnecessarily harder for yourself and your code than is needed.

For example:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;

@SuppressWarnings("serial")
public class Example2 extends JPanel {
    private static final int MY_WIDTH = 1600;
    private static final int MY_HEIGHT = 720;
    List<Rectangle> rectangles = new ArrayList<>();

    public Example2() {
        setPreferredSize(new Dimension(MY_WIDTH, MY_HEIGHT));
        setBackground(Color.WHITE);
        
        rectangles.add(new Rectangle(0, 0, 200, 200));
        rectangles.add(new Rectangle(0, 80 + MY_HEIGHT / 2, 200, 200));
    }
    
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        for (Rectangle rectangle : rectangles) {
            g2.fill(rectangle);
        }
    }
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            Example2 example = new Example2();
            
            JFrame frame = new JFrame("GUI");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(example);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }
}

And yes, as suggested in comments, override paintComponent, not paint. This reduces the risk of unwanted side effects that might come from painting child components or borders, and also allows for automatic double-buffering for when you do animation.

Also, a while (true) loop is not a healthy construct within an event-driven GUI program, not as you've written it. If you need repeated actions in a Swing program (which you don't in your example, not yet), use a Swing Timer instead.

So doing this this way gives you good flexibility. For instance, if you wanted to modify the above program to allow addition of shapes on mouse click, it would be easy to do so:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;

@SuppressWarnings("serial")
public class Example3 extends JPanel {
    private static final int MY_WIDTH = 1600;
    private static final int MY_HEIGHT = 720;
    List<ColorShape> colorShapes = new ArrayList<>();

    public Example3() {
        setPreferredSize(new Dimension(MY_WIDTH, MY_HEIGHT));
        setBackground(Color.WHITE);
        
        addMouseListener(new MyMouse());
    }
    
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        for (ColorShape colorShape : colorShapes) {
            colorShape.draw(g2);
        }
    }
    
    private class MyMouse extends MouseAdapter {
        @Override
        public void mousePressed(MouseEvent e) {
            // create a random color
            float hue = (float) Math.random();
            float saturation = 1f;
            float brightness = (float) (0.5 * Math.random() + 0.5);
            Color color = Color.getHSBColor(hue, saturation, brightness);

            // create a new ColorShape, add to list, and repaint:
            colorShapes.add(new ColorShape(e.getPoint(), color));
            repaint();
        }
    }
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            Example3 example = new Example3();
            
            JFrame frame = new JFrame("GUI");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(example);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }
}
class ColorShape {
    private int width = 80;
    private Point location;
    private Color color;
    private Shape shape;

    public ColorShape(Point location, Color color) {
        this.location = location;
        this.color = color;
        int x = location.x - width / 2;
        int y = location.y - width / 2;
        shape = new Ellipse2D.Double(x, y, width, width);
    }
    
    public void draw(Graphics2D g2) {
        g2.setColor(color);
        g2.fill(shape);
    }
    
    public Point getLocation() {
        return location;
    }
}