尽管使用了 paintComponent 和动画线程,但使用 Java/Swing 的动画仍然不稳定
Animation using Java/Swing is jumpy although using paintComponent and animation thread
我有一个简单的 Java/Swing 应用程序,它试图通过从左向右移动来为盒子设置动画:
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();
}
}
问题是动画不干净,盒子跳动(有时)几个像素。我已经做了一些研究,根据他们的说法,如果使用 paintComponent(而不是 paint)并进行基于时间的动画(而不是基于帧),它应该可以正常工作。我都做了但是动画还是不干净
谁能告诉我哪里出了问题?
你应该让你的 while-true-loop 稍微休息一下。你有点烧 CPU!您正在生成大量的绘画事件;在线程调度程序决定的某个时间,调度程序将交给事件调度线程,据我所知,这可能会将你的万亿绘画事件合并为一个事件,并最终执行 paintComponent。
在下面的示例中,线程休眠 20 毫秒,这为您提供了 50fps 的最大帧速率。应该够了。
while (true) {
// trigger repaint
TestFrame.this.repaint();
try {
Thread.sleep(20);
} catch(InterruptedException exc() {
}
}
你在我机器上的绘画相当流畅,但它在那个循环中消耗了很多性能,但在较慢的机器上,我可以想象如果你的应用程序忙于执行 while 或处理绘画,动画会跳动事件而不是渲染。
根据每次渲染经过的时间来更新位置可能会更好,而且我不确定通过 Date
对象比较动画目的的时间有多准确,所以比较小的时间差异使用 System.nanoTime()
可能会更好。例如:
long currentNanoTime = System.nanoTime();
long timeElapsed = currentNanoTime - lastUpdateNanoTime;
lastUpdateNanoTime = currentNanoTime;
...
int newXPosition = oldXPosition + (velocityXInPixelsPerNanoSecond * timeElapsed);
我对你的代码做了一些修改。
我调用了 SwingUtilities invokeLater 方法在 Event Dispatch thread.
上创建和使用您的 Swing 组件
我调用了系统的currentTimeinMillis方法来获取当前时间
我没有设置 JFrame 的大小,而是设置了 JPanel 的大小并打包了 JFrame。我减小了 JPanel 的大小以加快重绘速度。
我在 while(true) 循环中添加了延迟,正如 fjf2002 在他的回答中所建议的那样。
这是经过修改和格式化的代码:
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();
}
}
我有一个简单的 Java/Swing 应用程序,它试图通过从左向右移动来为盒子设置动画:
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();
}
}
问题是动画不干净,盒子跳动(有时)几个像素。我已经做了一些研究,根据他们的说法,如果使用 paintComponent(而不是 paint)并进行基于时间的动画(而不是基于帧),它应该可以正常工作。我都做了但是动画还是不干净
谁能告诉我哪里出了问题?
你应该让你的 while-true-loop 稍微休息一下。你有点烧 CPU!您正在生成大量的绘画事件;在线程调度程序决定的某个时间,调度程序将交给事件调度线程,据我所知,这可能会将你的万亿绘画事件合并为一个事件,并最终执行 paintComponent。
在下面的示例中,线程休眠 20 毫秒,这为您提供了 50fps 的最大帧速率。应该够了。
while (true) {
// trigger repaint
TestFrame.this.repaint();
try {
Thread.sleep(20);
} catch(InterruptedException exc() {
}
}
你在我机器上的绘画相当流畅,但它在那个循环中消耗了很多性能,但在较慢的机器上,我可以想象如果你的应用程序忙于执行 while 或处理绘画,动画会跳动事件而不是渲染。
根据每次渲染经过的时间来更新位置可能会更好,而且我不确定通过 Date
对象比较动画目的的时间有多准确,所以比较小的时间差异使用 System.nanoTime()
可能会更好。例如:
long currentNanoTime = System.nanoTime();
long timeElapsed = currentNanoTime - lastUpdateNanoTime;
lastUpdateNanoTime = currentNanoTime;
...
int newXPosition = oldXPosition + (velocityXInPixelsPerNanoSecond * timeElapsed);
我对你的代码做了一些修改。
我调用了 SwingUtilities invokeLater 方法在 Event Dispatch thread.
上创建和使用您的 Swing 组件
我调用了系统的currentTimeinMillis方法来获取当前时间
我没有设置 JFrame 的大小,而是设置了 JPanel 的大小并打包了 JFrame。我减小了 JPanel 的大小以加快重绘速度。
我在 while(true) 循环中添加了延迟,正如 fjf2002 在他的回答中所建议的那样。
这是经过修改和格式化的代码:
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();
}
}