Reputation: 2135
I have a simple Java/Swing application that tries to animate a box by moving it from left to right:
public class TestFrame extends JFrame {
protected long startTime = new Date().getTime();
public class MainPanel extends JPanel {
@Override
protected void paintComponent(Graphics g) {
// calculate elapsed time
long currentTime = new Date().getTime();
long timeDiff = currentTime - TestFrame.this.startTime;
// animation time dependent
g.fillRect((int) (timeDiff / 100), 10, 10, 10);
}
}
public class MainLoop implements Runnable {
@Override
public void run() {
while (true) {
// trigger repaint
TestFrame.this.repaint();
}
}
}
public static void main(String[] args) {
new TestFrame();
}
public TestFrame() {
// init window
this.setTitle("Test");
this.setSize(500, 500);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.add(new MainPanel());
this.setVisible(true);
// start render loop
Thread loop = new Thread(new MainLoop());
loop.start();
}
}
The problem is that the animation is not clean and the box jumps (sometimes) a few pixels. I already did some researches and according to them it should work fine if using paintComponent (instead of paint) and doing a time based animation (not frame based). I did both but the animation is still not clean.
Could anybody give me a hint what is going wrong?
Upvotes: 1
Views: 1169
Reputation: 51445
I made a few changes to your code.
I called the SwingUtilities invokeLater method to create and use your Swing components on the Event Dispatch thread.
I called the System currentTimeinMillis method to get the current time.
Instead of setting the JFrame size, I set the size of the JPanel and packed the JFrame. I reduced the size of the JPanel to speed up the repainting.
I added a delay in the while(true) loop, as fjf2002 suggested in his answer.
Here's the revised and formatted code:
package com.ggl.testing;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class TestFrame extends JFrame {
private static final long serialVersionUID = 272649179566160531L;
protected long startTime = System.currentTimeMillis();
public class MainPanel extends JPanel {
private static final long serialVersionUID = 5312139184947337026L;
public MainPanel() {
this.setPreferredSize(new Dimension(500, 30));
}
@Override
protected void paintComponent(Graphics g) {
// calculate elapsed time
long currentTime = System.currentTimeMillis();
long timeDiff = currentTime - TestFrame.this.startTime;
// animation time dependent
g.fillRect((int) (timeDiff / 100), 10, 10, 10);
}
}
public class MainLoop implements Runnable {
@Override
public void run() {
while (true) {
// trigger repaint
TestFrame.this.repaint();
try {
Thread.sleep(20L);
} catch (InterruptedException e) {
}
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new TestFrame();
}
});
}
public TestFrame() {
// init window
this.setTitle("Test");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.add(new MainPanel());
this.pack();
this.setVisible(true);
// start render loop
Thread loop = new Thread(new MainLoop());
loop.start();
}
}
Upvotes: 1
Reputation: 5496
Your painting on my machine is rather smooth but it is burning a lot of performance in that loop, but on a slower machine I could imagine that the animation is jumpy if your application is busy executing the while or handling the paint events instead of rendering.
It may be better to update the position based on how much time has elapsed per render, and I am unsure how accurate it is to compare times for animation purposes through Date
objects, so comparing small time differences using System.nanoTime()
may be better. For example:
long currentNanoTime = System.nanoTime();
long timeElapsed = currentNanoTime - lastUpdateNanoTime;
lastUpdateNanoTime = currentNanoTime;
...
int newXPosition = oldXPosition + (velocityXInPixelsPerNanoSecond * timeElapsed);
Upvotes: 0
Reputation: 882
You should give your while-true-loop a little rest. You're kind of burning your CPU! You're generating a tremendous amount of paint events; at some time which the thread scheduler decides, the scheduler hands off to the event dispatching thread, which as far as I recall may collapse your trillon of paint events into a single one and eventually execute paintComponent.
In the following example, the thread sleeps 20ms, which gives you a maximum frame rate of 50fps. That should be enough.
while (true) {
// trigger repaint
TestFrame.this.repaint();
try {
Thread.sleep(20);
} catch(InterruptedException exc() {
}
}
Upvotes: 2