Mo2
Mo2

Reputation: 1131

Multiple Animations via InvokeLater - Java

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

Answers (1)

Paul Samsotha
Paul Samsotha

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?"

  1. You have to add multiple components to one visible "painting surface". So basically you will have to layer these component to the surface, having to deal with component opacity, among other things.
  2. You use the javax.swing.Timer, in which you will loop through the List and call each ball's animate() method.
  3. NO.

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

Related Questions