You have a number of very important topics you need to understand
The first would be gaining a better understanding of how painting works in Swing:
Two main concerns crop up in your code:
- Overriding
paint
(and not calling super.paint
). Generally, you are discouraged from overriding paint
directly, instead, preference is given to overriding paintComponent
(and making sure you call super.paintComponent
in order to preserve established paint chain)
- Calling
repaint
from within paint
. This is not a good idea, as it can cause the UI to become saturated with paint requests, increasing the pressure on the CPU and degrading the perform of the program and system.
This leads to some slight modifications that might look something like...
public class Panel extends JPanel {
public static final int drops = 1;
public RainDrop[] d = new RainDrop[drops];
public Panel() {
for (int i = 0; i < drops; i++) {
d[i] = new RainDrop();
}
}
public void update() {
for (int i = 0; i < drops; i++) {
d[i].fall();
}
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (int i = 0; i < drops; i++) {
d[i].draw(g);
repaint();
}
}
}
Next, you need to gain some understanding into how the event dispatching process works and how concurrency should be used within the API.
Start with Concurrency in Swing.
The short version is:
- Swing is single threaded
- Swing is NOT thread safe
This means that, you should never perform any long running or blocking operations from within the context of the Event Dispatching Thread, but also, you should never update the UI or something the UI relies on from outside of the context of the EDT.
Remember, from the painting documentation, Swing uses a passive rendering approach. This means that the Swing API makes decisions about when and what should be painted, you can only make suggestions, and that painting may occur at any time, without your knowledge or input. This makes the API susceptible to thread race conditions and generate weird and hard to replicate paint issues.
This leads to the next step. You need some way to update the state of the rain drop(s) and schedule new paint cycles, all of which must be done in a none blocking manner but which can safely update the state of the UI.
The simplest solution to this is using a Swing Timer
This leads to following, slight, modification to the Panel
class...
public class Panel extends JPanel {
//...
public Panel() {
//...
Timer timer = new Timer(10, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
update();
repaint();
}
});
timer.start();
}
}
Every 10th of a second, the rain drop is updated and new paint cycle is scheduled, thus providing the "core" animation engine.
I'd also recommend some slight modifications to the Runner
class...
public class Runner {
public static void main(String[] args) {
new Runner();
}
public Runner() throws HeadlessException {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame obj = new JFrame();
Panel j = new Panel();
obj.setSize(640, 480);
obj.setDefaultCloseOperation(EXIT_ON_CLOSE);
obj.setTitle("Rain");
obj.add(j);
obj.setLocationRelativeTo(null);
obj.setResizable(false);
obj.setVisible(true);
}
});
}
}
This does a number of things...
- Removes the
extends JFrame
, as you're not actually using it and it just confuses the issue. Also, as a general recommendation, you should avoid extending directly from top level containers, lots of reasons, but basically, it couples your code and makes it inflexible
- Moves the creation of the UI into the context of the Event Dispatching Thread
- Calls
setResiazable
and setVisible
last, as this can have an undesirable affect on the UI when it's displayed (displaying a blank screen) - Swing's layout API is lazy, so unless you tell it, it won't generally update itself.