Java Swing动画闪烁,使GUI响应缓慢

Java Swing动画闪烁,使GUI响应缓慢,java,performance,swing,animation,graphics,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 st

我正在尝试编写一个简单的程序:一个弹跳球,在你按下屏幕上的“开始”按钮后出现并开始弹跳。应按“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;
}
import java.awt.*;
导入java.awt.event.*;
导入java.awt.geom.*;
导入java.util.*;
导入javax.swing.*;
公务舱弹跳
{
公共静态void main(字符串[]args)
{
JFrame frame=新的BounceFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.show();
}
}
类BounceFrame扩展了JFrame
{
公共BounceFrame()
{
设置尺寸(宽度、高度);
设定标题(“反弹”);
容器contentPane=getContentPane();
画布=新的BallCanvas();
添加(画布,BorderLayout.CENTER);
JPanel buttonPanel=新的JPanel();
addButton(buttonPanel,“开始”,新建ActionListener()
{
已执行的公共无效操作(操作事件evt)
{
addBall();
}
});
添加(buttonPanel,BorderLayout.SOUTH);
}
公共void addButton(容器c、字符串标题、ActionListener侦听器)
{
JButton按钮=新JButton(标题);
c、 添加(按钮);
addActionListener(listener);
}
公共无效addBall()
{
尝试
{
球b=新球(画布);
13.添加(b);
for(int i=1;i=canvas.getWidth())
{
x=canvas.getWidth()-XSIZE;
dx=-dx;
}
if(y<0)
{
y=0;
dy=-dy;
}
如果(y+YSIZE>=canvas.getHeight())
{
y=canvas.getHeight()-YSIZE;
dy=-dy;
}
paint(canvas.getGraphics());
}
私有组件画布;
私有静态最终int XSIZE=15;
专用静态最终物理尺寸=15;
私有整数x=0;
私有整数y=0;
私有整数dx=2;
私有int dy=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();
}
public void addBall(){
球b=新球(画布);
13.添加(b);
新线程(()->{
for(inti=1;ib.move());
试一试{
睡眠(10);
}捕捉(中断异常e){
e、 printStackTrace();
}
}
}).start();
}

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

问题#1:
绘制
重新绘制
从 :

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

因此
Ball.move
末尾的
canvas.paint()
行必须离开

你想打电话吗 相反 但是,只要将
油漆
替换为
重新油漆
,就会发现第二个问题,它甚至会阻止球出现

问题2:在
ActionListener中设置动画
理想的
ActionListener.actionPerformed
方法会更改程序的状态并尽快返回,使用类似
repaint
的惰性方法让Swing在最方便的时候安排实际工作

相反,您的程序基本上执行
actionPerformed
方法中的所有操作,包括所有动画

解决方案:A 一个更典型的结构是启动一个 当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();
}
对你来说,最重要的是 (而且完全不见了) 国家是最重要的 “我们开始了吗?” 位,可作为
布尔值存储在
BallCanvas
中。 这是应该做所有动画的类,因为它还拥有画布和所有球

BallCanvas
获得一个字段,
正在运行

private boolean isRunning = false;  // new field

// Added generic type to `balls` --- see below.
private java.util.List<Ball> balls = new ArrayList<Ball>();
最后,
BallCanvas.moveBalls
是新的 “更新所有内容”
计时器调用的方法

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绘画的工作原理), 看见 ": 良好的绘制代码是应用程序性能的关键”
Amy Fowler所著。

我建议您使用“Timer”类来运行gameloop。它可以无限运行,您可以随时使用Timer停止它。stop()
你也可以相应地设置它的速度。

每次你
addBall
你睡眠10000*10 msThread.sleep(1000)//对于1个扇区,有很多成熟的闪烁解决方案,一个著名的是双缓冲区(甚至三缓冲区),基本原理是在完成时(或发生vsync时)将对象绘制到脱机缓冲区然后翻转到屏幕上。就性能而言,托管运行时应用程序的运行速度可能与本地应用程序的运行速度一样快。这对动画效果更好。@tibetty:在一个300×200的小窗口周围滑动一个圆圈,在任何比PC更强大的东西上都不会闪烁。肯定有比缺少双缓冲更糟糕的事情了。这有点帮助
public void addBall()
{
    Ball b = new Ball(canvas);
    canvas.add(b);
    this.canvas.setRunning(true);
}