Reputation: 1131
I'm trying to make a GUI where it has essentially has multiple bouncing balls. The balls are added via a JButton. I was able to make the Ball class successfully and have it animate on the screen with only one ball, however I was not able to get multiple balls added via the use of the button. I have tried making an ActionListener which creates new threads and calls on SwingUtilities.InvokeLater, but it only made the GUI freeze. I have tried following this guide on how to use InvokeLater: http://www.javamex.com/tutorials/threads/invokelater.shtml
Here is my code so far. Any help is appreciated. (I realize that the bouncing ball question has been asked here before, but I was not able to understand the methods explained in the replies and thought I would ask here rather than comment on a 2 year old question)
Ball Class
package BouncingBall;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.Random;
public class Ball extends JComponent
{
private double dx;
private double dy;
private double x;
private double y;
private Color color;
private Random rand = new Random();
private Ellipse2D.Double ball;
private final static int diam = 10;
private final static int xLim = BallFrame.FRAME_WIDTH-diam;
private final static int yLim = BallFrame.FRAME_HEIGHT-diam*7;
boolean xUpperBound = true;
boolean yUpperBound = true;
public Ball()
{
color = new Color(rand.nextFloat(),rand.nextFloat(),rand.nextFloat());
x = rand.nextInt(xLim);
y = rand.nextInt(yLim);
dx = rand.nextInt(9)+1;
dy = rand.nextInt(9)+1;
}
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
draw(g2);
}
public void draw(Graphics2D g2)
{
ball = new Ellipse2D.Double(x,y,diam,diam);
g2.setColor(color);
g2.fill(ball);
}
public void move()
{
if (((x+dx) < xLim) && xUpperBound)
x+=dx;
else if (x > 0)
{
xUpperBound = false;
x-=dx;
}
else
xUpperBound = true;
if (((y+dy) < yLim) && yUpperBound)
y+=dy;
else if (y > 0)
{
yUpperBound = false;
y-=dy;
}
else
yUpperBound = true;
}
public void animate()
{
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
try
{
while (true)
{
repaint();
move();
Thread.sleep(40);
}
}
catch (InterruptedException e)
{
System.out.println("Thread was interrupted!");
}
}
});
}
}
BallFrame Class
package BouncingBall;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
public class BallFrame extends JFrame
{
public final static int FRAME_WIDTH = 800;
public final static int FRAME_HEIGHT = 600;
public BallFrame()
{
class ballListener implements ActionListener
{
@Override
public void actionPerformed(ActionEvent actionEvent)
{
Thread thread = new Thread()
{
public void run()
{
Ball temp = new Ball();
temp.animate();
add(temp);
}
};
thread.start();
}
}
setLayout(new BorderLayout());
JButton addBall = new JButton("Add Ball");
ActionListener listener = new ballListener();
addBall.addActionListener(listener);
add(addBall, BorderLayout.SOUTH);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
BallFrame frame = new BallFrame();
frame.setSize(FRAME_WIDTH, FRAME_HEIGHT );
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.setVisible( true );
}
});
}
}
Upvotes: 0
Views: 790
Reputation: 208984
A problem I already in design, is the fact that you are making Ball
extends JComponent
which already limits you in many ways. You should instead just make Ball
a simple state-holder/state-manipulation class, with a method to animate and draw the ball state.
Then just have one JComponent/JPanel class for the painting of the balls. In that class you can have a List<Ball>
that you will draw in the paintComponent
method. Whenever you want to add a ball, just add a Ball
to the list.
Also, instead of using separate threads, which shouldn't be done with painting (as all painting should be done on the EDT), you should instead use a javax.swing.Timer
for the timing animation. You can see more at How to Use Swing Timers
You can also see a bunch of examples of the above methods here and here and here and here and here and here.
UPDATE
"Can you explain extending JComponent limits it? As for not using threads. How would I have multiple Ball objects animate independently? Doesn't that require multiple threads?"
See the examples from the links I posts. They are have examples of using the timer and a list of objects
Here's the basic idea in some snippets of code
Ball class
public class Ball {
public void draw(Graphics g) {}
public void animate() {}
}
BallPanel class
public class BallPanel extends JPanel {
List<Ball> balls;
public BallPanel() {
Timer timer = new Timer(40, new ActionListener(){
public void actionPerformed(ActionEvent e) {
for (Ball ball : balls) {
ball.animate();
}
repaint();
}
});
timer.start();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Ball ball : ball ) {
ball.draw(g);
}
}
public void addBall(...) {
balls.add(new Ball(..));
}
}
Upvotes: 2