The basic premise for any animation is change over time.
You need to be able to move the ball from position A to position B over a given time period. To do that, you need some kind of "ticker" that can be used to update the position of the ball over that period. Generally speaking, 25fps (or about 40 milliseconds) is more than enough.
To achieve this safely in Swing, the easiest solution is to use a Swing Timer
. You could use a Thread
, but then you become responsible for syncing the updates back to the UI and it's more complexity then is really required at this stage.
This example uses a duration of 1 second to move the ball from point A to point B. This is a linear animation and you would need to investigate an appropriate animation framework to get a more complex solution.
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
public class LinePanel extends JPanel {
// myObject ball;
private Point b1 = new Point(0, 0);
private Point startPoint = new Point(0, 0);
private Point targetPoint = new Point(0, 0);
private MouseHandler mouseHandler = new MouseHandler();
private Point p1 = new Point(100, 100);
private Point p2 = new Point(540, 380);
private boolean drawing;
private Timer animate;
private long startTime;
private int duration = 1000;
public LinePanel() {
this.setPreferredSize(new Dimension(640, 480));
this.addMouseListener(mouseHandler);
this.addMouseMotionListener(mouseHandler);
animate = new Timer(40, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
long now = System.currentTimeMillis();
long dif = now - startTime;
if (dif >= duration) {
dif = duration;
((Timer)e.getSource()).stop();
}
float progress = (float)dif / (float)duration;
b1 = calculateProgress(startPoint, targetPoint, progress);
repaint();
}
});
animate.setRepeats(true);
animate.setCoalesce(true);
}
public void moveBallTo(Point target) {
if (animate.isRunning()) {
animate.stop();
}
startPoint = b1;
targetPoint = target;
startTime = System.currentTimeMillis();
animate.start();
}
public void moveBallBy(int xDelta, int yDelta) {
animate.stop();
Point t = new Point(targetPoint == null ? b1 : targetPoint);
t.x += xDelta;
t.y += yDelta;
moveBallTo(t);
}
public Point calculateProgress(Point startPoint, Point targetPoint, double progress) {
Point point = new Point();
if (startPoint != null && targetPoint != null) {
point.x = calculateProgress(startPoint.x, targetPoint.x, progress);
point.y = calculateProgress(startPoint.y, targetPoint.y, progress);
}
return point;
}
public int calculateProgress(int startValue, int endValue, double fraction) {
int value = 0;
int distance = endValue - startValue;
value = (int)Math.round((double)distance * fraction);
value += startValue;
return value;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.blue);
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(new BasicStroke(8,
BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));
g.drawLine(p1.x, p1.y, p2.x, p2.y);
g.setColor(Color.RED);
g.drawOval(b1.x - 4, b1.y - 4, 8, 8);
// ball = new myObject(b1.x, b1.y, "Stuff/ball.png", 50, 50);
// g.drawImage(ball.getImage(), ball.getX(), ball.getY(), ball.getWidth(), ball.getHeight(), null);
repaint();
}
private class MouseHandler extends MouseAdapter {
@Override
public void mousePressed(MouseEvent e) {
drawing = true;
p1 = e.getPoint();
p2 = p1;
repaint();
}
@Override
public void mouseReleased(MouseEvent e) {
drawing = false;
p2 = e.getPoint();
repaint();
}
@Override
public void mouseDragged(MouseEvent e) {
if (drawing) {
p2 = e.getPoint();
repaint();
}
}
}
private class ControlPanel extends JPanel {
private static final int DELTA = 50;
// above is telling the ball to move by 50 pixels
// I want it to move by 50 pixels but gradually I dont want it to teleport
public ControlPanel() {
this.add(new MoveButton("u2190", KeyEvent.VK_LEFT, -DELTA, 0));
this.add(new MoveButton("u2191", KeyEvent.VK_UP, 0, -DELTA));
this.add(new MoveButton("u2192", KeyEvent.VK_RIGHT, DELTA, 0));
this.add(new MoveButton("u2193", KeyEvent.VK_DOWN, 0, DELTA));
}
private class MoveButton extends JButton {
KeyStroke k;
int myX, myY;
public MoveButton(String name, int code, final int myX, final int myY) {
super(name);
this.k = KeyStroke.getKeyStroke(code, 0);
this.myX = myX;
this.myY = myY;
this.setAction(new AbstractAction(this.getText()) {
@Override
public void actionPerformed(ActionEvent e) {
// LinePanel.this.b1.translate(myX, myY);
moveBallBy(myX, myY);
LinePanel.this.repaint();
}
});
ControlPanel.this.getInputMap(
WHEN_IN_FOCUSED_WINDOW).put(k, k.toString());
ControlPanel.this.getActionMap().put(k.toString(), new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
MoveButton.this.doClick();
}
});
}
}
}
private void display() {
JFrame f = new JFrame("LinePanel");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.add(new ControlPanel(), BorderLayout.SOUTH);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
new LinePanel().display();
}
});
}
}
Take a look at ...
Side note
Doing this ball = new myObject(b1.x, b1.y, "Stuff/ball.png", 50, 50);
in you paint method is incredibly inefficient. paint
may be called a number of times in quick succession. Where ever possible, you want the paint
methods to be as optimized as you can make them, to much time spent in your paint method will make your program look slow