在Swing中使用大型自定义组件时,如何有效地重新绘制?

时间:2009-05-18 14:13:18

标签: java user-interface swing optimization graphics

我创建了一个自定义组件(派生自JComponent) 可拖动的 Bezier曲线 (看起来像挂电缆,有人可能知道它 来自Bender或Cubase)

我的问题是:曲线可能变得真的很长, 让我们说从桌面的左上角到右下角。

这使得Swing的重绘功能效率低下: 曲线的面积可能是几百个像素,但面积是 组件(主要是'透明')是数百万像素。

我的主观印象是:
曲线越长,拖动时闪烁越多

我希望我能清楚地说明这个问题。

当我以某种方式可以自己选择哪些地区时,也许会有所帮助 组件需要重新绘制

修改
一团糟!我正在使用Netbeans分析应用程序,这有助于 通常会找到效率低下的代码,但是这个Swing框架正在制作数百个 嵌套电话!我只是想不通,什么是缓慢的,为什么 顺便说一下,禁用super.paint(...)super.paintComponent(...)无效。

8 个答案:

答案 0 :(得分:4)

查看Chet Haase和Romain Guy的Filthy Rich Clients。他们在制作具有响应性和图形效果的用户界面的过程中解决了这些非常优化的问题。

答案 1 :(得分:3)

每次刷新组件时,在绘制线程上执行所有bezier数学是(因为你已经收集)一个坏主意。你的曲线经常改变吗?如果没有,那么为什么不在它改变时将它绘制到BufferedImage,并改变你的paint()代码以简单地将缓冲的图像绘制到组件中。

class CurveComponent extends JComponent {
    private BufferedImage image;

    @Override
    public void paintComponent( Graphics g ) {
        if ( image == null ) {
            return;
        }
        g.drawImage( image, 0, 0, this );
    }

    private void updateCurve() {
        image = new BufferedImage( getWidth(), getHeight(), BufferedImage.ARGB );
        Graphics g = image.getGraphics();
        // draw the curve onto image using g.
        g.dispose();
    }
}

只在需要时调用updateCurve(),并且不会不必要地重复所有昂贵的数学。即使对于全屏窗口,绘画也应该非常灵敏。 drawImage()将做一个简单的内存复制,应该是闪电般的。

答案 2 :(得分:3)

尝试编写一个很小的测试应用程序,除了您需要重现此问题之外什么也没有。这将使分析更容易。然后在这里发布该应用程序,以便我们查看可能的解决方案。

我发现你的问题很有趣,所以我自己写了一个测试应用程序。这会绘制一条Bezier曲线,在您拖动时会不断调整大小。我创建了一个渐变背景,以确保这适用于令人讨厌的背景。虽然我使用的是顶级机器,但我的性能和闪烁都很好。

阅读“肮脏的富客户端”以了解编写表现非常好的自定义Swing组件的所有技巧是值得的。

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.Point2D;

public class CustomComponent extends JComponent {

    private Point2D start = new Point2D.Double(0, 0);
    private Point2D end = new Point2D.Double(300, 200);

    private CustomComponent() {
        this.setOpaque(true);
        final MouseAdapter mouseAdapter = new MouseAdapter() {

            @Override
            public void mouseDragged(MouseEvent e) {
                    setEnd(e.getPoint());
            }

        };
        this.addMouseListener(mouseAdapter);
        this.addMouseMotionListener(mouseAdapter);
    }

    public void setStart(Point2D start) {
        this.start = start;
        repaint();
    }

    public void setEnd(Point2D end) {
        this.end = end;
        repaint();
    }

    @Override
    protected void paintComponent(Graphics g) {

        final Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        // draw gradient background
        final int width = getWidth();
        final int height = getHeight();
        g2.setPaint(new GradientPaint(0, 0, Color.WHITE, width, height, Color.YELLOW));
        g2.fillRect(0, 0, width, height);

        // draw Bezier curve
        final Shape shape = new CubicCurve2D.Double(start.getX(), start.getY(), start.getX(), end.getY(), end.getX(), start.getY(), end.getX(), end.getY());
        g2.setColor(Color.BLACK);
        g2.draw(shape);

        g2.drawString("Click and drag to test for flickering", 100, 20);
    }

    public static void main(String[] args) {
        final CustomComponent component = new CustomComponent();
        final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        final Dimension size = new Dimension(screenSize.width - 20, screenSize.height - 100);
        component.setPreferredSize(size);

        final JFrame frame = new JFrame();
        frame.add(component);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

}

有些注意事项:

  • 仅覆盖paintComponent(Graphics g),而不覆盖其他paintXXX()方法
  • 如果可能,将自定义组件设置为不透明
  • 仅使用repaint()来请求重新绘制。切勿直接在代码中直接订购重绘。这让Swing可以很好地处理它。

答案 3 :(得分:2)

没有有效的方法可以为对角线结构创建大量的小剪辑矩形,这样可以避免出现两种闪烁的策略:

  • 双缓冲。这需要大量的存储器,但存储器复制速度非常快(通常发生在“电子束”从右下方向左上方返回的时候......如果LCD中仍有光束)。

  • 请勿调用super.paint()(draws or "erases" the background)并使用背景颜色再次绘制曲线以将其删除。

有关详细信息,请参阅this document

[编辑]如果fillRect()不是抽象的,你可以设置一个断点:)在paint()中设置一个断点,检查谁调用它,以及当时是否清除了背景。应该是因为渲染完全错误。然后在调用链中进一步设置断点。

答案 4 :(得分:1)

您可以使用repaint(Rectangle r)

重绘屏幕的较小部分

http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/JComponent.html#repaint(java.awt.Rectangle)

然后你提到闪烁。由于你使用的是swing,它使用双缓冲,你的闪烁必须来自其他东西。你在paintComponent(...)清除屏幕了吗?即致电fillRect(...)?不要这样做,不需要它(IIRC)。

答案 5 :(得分:1)

你用哪种方法画你的路边? paint vs paintComponent?

答案 6 :(得分:0)

我的解决方案是部分重新设计

现在我没有用组件代表每个“电缆”元素 现在,电缆只是虚拟对象(没有涉及JComponent) 重绘在“父级JFrame 的内容”窗格中“全局”进行。

现在它效率很高,而且闪烁得更少。

答案 7 :(得分:0)

只需在getVisibleRect();内使用paintComponent(Graphics g)即可获得实际需要重绘的区域