【问题标题】:JPanel flickering issueJPanel 闪烁问题
【发布时间】:2025-12-10 08:15:01
【问题描述】:

对于我的游戏,我绘制到java.awt.Image,然后将图像绘制到 JPanel 上。我这样做有几个原因,主要是因为我不希望游戏渲染占用 EDT 上的 CPU 周期,以及为了便携性。

出现问题导致java.awt.JPanel在使用时闪烁

graphics#drawImage(图像img, int x, int y, int width, int height, ImageObserver 观察者)

.

然而,

graphics#drawImage(Image img, int x, int y, ImageObserver 观察者)

没有引起这个问题。

这是我的代码:

沙盒.java

public class Sandbox implements Paintable {

public static void main(String[] args) throws Exception {
    GameWindow window = new GameWindow("Test", 800, 600);
    GameScreen screen = new GameScreen(800, 600);
    Sandbox sandbox = new Sandbox();

    window.add(screen);
    window.setVisible(true);

    boolean running = true;
    while(running) {
        sandbox.update();
        screen.getPaintBuffer().clear();
        screen.getPaintBuffer().paint(sandbox);
        screen.repaint();
        Thread.sleep(1000 / 60);
    }

}

private int x = 0, y = 0;

public void update() {
    x++;
    y++;
}

public void paint(Graphics g) {
    g.drawRect(x, y, 50, 50);
}

}

GameWindow.java

public class GameWindow extends JFrame {

public GameWindow(String title, int width, int height) {
    setTitle(title);
    setSize(width, height);
    setResizable(false);
    setLocationByPlatform(true);
    setDefaultCloseOperation(3);
}

}

GameScreen.java

public class GameScreen extends JPanel {

private ImageBuffer buffer;

public GameScreen(int width, int height) {
    buffer = new ImageBuffer(width, height);
}

public void paint(Graphics g) {
    g.drawImage(getPaintBuffer().getBuffer(), 0, 0, getWidth(), getHeight(), null);
}

public ImageBuffer getPaintBuffer() {
    return buffer;
}

}

Paintable.java

public interface Paintable {

public void paint(Graphics g);

}

ImageBuffer.java

public class ImageBuffer {

private final Image buffer;
private int width, height;

public ImageBuffer(int width, int height) {
    buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
    this.width = width;
    this.height = height;
}

public void paint(Paintable paintable) {
    paintable.paint(buffer.getGraphics());
}

public void clear() {
    buffer.getGraphics().clearRect(0, 0, width, height);
}

public Image getBuffer() {
    return buffer;
}

}

【问题讨论】:

  • 我会说这是一个侥幸,一个工作,另一个没有
  • 侥幸发生一次,它们不容易重现,这个错误是。
  • 伙计,如果您对此有信心,请提交错误报告

标签: java swing jpanel


【解决方案1】:

GameScreenpaint 方法更改为...

  1. 改写paintComponent
  2. 请致电super.paintComponent 以维护油漆链的合约
  3. this 作为ImageObsever 参数传递给drawImage

例如...

public class GameScreen extends JPanel {

    private ImageBuffer buffer;

    public GameScreen(int width, int height) {
        buffer = new ImageBuffer(width, height);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(getPaintBuffer().getBuffer(), 0, 0, getWidth(), getHeight(), this);
    }

    public ImageBuffer getPaintBuffer() {
        return buffer;
    }

}

查看Painting in AWT and SwingPerforming Custom Painting 了解有关绘画工作原理的更多详细信息

更新

基本问题是缩放问题...

您用来绘制的图像是800x600,但GameScreen 实际上是794x572(在我的电脑上),这会导致图像被缩放。现在,Swing 的默认缩放并不漂亮,而且通常基于速度而不是质量。

现在,您可以通过多种方式改进这一点,但一种快速的方法是应用一些更高的渲染提示,例如

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D) g.create();
    g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
    g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
    g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
    g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
    g2d.drawImage(getPaintBuffer().getBuffer(), 0, 0, getWidth(), getHeight(), this);
    g2d.dispose();
}

现在,我已经过火了,所以你可能想删除一些,看看有什么变化

更新

渲染提示可能会减慢渲染过程,因此更好的解决方案可能是覆盖 GameScreengetPreferredSize 方法并返回预期大小

public static class GameScreen extends JPanel {

    private ImageBuffer buffer;
    private Dimension size;

    public GameScreen(int width, int height) {
        buffer = new ImageBuffer(width, height);
        this.size = new Dimension(width, height);
    }

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

然后,而不是将大小传递给GameWindow,只需调用pack

GameWindow window = new GameWindow("Test");
GameScreen screen = new GameScreen(800, 600);
Sandbox sandbox = new Sandbox();

window.add(screen);
window.pack();
window.setVisible(true);

这样,每个人的尺寸都一样

【讨论】:

  • 我试过用paintComponent代替,它不会改变任何东西。我不想使用 ImageObserver 并且无论如何这都不相关。另外我不调用 super.paint(g) 因为我不打算向这个类添加任何组件。无论哪种方式,这些更改都不相关,并且无法解决我的问题。
  • @ChrisGray 绘画比“绘画”组件更复杂。你有责任维护油漆链,否则奇怪和奇妙的事情可能会出错。 ImageObsever 向 API 提供了一些重新激活的信息,因此组件可以在 Image 更改时自行更新。两者都做是“好习惯”
  • 我明白这一点。我已经在 Swing 和 AWT 中绘画了很长一段时间。现在,此图像用作缓冲区,我不希望它在完成之前显示或重绘到屏幕上。每次更改图像时更新 JPanel 都是浪费处理能力,没有必要这样做。有时应该忽略“良好做法”的内容。
  • 当然可以,只要你不介意什么时候出错。您应该考虑改用BufferStrategy 并完全控制绘画过程,这就是它的设计目的。无论如何,您的问题不是错误,而是误解或忽略了两个 drawImage 方法正在做的事情之间的差异,以及 JFrame 将其窗口装饰画在框架,使其内容小于实际的窗口边界。您可以通过在 GameScreen 中使用 getPreferredSize 来解决此问题
  • 我知道,我选择不使用 Canvas 和缓冲策略(我不想在评论中列出我所有的原因)。我也可以调用 getContentPane().setPreferredSize(new Dimension(width, height); 然后调用 pack()。但为了简单起见,我可以快速写出来,我选择退出。