首页 > 解决方案 > 使用计时器和paintComponent 进行渲染会导致没有渲染发生

问题描述

我一直在尝试开发一个游戏,我正处于让所有单独的组件工作的初步阶段(因此我的代码很糟糕),在尝试渲染一个移动的正方形时,我遇到了一个问题。paintComponent 方法将绘制正方形,但是当我使用计时器时,我无法让它工作。

我尝试过移动计时器方法,在不同点使用重绘,并在某一点报废所有内容并从头开始重做。

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.time.Clock;
import java.time.Duration;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.JFrame;

public class InputTest extends MyPanel implements KeyListener {
    private static int x;
    private static int y;

    public static void main(String[] args) {
        JFrame f = new JFrame();
        InputTest p = new InputTest();
        f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        f.setSize(500, 500);
        p.setSize(500, 500);
        f.add(p);
        f.addKeyListener(new InputTest());
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                x++;
                y++;
                if (x > 500) {
                    x = 0;
                }
                if (y > 500) {
                    y = 0;
                }
                System.out.println("Tick"+x+","+y);

            }
        }, 0, 500);

        p.setVisible(true);
        f.setVisible(true);
    }

    @Override

    public void keyPressed(KeyEvent e) {

    }

    @Override
    public void keyReleased(KeyEvent e) {
        // TODO Auto-generated method stub

    }

    @Override
    public void keyTyped(KeyEvent e) {
        // TODO Auto-generated method stub

    }

    public void update() {

    }

    @Override
    protected void paintComponent(Graphics g) {
        g.setColor(Color.BLACK);
        while(x!=501&&y!=501) {
            g.fillRect(x, y, 30, 30);
            repaint();
        }
    }
}

正方形应该从左上角缓慢移动到右下角。相反,我没有得到任何图像。

标签: javaswinggraphics

解决方案


Swing 是单线程的

当 Swing 触发绘制周期时,直到层次结构中的所有组件都完成操作后,结果才会呈现到屏幕上。

这意味着当您执行类似...

@Override
protected void paintComponent(Graphics g) {
    g.setColor(Color.BLACK);
    while (x != 501 && y != 501) {
        g.fillRect(x, y, 30, 30);
        repaint();
    }
}

在您退出该方法之前,什么都不会发生paintComponent,这意味着,在这种情况下,您绘制的最后一件事就是将要渲染的内容(或者在这种情况下是一个不错的连续性)。

作为旁注,您永远不应该repaint直接或间接地从绘图方法中调用,这是消耗所有 CPU 周期并使您的系统崩溃的真正好方法。

相反,绘画应该简单地描绘当前/期望的状态。此外,除非您真的知道自己在做什么,否则您应该始终super.paintComponent先打电话,例如...

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    g.setColor(Color.BLACK);
    g.fillRect(x, y, 30, 30);
}

有关更多详细信息,请参阅Swing 中的并发

Swing 不是线程安全的

您应该始终避免从事件调度线程之外更新 UI。这还包括更新 UI 所依赖的状态。这样做会导致各种难以诊断的故障。

由于java.util.Timer使用它自己的Thread来执行它的调度,所以它不适合在 Swing 中使用。相反,您应该使用javax.swing.Timer,它将在 EDT 外等待,但触发它分配ActionListener了 EDT 的上下文

例如...

Timer timer = new Timer(500, new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        x++;
        y++;
        if (x > 500) {
            x = 0;
        }
        if (y > 500) {
            y = 0;
        }

        // Trigger a new paint cycle...
        repaint();
    }
});
timer.start();

有关更多详细信息,请参阅如何使用摆动计时器

所有“其他”的东西

KeyListener最好避免,原因有很多。使用Key Bindings API,它将为您省去很多麻烦。

组件的可用大小总是窗口的大小减去它的边框装饰。这意味着 usingf.setSize(500, 500)将使可用的上下文区域更小(取决于您运行代码的平台)。

最好覆盖组件的getPreferredSize方法并返回大小提示。然后,您可以使用JFrame#pack“打包”内容周围的窗口,使窗口大于内容大小,但您不会浪费时间试图弄清楚为什么事情不会在您期望的地方停止。

可运行示例

我还修改了代码以消除对 的依赖static,这从来都不是一个好主意

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class InputTest extends JPanel {

    private int x;
    private int y;

    public static void main(String[] args) {
        JFrame f = new JFrame();
        InputTest p = new InputTest();
        f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        f.add(p);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    public InputTest() {
        Timer timer = new Timer(500, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                x++;
                y++;
                if (x > 500) {
                    x = 0;
                }
                if (y > 500) {
                    y = 0;
                }

                // Trigger a new paint cycle...
                repaint();
            }
        });
        timer.start();
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(500, 500);
    }

    @Override
    protected void paintComponent(Graphics g) {
        g.setColor(Color.BLACK);
        g.fillRect(x, y, 30, 30);
    }
}

推荐阅读