The way I do 2D animations is as follows:
Sprite Retrieval Class
This class can either load an image file or it can load a sprite from a sprite sheet.
It's very self explanatory.
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class Sprite {
private static BufferedImage spriteSheet;
private static final int TILE_SIZE = 32;
public static BufferedImage loadSprite(String file) {
BufferedImage sprite = null;
try {
sprite = ImageIO.read(new File("res/" + file + ".png"));
} catch (IOException e) {
e.printStackTrace();
}
return sprite;
}
public static BufferedImage getSprite(int xGrid, int yGrid) {
if (spriteSheet == null) {
spriteSheet = loadSprite("AnimationSpriteSheet");
}
return spriteSheet.getSubimage(xGrid * TILE_SIZE, yGrid * TILE_SIZE, TILE_SIZE, TILE_SIZE);
}
}
Animation Class
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
public class Animation {
private int frameCount; // Counts ticks for change
private int frameDelay; // frame delay 1-12 (You will have to play around with this)
private int currentFrame; // animations current frame
private int animationDirection; // animation direction (i.e counting forward or backward)
private int totalFrames; // total amount of frames for your animation
private boolean stopped; // has animations stopped
private List<Frame> frames = new ArrayList<Frame>(); // Arraylist of frames
public Animation(BufferedImage[] frames, int frameDelay) {
this.frameDelay = frameDelay;
this.stopped = true;
for (int i = 0; i < frames.length; i++) {
addFrame(frames[i], frameDelay);
}
this.frameCount = 0;
this.frameDelay = frameDelay;
this.currentFrame = 0;
this.animationDirection = 1;
this.totalFrames = this.frames.size();
}
public void start() {
if (!stopped) {
return;
}
if (frames.size() == 0) {
return;
}
stopped = false;
}
public void stop() {
if (frames.size() == 0) {
return;
}
stopped = true;
}
public void restart() {
if (frames.size() == 0) {
return;
}
stopped = false;
currentFrame = 0;
}
public void reset() {
this.stopped = true;
this.frameCount = 0;
this.currentFrame = 0;
}
private void addFrame(BufferedImage frame, int duration) {
if (duration <= 0) {
System.err.println("Invalid duration: " + duration);
throw new RuntimeException("Invalid duration: " + duration);
}
frames.add(new Frame(frame, duration));
currentFrame = 0;
}
public BufferedImage getSprite() {
return frames.get(currentFrame).getFrame();
}
public void update() {
if (!stopped) {
frameCount++;
if (frameCount > frameDelay) {
frameCount = 0;
currentFrame += animationDirection;
if (currentFrame > totalFrames - 1) {
currentFrame = 0;
}
else if (currentFrame < 0) {
currentFrame = totalFrames - 1;
}
}
}
}
}
The animation class has start, stop, restart and reset to control the animation. You could easily add a boolean to test whether it should loop or not. I will not add this for you.
Frame Class
import java.awt.image.BufferedImage;
public class Frame {
private BufferedImage frame;
private int duration;
public Frame(BufferedImage frame, int duration) {
this.frame = frame;
this.duration = duration;
}
public BufferedImage getFrame() {
return frame;
}
public void setFrame(BufferedImage frame) {
this.frame = frame;
}
public int getDuration() {
return duration;
}
public void setDuration(int duration) {
this.duration = duration;
}
}
The Frame class holds an image and a duration associated with that image.
Implementation
The way I implement the animation is as follows:
Player.java
// Images for each animation
private BufferedImage[] walkingLeft = {Sprite.getSprite(0, 1), Sprite.getSprite(2, 1}; // Gets the upper left images of my sprite sheet
private BufferedImage[] walkingRight = {Sprite.getSprite(0, 2), Sprite.getSprite(2, 2};
private BufferedImage[] standing = {Sprite.getSprite(1, 0)};
// These are animation states
private Animation walkLeft = new Animation(walkingLeft, 10);
private Animation walkRight = new Animation(walkingRight, 10);
private Animation standing = new Animation(standing, 10);
// This is the actual animation
private Animation animation = standing;
In your update or tick method you will have:
animation.update();
In your render or draw method you will have:
g.drawImage(animation.getSprite(), x, y, null);
Lets say you have implemented MouseListener
public void mousePressed(MouseEvent e) {
animation = walkLeft;
animation.start();
}
public void mouseReleased(MouseEvent e) {
animation.stop();
animation.reset();
animation = standing;
}
Closing Notes
Here is a screen shot of the layout of a sprite sheet.

Each tile is 32 x 32 pixels.
Normally I wouldn't write this much code. However, I remember when I was struggling to figure this out and it annoyed the shit out of me... So here you go!
Good luck and I hope you can refactor it to suit your needs :)