Swing中的增量图形

我试图在Swing中完成图形处理(画线等)的工作。 到目前为止,我见过的所有教程都声明了一个覆盖paintComponent的类,并且所有paintComponent方法都做了一些特定的事情,比如绘制一个红色正方形(尽管可能每次都在不同的位置绘制它)。 或者他们可能会绘制一些线条和形状,但paintComponent方法会同时执行所有操作。

我想弄清楚:假设我想在一个组件中绘制一个东西,然后在它上面绘制其他东西而不删除我之前绘制的东西。 我的第一个想法是让我的paintComponent覆盖调用回调。

 import java.awt.*; import javax.swing.*; public class DrawTest { private interface GraphicsAction { public void action (Graphics g); } private static class TestPanel extends JPanel { private GraphicsAction paintAction; public void draw (GraphicsAction action) { paintAction = action; repaint(); } @Override public void paintComponent (Graphics g) { super.paintComponent (g); if (paintAction != null) paintAction.action(g); } } private static void createAndShowGui() { JFrame frame = new JFrame ("DrawTest"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setPreferredSize(new Dimension(500,500)); TestPanel p = new TestPanel (); frame.getContentPane().add(p); frame.pack(); frame.setVisible(true); p.repaint(); p.draw (new GraphicsAction () { public void action (Graphics g) { g.setColor(Color.RED); g.drawLine(5, 30, 100, 50); } }); // in real life, we would do some other stuff and then // later something would want to add a blue line to the // diagram p.draw (new GraphicsAction () { public void action (Graphics g) { g.setColor(Color.BLUE); g.drawLine(5, 30, 150, 40); } }); } public static void main (String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { createAndShowGui(); } }); } } 

这不起作用。 出现蓝线,但没有红线。 我猜这是因为repaint()会导致一切都在我绘制蓝线时重新开始,但我不确定; 无论如何,我不知道如何让paintComponent被调用。

另外,如果我在两个p.draw调用之间放置一个Thread.sleep(1000) ,我甚至都看不到红线。 因此,我不清楚如何让我的图形显示在我想要的时候。

我在Swing中对“增量图形”进行了一些搜索,但没有任何有助于找到解决方案。 我发现了一篇Oracle文章“在AWT和Swing中绘画”,讨论了覆盖update()方法以完成增量图形,但我还没有找到任何实际的例子。

那么我怎样才能做到我想要的呢? 这似乎是一个很常见的任务,应该有一个简单的方法来做,但我还没有找到一个。 我假设它应该是可行的而不需要调用getGraphics ,基于其他StackOverflow响应,它最多只是一种诡计。

Swing中的绘画具有破坏性。 也就是说,每当新的绘制周期运行时,您都应该根据要绘制的对象的状态完全重建输出。

看看AWT和Swing中的绘画

所以当你打电话

 p.draw (new GraphicsAction () { public void action (Graphics g) { g.setColor(Color.RED); g.drawLine(5, 30, 100, 50); } }); 

其次是

 p.draw (new GraphicsAction () { public void action (Graphics g) { g.setColor(Color.BLUE); g.drawLine(5, 30, 150, 40); } }); 

你基本上扔掉了第一个动作。 忽略目前如何安排重拍。 第一个请求说“画一条红线”,第二个请求“画一条蓝线”,但在执行这些动作之前,清理Graphics上下文,准备更新。

这非常重要,因为您提供的Graphics上下文是共享资源。 之前绘制的所有组件都使用了相同的上下文,在您使用相同的上下文之后绘制的所有组件。 这意味着,如果在绘制之前没有“清理”上下文,则最终会出现不需要的绘制工件。

但你怎么能绕过它?

这里有几个选择。

您可以绘制到具有自己的Graphics上下文的后备缓冲区(或BufferedImage ),您可以添加它,并且只需要在paintComponent方法中“绘制”。

这意味着,每次调用p.draw(...) ,您实际上会首先repaint到此缓冲区,然后调用repaint

这个问题是,你需要保持缓冲区的大小。 每次组件大小更改时,您都需要根据组件的新大小将此缓冲区复制到新缓冲区。 这有点乱,但是可行。

另一种解决方案是将每个操作放在List并在需要时,只需循环遍历List并在需要时重新应用操作。

这可能是最简单的方法,但随着动作数量的增加,可能会降低油漆过程的有效性,从而减缓油漆过程。

您也可以使用两者的组合。 当缓冲区不存在时生成缓冲区,遍历操作List并将它们渲染到缓冲区,并简单地在paintComponent方法中绘制缓冲区。 每当组件resize时,只需将缓冲区置null并允许paintComponent重新生成它…例如……

另外,如果我在两个p.draw调用之间放置一个Thread.sleep(1000)

Swing是一个单线程框架。 这意味着所有更新和修改都应在Event Dispatching Thread的上下文中完成。

同样,任何阻止EDT运行的东西都会阻止它处理(除其他外)绘制请求。

这意味着当您在p.draw调用之间sleep时,您将停止运行EDT,这意味着它无法处理您的绘制请求…

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

更新了示例

在此处输入图像描述

我只想指出这是非常低效的。 每次调用invalidate重新创建缓冲区将创建大量短期对象,并且可能会显着降低性能。

通常,我会使用javax.swing.Timer ,设置为非重复,每次调用invalidate时都会重新启动。 这将设置为短暂的延迟(介于125-250毫秒之间)。 当计时器被触发时,我只是在这个时候重新构造缓冲区,但这只是一个例子;)

 import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.SwingUtilities; public class DrawTest { private interface GraphicsAction { public void action(Graphics g); } private static class TestPanel extends JPanel { private GraphicsAction paintAction; private BufferedImage buffer; @Override public void invalidate() { BufferedImage img = new BufferedImage( Math.max(1, getWidth()), Math.max(1, getHeight()), BufferedImage.TYPE_INT_ARGB); Graphics2D g2d = img.createGraphics(); g2d.setColor(getBackground()); g2d.fillRect(0, 0, getWidth(), getHeight()); if (buffer != null) { g2d.drawImage(buffer, 0, 0, this); } g2d.dispose(); buffer = img; super.invalidate(); } protected BufferedImage getBuffer() { if (buffer == null) { buffer = new BufferedImage( Math.max(1, getWidth()), Math.max(1, getHeight()), BufferedImage.TYPE_INT_ARGB); Graphics2D g2d = buffer.createGraphics(); g2d.setColor(getBackground()); g2d.fillRect(0, 0, getWidth(), getHeight()); g2d.dispose(); } return buffer; } public void draw(GraphicsAction action) { BufferedImage buffer = getBuffer(); Graphics2D g2d = buffer.createGraphics(); action.action(g2d); g2d.dispose(); repaint(); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); g.drawImage(getBuffer(), 0, 0, this); } } private static void createAndShowGui() { JFrame frame = new JFrame("DrawTest"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setPreferredSize(new Dimension(500, 500)); TestPanel p = new TestPanel(); frame.getContentPane().add(p); frame.pack(); frame.setVisible(true); p.repaint(); p.draw(new GraphicsAction() { public void action(Graphics g) { g.setColor(Color.RED); g.drawLine(5, 30, 100, 50); } }); // in real life, we would do some other stuff and then // later something would want to add a blue line to the // diagram p.draw(new GraphicsAction() { public void action(Graphics g) { g.setColor(Color.BLUE); g.drawLine(5, 30, 150, 40); } }); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { createAndShowGui(); } }); } }