Swing中的增量图形

ajb*_*ajb 3 java graphics swing graphics2d

我正试图在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();                 
            }
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

这不起作用.出现蓝线,但没有红线.我猜这是因为当我绘制蓝线时,repaint()in draw会导致一切都重新开始,但我不确定; 无论如何,我不知道怎么回事 paintComponent.

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

我已经在Swing中对"增量图形"进行了一些搜索,但没有任何东西可以帮助找到解决方案.我发现了一篇Oracle文章"在AWT和Swing中绘画",讨论了覆盖一种update() 实现增量图形的方法,但我还没有找到任何实际的例子.

那么我怎么能做到这一点呢?这似乎是一个很常见的任务,应该有一个简单的方法来做,但我还没有找到一个.我假设它应该可以在没有调用的情况下完成 getGraphics,基于其他StackOverflow响应,它充其量只是一种诡计.

Mad*_*mer 6

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

看看AWT和Swing中的绘画

所以当你打电话

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

其次是

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

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

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

但你怎么能绕过它?

这里有一些选择.

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

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

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

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

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

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

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

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

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

这意味着当您sleepp.draw两次调用之间时,您正在停止运行EDT,这意味着它无法处理您的绘制请求...

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

更新了示例

在此输入图像描述

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

通常情况下,我会使用a 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();
            }
        });
    }
}
Run Code Online (Sandbox Code Playgroud)