【问题标题】:Animation using Java/Swing is jumpy although using paintComponent and animation thread尽管使用了paintComponent和动画线程,但使用Java/Swing的动画很跳跃
【发布时间】:2015-02-25 05:16:58
【问题描述】:

我有一个简单的 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)并进行基于时间的动画(不是基于帧),它应该可以正常工作。我都做了,但动画仍然不干净。

谁能给我提示一下出了什么问题?

【问题讨论】:

标签: java swing animation


【解决方案1】:

你应该让你的 while-true-loop 休息一下。你有点烧你的CPU!你正在产生大量的绘画事件;在线程调度程序决定的某个时间,调度程序将移交给事件调度线程,据我所知,这可能会将您的一万亿绘制事件合并为一个并最终执行绘制组件。

在以下示例中,线程休眠 20 毫秒,这为您提供了 50 fps 的最大帧速率。应该够了。

while (true) {
    // trigger repaint
    TestFrame.this.repaint();

    try {
        Thread.sleep(20);
    } catch(InterruptedException exc() {
    }
}

【讨论】:

  • @MadProgrammer:嗯,让我想想。一帧的时间乘以 fps 得到的常数是 1 秒。如果时间增加,fps 会降低。我认为你错了。
【解决方案2】:

我对您的代码做了一些更改。

  1. 我调用了 SwingUtilities 的 invokeLater 方法来在 Event Dispatch thread 上创建和使用您的 Swing 组件。

  2. 我调用了 System currentTimeinMillis 方法来获取当前时间。

  3. 我没有设置 JFrame 的大小,而是设置了 JPanel 的大小并打包了 JFrame。我减小了 JPanel 的大小以加快重绘速度。

  4. 我在 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();
    }
}

【讨论】:

    【解决方案3】:

    您在我的机器上的绘画相当流畅,但它在该循环中消耗了很多性能,但在较慢的机器上,我可以想象如果您的应用程序忙于执行 while 或处理绘画事件,动画会跳动渲染。

    根据每次渲染经过的时间更新位置可能会更好,我不确定通过Date 对象比较动画目的的时间有多准确,因此使用System.nanoTime() 比较小的时间差异可能更好。例如:

    long currentNanoTime = System.nanoTime();
    long timeElapsed = currentNanoTime - lastUpdateNanoTime;
    lastUpdateNanoTime = currentNanoTime;
    ...
    int newXPosition = oldXPosition + (velocityXInPixelsPerNanoSecond * timeElapsed);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-27
      • 1970-01-01
      相关资源
      最近更新 更多