Animate Image in Java2d

2.5k views Asked by At

im learning Java2d, and im trying to animate my image in x coordinate using a Timer, but is not working, the idea is between a time frame the image x value increments a value making it to move, can someone figure out what is the problem in my code?

Here is the code:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Iterator;
import java.util.Timer;
import java.util.TimerTask;

import javax.imageio.ImageIO;
import javax.swing.JPanel;

public class Screen extends JPanel {
    int posX;
    Timer timer;
    private BufferedImage image;

    public Screen() {
        setDoubleBuffered(true);
        posX = 1;
        timer = new Timer();
        timer.scheduleAtFixedRate(new Anima(), 100, 10);

        //Smile Icon
        try{
            image = ImageIO.read(getClass().getResource("/smily.png"));
        }catch(IOException e){

            e.printStackTrace();        
        }  

    }
    public void paint(Graphics g) {
        super.paint(g);
        Graphics2D g2d = (Graphics2D) g;
        g2d.drawImage(image,this.posX,100,null);


    }

    class Anima extends TimerTask{

        public void run() {

            posX += 20;
            repaint();      

        }

    }

    public void incMove() {
        posX += 20;
    }

}
2

There are 2 answers

0
Alex S. Diaz On BEST ANSWER

Your code is working, but you are updating too fast.

Try

timer.scheduleAtFixedRate(new Anima(), 100, 1000);

To check your animation.

0
MadProgrammer On

Two main things:

  1. The delay is small
  2. The change is large

This means that the object can be moved out side of the visible bounds quickly, usually quicker then the screen can be realized on the screen.

You could play with the timing, but, animation is the illusion of change over time, you may find it better to reduce the size of the change rather the then the delay (although 100fps might be asking a bit much ;))

There is also no bounds checking, so the object is free to move off the viewable area, this might be desirable, but probably would have at least hinted towards the problem you were having.

As Swing is not thread safe (you shouldn't update the UI from outside the context of the EDT), you are also running the risk of inuring a thread race condition. In this example, it would probably be very hard to do, but the concept of how you are changing the state is dangerous.

Because Swing uses a passive rendering engine, a paint cycle may occur at anytime, without your interaction or knowledge, so you should be careful updating variables which the paint methods need to render the state outside of the context of the EDT.

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Screen extends JPanel {

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new Screen());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    private int posX;
    private int delta = 2;

    public Screen() {
        setDoubleBuffered(true);
        posX = 1;

        Timer timer = new Timer(10, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                incMove();
                repaint();
            }
        });
        timer.start();

    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g); //To change body of generated methods, choose Tools | Templates.
        Graphics2D g2d = (Graphics2D) g;
        g2d.drawRect(this.posX, 100, 10, 10);
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(200, 200);
    }

    public void incMove() {
        posX += delta;
        if (posX + 10 > getWidth()) {
            posX = getWidth() - 10;
            delta *= -1;
        } else if (posX < 0) {
            posX = 0;
            delta *= -1;
        }
    }

}

This simple example uses a Swing Timer as the primary engine, it reduces the amount of change (to 2) as well as adds bounds checking

Take a look at Concurrency in Swing and How to use Swing Timers for more details