Simple GUI animation in Java

Posted on

Problem

I wrote a simple animation of a ball moving across the screen. I was wondering if I used the static variable correctly. If not, how could I make it non-static so I could instantiate it in the main method whilst being able to access its getter methods in the listener inner class? Also, did I use the SwingUtilities.invokeLater() method properly?

Please let me know of any improvements that I can make.

import java.awt.*;
import javax.swing.*;

public class SimpleAnimation {
    private JFrame frame;
    private int x = 5;
    private int y = 5;

    static SimpleAnimation animation;

    public static void main(String[] args){
        animation = new SimpleAnimation();
        animation.buildGUI();

        // Use i to change co-ordinates x and y to produce a moving ball on the screen.
        for(int i = 0; i <= 300; i+=1){
            animation.setCoordinates(i, i);
            animation.frame.repaint();      // JVM uses new co-ordinates in paintComponent() to repaint the ball
            try{
                Thread.sleep(10);
            }catch(InterruptedException interruptedException){
                interruptedException.printStackTrace();
            }
        }
    }

    public void buildGUI(){
        frame = new JFrame("Simple animation");
        SwingUtilities.invokeLater(
                new Runnable() {
                    @Override
                    public void run() {
                        frame.setContentPane(new DrawPanel(new BorderLayout()));
                        frame.setSize(400, 500);
                        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
                        frame.setLocationRelativeTo(null);
                        frame.setVisible(true);
                    }
                }
        );

    }

    // GETTERS
    int getX(){
        return x;
    }

    int getY(){
        return y;
    }

    // SETTERS
    void setCoordinates(int newX, int newY){
        x = newX;
        y = newY;
    }

    class DrawPanel extends JPanel{
        public DrawPanel(LayoutManager layout){
            super(layout);
        }

        @Override
        public void paintComponent(Graphics g){
            g.setColor(Color.ORANGE);
            g.fillOval(animation.getX(), animation.getY(), 100, 100);
        }
    }
}

Solution

I was wondering if I used the static variable correctly? If not, how could I make it non-static so I could instantiate it in the main method whilst being able to access its getter methods in the listener inner class?

I would prefer to not use a static variable for this, which you could easily achieve by passing the animation object to the panel:

public class AnimationPanel extends JPanel {

    private SimpleAnimation animation;

    public AnimationPanel(LayoutManager layout, SimpleAnimation animation) {
        super(layout);
        this.animation = animation;
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(Color.ORANGE);
        g.fillOval(animation.getX(), animation.getY(), 100, 100);
    }
}

With a simple animation class (in a real project, setcoordinates would more likely be updateCoordinates, as the animation model should contain its logic itself, ie it should know what to animate, instead of having the main class/controller manage this. the main class would just be a loop constantly calling updateCoordinates on the model, and repaint on the view):

public class SimpleAnimation {
    private int x = 5;
    private int y = 5;

    int getX(){
        return x;
    }

    int getY(){
        return y;
    }

    void setCoordinates(int newX, int newY){
        x = newX;
        y = newY;
    }
}

And then use it in your main like this:

public class AnimationMain {

    public static void main(String[] args) {
        SimpleAnimation animation = new SimpleAnimation();
        AnimationPanel animationPanel = new AnimationPanel(new BorderLayout(), animation);

        buildGUI(animation, animationPanel);

        for (int i = 0; i <= 300; i += 1) {
            animation.setCoordinates(i, i);
            animationPanel.repaint();
            try {
                Thread.sleep(10);
            } catch (InterruptedException interruptedException) {
                interruptedException.printStackTrace();
            }
        }
    }

    public static void buildGUI(SimpleAnimation animation, AnimationPanel animationPanel) {
        JFrame frame = new JFrame("Simple animation");
        SwingUtilities.invokeLater(
                new Runnable() {
                    @Override
                    public void run() {
                        frame.setContentPane(animationPanel);
                        frame.setSize(400, 500);
                        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
                        frame.setLocationRelativeTo(null);
                        frame.setVisible(true);
                    }
                }
        );
    }
}

The main reason I would prefer this approach is that it follows the MVC approach (separating the code by model (SimpleAnimation), view (AnimationPanel), and controller (AnimationMain)). This seems a lot more flexible (with your approach, I even have problems extracting the main method to its own class, for example).

Misc

  • don’t import *, instead make all your imports explicit, so readers know what classes you use.
  • your methods should be explicitly declared as private or public.
  • your spacing is sometimes off (eg i+=1; it should be i += 1, or the more customary i++).

Leave a Reply

Your email address will not be published. Required fields are marked *