Una soluzione è quella che ti ho già proposto. Posso farti un esempio:
Codice:
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
import java.util.LinkedList;
import java.util.Random;
public class Main {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
new Main().execute();
}
});
}
private void execute() {
final int WIDTH = 500;
final int HEIGHT = 500;
final long DURATIONMS = 10000;
final int RESOLUTIONMS = 25;
final PCanvas CANVAS = new PCanvas();
final Parametric PARAMETRIC = new Sinusoid(CANVAS, WIDTH, HEIGHT);
final Generator GENERATOR = new Generator(PARAMETRIC, DURATIONMS, RESOLUTIONMS);
final JFrame WINDOW = new JFrame("Test");
final JButton START = new JButton(new AbstractAction("Start") {
public void actionPerformed(ActionEvent e) {
GENERATOR.start();
}
});
CANVAS.getComponent().setPreferredSize(new Dimension(WIDTH, HEIGHT));
CANVAS.getComponent().setBackground(Color.WHITE);
WINDOW.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
WINDOW.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
GENERATOR.stop();
}
});
WINDOW.add(CANVAS.getComponent());
WINDOW.add(START, BorderLayout.SOUTH);
WINDOW.pack();
WINDOW.setVisible(true);
}
}
class Sinusoid implements Parametric {
private final Random RANDOM = new Random(0);
private final PCanvas CANVAS;
private final int WIDTH;
private final int HEIGHT;
public Sinusoid(PCanvas canvas, int width, int height) {
CANVAS = canvas;
WIDTH = width;
HEIGHT = height;
}
public void apply(Double t) {
final double Y = HEIGHT / 2 * Math.sin(t * 2 * Math.PI) + HEIGHT / 2;
final double X = WIDTH * t; //(0 ~ WIDTH)
final double RAY = 5;
final Color FILL = new Color(RANDOM.nextInt(255), RANDOM.nextInt(255), RANDOM.nextInt(255));
final Bubble BUBBLE = new Bubble(X, Y, RAY, FILL);
CANVAS.push(BUBBLE);
}
}
class Generator {
private final TimeLine TIMELINE = new TimeLine();
private final Parametric FUNCTION;
private final javax.swing.Timer TIMER;
private final long DURATION;
public Generator(Parametric fun, long durationMs, int resolutionMs) {
FUNCTION = fun;
DURATION = durationMs;
TIMER = new javax.swing.Timer(resolutionMs, new ActionListener() {
public void actionPerformed(ActionEvent e) {
applyParametric();
}
});
}
private void applyParametric() {
double t = (double)TIMELINE.get() / (double) DURATION;
TIMELINE.update();
if(t >= 1) {
t = 1;
FUNCTION.apply(t);
stop();
} else {
FUNCTION.apply(t);
}
}
public void start() {
TIMELINE.start();
TIMER.restart();
}
public void stop() {
TIMER.stop();
}
}
class TimeLine {
private final long MILLION = 1000000;
private long then;
private long time;
public void start() {
then = System.nanoTime();
time = 0;
}
public void update() {
long now = System.nanoTime();
long dTimeNanos = now - then;
then = now;
time += dTimeNanos / MILLION;
}
public long get() {
return time;
}
}
interface Parametric {
public void apply(Double t);
}
interface Paintable {
void paint(Graphics g);
}
class PCanvas {
private final LinkedList<Paintable> PAINTABLES = new LinkedList<Paintable>();
private final JPanel PANEL = new JPanel() {
@Override
protected void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
for(Paintable p : PAINTABLES) {
p.paint(graphics);
}
}
};
public void push(Paintable p) {
PAINTABLES.push(p);
PANEL.repaint();
}
public void clear() {
PAINTABLES.clear();
PANEL.repaint();
}
public Component getComponent() {
return PANEL;
}
}
class Bubble implements Paintable {
private final Color FILL;
private final Shape SHAPE;
public Bubble(double x, double y, double ray, Color fill) {
FILL = fill;
SHAPE = new Ellipse2D.Double(x - ray, y - ray, 2 * ray, 2 * ray);
}
public void paint(Graphics g) {
Graphics2D graphics = (Graphics2D) g;
graphics.setPaint(FILL);
graphics.fill(SHAPE);
}
}
Comunque il problema del tuo codice è che esprime un'idea di disegno che presuppone un Graphics "stateful" mentre Graphics è "stateless". Detto altrimenti lo stato di Graphics viene azzerato tra due invocazioni di repaint successive. Così se disegni una bolla in 10, 10 e la volta seguente disegni una bolla in 20, 20 a video appare una sola bolla, in 20,20. Se vuoi farle apparire tutte e due la prima volta disegni la bolla (10,10), la seconda volta disegni la bolla (10,10) e la bolla (20,20).