OK, so if you really want to go for the nanosecond, at the end of this answer is a way to do it using a ScheduledThreadPool. But this is just pure madness, this may lead to tons of problems and the result is disappointing. I would really not go down that road.
With 50Hz (ie, 50 refresh per second) you should be able to achieve a decent result. The thing is that I would rather drop the assumption that you can move only pixel per pixel and link your ball speed to your move increases.
Here is an example (just drag the slider to see the result):
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.MalformedURLException;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class TestAnimation2 {
private static final int NB_OF_IMAGES_PER_SECOND = 50;
private static final int WIDTH = 800;
private static final int HEIGHT = 600;
private static final int MIN = 0;
private static final int MAX = 100;
private double speed = convert(50);
private double dx;
private double dy;
private double x = WIDTH / 2;
private double y = HEIGHT / 2;
private JFrame frame;
private CirclePanel circle;
private Runnable job;
private long lastMove = System.currentTimeMillis();
protected void initUI() throws MalformedURLException {
frame = new JFrame(TestAnimation2.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(null);
circle = new CirclePanel();
circle.setSize(20, 20);
frame.add(circle);
frame.setSize(WIDTH, HEIGHT);
dx = speed;
dy = speed;
final JSlider slider = new JSlider(MIN, MAX);
slider.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
speed = convert(slider.getValue());
if (dx > 0) {
dx = speed;
} else {
dx = -speed;
}
if (dy > 0) {
dy = speed;
} else {
dy = -speed;
}
}
});
slider.setValue(50);
slider.setLocation(0, 0);
slider.setSize(slider.getPreferredSize());
frame.add(slider);
frame.setVisible(true);
Timer t = new Timer(1000 / NB_OF_IMAGES_PER_SECOND, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
move();
}
});
t.start();
}
protected double convert(double sliderValue) {
return sliderValue + 1;
}
protected void move() {
x += dx;
y += dy;
if (x + circle.getWidth() > frame.getContentPane().getWidth()) {
x = frame.getContentPane().getWidth() - circle.getWidth();
dx = -speed;
} else if (x < 0) {
x = 0;
dx = speed;
}
if (y + circle.getHeight() > frame.getContentPane().getHeight()) {
y = frame.getContentPane().getHeight() - circle.getHeight();
dy = -speed;
} else if (y < 0) {
y = 0;
dy = speed;
}
circle.setLocation((int) x, (int) y);
circle.repaint();
}
public static class CirclePanel extends JPanel {
public CirclePanel() {
super();
setOpaque(false);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.RED);
g.fillOval(0, 0, getWidth(), getHeight());
}
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException,
UnsupportedLookAndFeelException {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
try {
new TestAnimation2().initUI();
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
});
}
}
Here is an example using a scheduled thread pool (very dangerous and disappointing)
import java.awt.Color;
import java.awt.Graphics;
import java.net.MalformedURLException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class TestAnimation2 {
private static final int WIDTH = 800;
private static final int HEIGHT = 600;
private static final int SLOWEST_RATE = 10000000;
private static final int FASTEST_RATE = 1000;
private static final int RANGE = SLOWEST_RATE - FASTEST_RATE;
private static final int MIN = 0;
private static final int MAX = 100;
private double dx;
private double dy;
private double x = WIDTH / 2;
private double y = HEIGHT / 2;
private volatile long delay = convert(50);
private JFrame frame;
private CirclePanel circle;
private Runnable job;
private long lastMove = System.currentTimeMillis();
protected void initUI() throws MalformedURLException {
frame = new JFrame(TestAnimation2.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(null);
circle = new CirclePanel();
circle.setSize(20, 20);
frame.add(circle);
frame.setSize(WIDTH, HEIGHT);
dx = 1;
dy = 1;
final ScheduledExecutorService sheduledThreadPool = Executors.newScheduledThreadPool(1);
final JSlider slider = new JSlider(MIN, MAX);
slider.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
delay = convert(slider.getValue());
}
});
slider.setValue(50);
slider.setLocation(0, 0);
slider.setSize(slider.getPreferredSize());
frame.add(slider);
frame.setVisible(true);
job = new Runnable() {
@Override
public void run() {
move();
sheduledThreadPool.schedule(job, delay, TimeUnit.NANOSECONDS);
}
};
sheduledThreadPool.schedule(job, delay, TimeUnit.NANOSECONDS);
}
protected long convert(float sliderValue) {
return (long) (SLOWEST_RATE - sliderValue / (MAX - MIN) * RANGE);
}
protected void move() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
System.err.println("Ellapsed " + (System.currentTimeMillis() - lastMove) + " delay is " + (double) delay / 1000000 + " ms");
x += dx;
y += dy;
if (x + circle.getWidth() > frame.getContentPane().getWidth()) {
x = frame.getContentPane().getWidth() - circle.getWidth();
dx = -1;
} else if (x < 0) {
x = 0;
dx = 1;
}
if (y + circle.getHeight() > frame.getContentPane().getHeight()) {
y = frame.getContentPane().getHeight() - circle.getHeight();
dy = -1;
} else if (y < 0) {
y = 0;
dy = 1;
}
circle.setLocation((int) x, (int) y);
frame.repaint();
lastMove = System.currentTimeMillis();
}
});
}
public static class CirclePanel extends JPanel {
public CirclePanel() {
super();
setOpaque(false);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.RED);
g.fillOval(0, 0, getWidth(), getHeight());
}
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException,
UnsupportedLookAndFeelException {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
try {
new TestAnimation2().initUI();
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
}