Swing动画闪烁并使GUI响应缓慢

时间:2017-08-15 01:34:45

标签: java performance swing animation graphics

我正在尝试编写一个简单的程序:按下屏幕上的“开始”按钮后出现弹跳球并开始弹跳。应按“X”关闭程序。

出于某种原因,它运行得非常慢。球在闪烁,我按下“X”程序关闭后我必须等待很长时间。

以下是代码:

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.*;
import javax.swing.*;

public class Bounce
{
    public static void main(String[] args)
    {
        JFrame frame = new BounceFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.show();
    }
}

class BounceFrame extends JFrame
{
    public BounceFrame()
    {
        setSize(WIDTH, HEIGHT);
        setTitle("Bounce");
        Container contentPane = getContentPane();
        canvas = new BallCanvas();
        contentPane.add(canvas, BorderLayout.CENTER);
        JPanel buttonPanel = new JPanel();
        addButton(buttonPanel, "Start", new ActionListener()
        {
            public void actionPerformed(ActionEvent evt)
            {
                addBall();
            }
        });
        contentPane.add(buttonPanel, BorderLayout.SOUTH);
    }
    public void addButton(Container c, String title, ActionListener listener)
    {
        JButton button = new JButton(title);
        c.add(button);
        button.addActionListener(listener);
    }
    public void addBall()
    {
        try
        {
            Ball b = new Ball(canvas);
            canvas.add(b);
            for (int i = 1; i <= 10000; i++)
            {
                b.move();
                Thread.sleep(10);
            }
        }
        catch (InterruptedException exception)
        {
        }
    }
    private BallCanvas canvas;
    public static final int WIDTH = 300;
    public static final int HEIGHT = 200;
}

class BallCanvas extends JPanel
{
    public void add(Ball b)
    {
        balls.add(b);
    }
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D)g;
        for (int i = 0; i < balls.size(); i++)
        {
            Ball b = (Ball)balls.get(i);
            b.draw(g2);
        }
    }
    private ArrayList balls = new ArrayList();
}

class Ball
{
    public Ball(Component c) { canvas = c; }
    public void draw(Graphics2D g2)
    {
        g2.fill(new Ellipse2D.Double(x, y, XSIZE, YSIZE));
    }
    public void move()
    {
        x += dx;
        y += dy;
        if (x < 0)
        {
            x = 0;
            dx = -dx;
        }
        if (x + XSIZE >= canvas.getWidth())
        {
            x = canvas.getWidth() - XSIZE;
            dx = -dx;
        }
        if (y < 0)
        {
            y = 0;
            dy = -dy;
        }
        if (y + YSIZE >= canvas.getHeight())
        {
            y = canvas.getHeight() - YSIZE;
            dy = -dy;
        }
        canvas.paint(canvas.getGraphics());
    }
    private Component canvas;
    private static final int XSIZE = 15;
    private static final int YSIZE = 15;
    private int x = 0;
    private int y = 0;
    private int dx = 2;
    private int dy = 2;
}

3 个答案:

答案 0 :(得分:3)

缓慢来自两个相关问题,一个简单,一个复杂。

问题#1:paintrepaint

来自 JComponent.paint docs

  

由Swing调用以绘制组件。   应用程序不应直接调用paint,而应使用repaint方法安排组件重绘。

所以canvas.paint()末尾的Ball.move行必须去。

你想打电话 Component.repaint 代替... 但只需用paint替换repaint就会发现第二个问题,即可防止球出现。

问题#2:ActionListener

内的动画

理想的ActionListener.actionPerformed方法会更改程序的状态并尽快返回,使用repaint之类的惰性方法让Swing在最方便的时候安排实际工作。

相比之下,您的程序基本上完成了actionPerformed方法中的所有内容,包括 all 动画。

解决方案:A Game Loop

更典型的结构是开始一个 javax.swing.Timer 当你的GUI启动时,让它运行 “永远”, 在每个时钟滴答声中更新模拟的状态。

public BounceFrame()
{
    // Original code here.
    // Then add:
    new javax.swing.Timer(
        10,  // Your timeout from `addBall`.
        new ActionListener()
        {
            public void actionPerformed(final ActionEvent ae)
            {
                canvas.moveBalls();  // See below for this method.
            }
        }
    ).start();
}

在你的情况下,最重要的 (并完全缺失) 国家是 “我们开始了吗?” 位,可以boolean存储为BallCanvas。 这是应该完成所有动画的类,因为它还拥有画布和所有球。

BallCanvas获得一个字段isRunning

private boolean isRunning = false;  // new field

// Added generic type to `balls` --- see below.
private java.util.List<Ball> balls = new ArrayList<Ball>();

...和一个setter方法:

public void setRunning(boolean state)
{
    this.isRunning = state;
}

最后,BallCanvas.moveBalls是新的 “更新所有的东西” Timer调用的方法:

public void moveBalls()
{
    if (! this.isRunning)
    {
        return;
    }
    for (final Ball b : balls)
    {
        // Remember, `move` no longer calls `paint`...  It just
        // updates some numbers.
        b.move();
    }
    // Now that the visible state has changed, ask Swing to
    // schedule repainting the panel.
    repaint();
}

(注意,在balls列表上迭代的次数是多少,现在该列表具有适当的泛型类型。 paintComponent中的循环可以做得很简单。)

现在BounceFrame.addBall方法很简单:

public void addBall()
{
    Ball b = new Ball(canvas);
    canvas.add(b);
    this.canvas.setRunning(true);
}

通过此设置,每次按空格键都会为模拟添加另一个球。 我能够在我的2006台式机上弹出超过100个球而没有一丝闪烁。 此外,我可以使用“X”按钮或Alt-F4退出应用程序,两者都没有在原始版本中作出响应。

如果您发现自己需要更多性能 (或者如果你只是想更好地理解Swing绘画是如何工作的), 看到 “Painting in AWT and Swing: 良好的绘画代码是应用程序性能的关键“ 作者:Amy Fowler。

答案 1 :(得分:0)

我建议你使用&#39;定时器&#39;用于运行gameloop的类。它无限运行,你可以随时使用timer.stop()停止它 您也可以相应地设置其速度。

答案 2 :(得分:-2)

您应该像这样更改addBall()

public void addBall() {
    Ball b = new Ball(canvas);
    canvas.add(b);
    new Thread(() -> {
        for (int i = 1; i <= 10000; i++) {
            EventQueue.invokeLater(() -> b.move());
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }).start();
}