Search code examples
javaswinggraphicsjpanelpaintcomponent

How to draw an array of Graphics


OK so i'm working on a school project (little animation) and I am currently trying to make rain. I'm not sure how I would go about drawing individual "drops" using JPanel. My Code so far:

Main Class:

public class RainPanel extends JPanel {
private static final long serialVersionUID = 1L;

public static void main(String[] args) {
    new RainPanel();
}
private final int WIDTH = 800, HEIGHT = 800;

Drop drop;

public RainPanel() {
    init();
}

public void init() {
    JFrame frame = new JFrame("Rain");
    JPanel drop = new Drop();
    frame.setVisible(true);
    frame.setSize(WIDTH, HEIGHT);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setLocationRelativeTo(null);
    frame.add(drop);
}

public void paintComponent(Graphics g) {
    super.paintComponent(g);
        drop.paint(g);
}

Drop class:

public class Drop extends JPanel implements ActionListener{
private static final long serialVersionUID = 1L;

int x,y;
int yVel = 2;

Timer t = new Timer(5, this);
Random r = new Random();
ArrayList<Drop> DropArray;

public Drop() {
    x = r.nextInt(800);
    y = r.nextInt(800);
    t.start();
}

public void paint(Graphics g) {
    super.paintComponent(g);
    DropArray = new ArrayList<>(100); 
    for (int i = 0; i < DropArray.size(); i++) {
        DropArray.add(new Drop());
    }
    g.setColor(Color.BLUE);
    g.fillRect(x, y, 3, 15);
}

public void update() {
    y += yVel;
    if (y > 800) 
        y = r.nextInt(800);
}

@Override
public void actionPerformed(ActionEvent e) {
        update();
        repaint();
}

I understand if you might be cringing hard right now (I'm fairly new to graphics coding and mostly familiar with Java itself). All i'm getting drawn currently is a single rain drop. Any suggestions are appreciated.


Solution

    • Don't call super.paintComponent from within paint, you're breaking the paint chain which could cause no end of issues. Override paintComponent directly instead
    • You shouldn't be modifying the state of a component or anything the component relies on from within any paint method, paint can be called a number of times in quick succession and this can cause no end of issues
    • Component based animation is not a simple task and unless you really, really need it, you should try and avoid it. Instead, write a class which is "paintable", which you can call from your paintComponent method

    For example..

    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.geom.Rectangle2D;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Random;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.Timer;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    
    public class RainDropsKeepFalling {
    
        public static void main(String[] args) {
            new RainDropsKeepFalling();
        }
    
        public RainDropsKeepFalling() {
            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 RainPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public class RainPane extends JPanel {
    
            private List<Drop> drops = new ArrayList<>(100);
    
            public RainPane() {
                for (int index = 0; index < 100; index++) {
                    drops.add(new Drop(getPreferredSize()));
                }
    
                Timer timer = new Timer(5, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        for (Drop drop : drops) {
                            drop.update(getSize());
                            repaint();
                        }
                    }
                });
                timer.start();
            }
    
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(200, 200);
            }
    
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                for (Drop drop : drops) {
                    Graphics2D g2d = (Graphics2D) g.create();
                    drop.paint(g2d);
                    g2d.dispose();
                }
            }
    
        }
    
        protected static final Random random = new Random();
    
        public static class Drop {
    
            private double vDelta = random.nextDouble() + 0.5;
            private int height = 15;
            private int width = 3;
            private double x;
            private double y = -height;
    
            private Rectangle2D shape;
    
            public Drop(Dimension size) {
                x = random.nextInt(size.width - width) + width;
                y = random.nextInt(size.height - height) + height;
                shape = new Rectangle2D.Double(x, y, width, height);
            }
    
            public void paint(Graphics2D g2d) {
                g2d.setColor(Color.BLUE);
                g2d.fill(shape);
            }
    
            public void update(Dimension size) {
                y += vDelta;
                if (y > size.height) {
                    y = -height;
                    x = random.nextInt(size.width - width) + width;
                }
                shape.setRect(x, y, width, height);
            }
    
        }
    
    }