Как эффективно перекрасить при использовании большого пользовательского компонента в Swing? - PullRequest
5 голосов
/ 18 мая 2009

Я создал пользовательский компонент (полученный из JComponent), который представляет перетаскиваемый кривая Безье .
(похоже на подвесной кабель, кто-то может знать это от Бендера или Кубазы)

Моя проблема: кривая может стать очень длинной , скажем, от левого верхнего до правого нижнего угла рабочего стола.

Это делает перерисовку Swing функциональностью неэффективной : Площадь кривой может быть несколько сотен пикселей, но площадь компонент (в основном «прозрачный» ) имеет размеры в миллионы пикселей.

У меня сложилось впечатление:
Чем длиннее кривая, тем больше мерцания я получаю при перетаскивании.

Надеюсь, я прояснил проблему.

Возможно, это помогло бы, если бы я каким-то образом мог выбрать , какие регионы компонента нужно перекрасить вообще .

EDIT:
Такой беспорядок! Я профилирую приложение, используя Netbeans, который помогает обычно находят неэффективный код, но эта среда Swing делает сотни вложенных звонков! Я просто не могу понять, что медленно и почему.
Кстати, отключение super.paint(...) или super.paintComponent(...) не помогает.

Ответы [ 8 ]

4 голосов
/ 18 мая 2009

Отъезд Грязные богатые клиенты от Chet Haase и Romain Guy. Они решают эту проблему, в частности, на пути к созданию отзывчивого и графически впечатляющего пользовательского интерфейса.

3 голосов
/ 19 мая 2009

Попробуйте написать крошечное тестовое приложение, которое не содержит ничего, кроме того, что вам нужно для воспроизведения этой проблемы. Это облегчит профилирование. Затем опубликуйте это приложение здесь, чтобы мы могли взглянуть на возможные решения.

Я нашел ваш вопрос интересным, поэтому сам написал тестовое приложение. Это рисует кривую Безье, которая постоянно изменяется при перетаскивании. Я создал градиентный фон, чтобы он хорошо работал с неприятным фоном. Я получаю хорошую производительность и низкий уровень мерцания, хотя я использую первоклассный аппарат.

Стоит почитать «Грязных богатых клиентов», чтобы изучить все приемы написания пользовательских компонентов 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 голосов
/ 19 мая 2009

Выполнение всей вашей более безумной математики в потоке краски при каждом обновлении компонента - это (как вы уже поняли) плохая идея. Ваша кривая часто меняется? Если нет, то почему бы не нарисовать его в 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 голосов
/ 18 мая 2009

Не существует эффективного способа создать множество маленьких прямоугольников с клипами для диагональной структуры, что дает вам две стратегии, чтобы избежать мерцания:

  • Двойная буферизация. Это требует огромного количества памяти, но копирование памяти происходит очень быстро (обычно это происходит, когда «электронный луч» возвращается из нижнего правого в верхний левый угол ... если на вашем ЖК-дисплее все еще был луч).

  • Не вызывайте super.paint () (который рисует или «стирает» фон ) и рисуйте кривую второй раз с цветом фона, чтобы стереть его.

Подробнее см. в этом документе .

[РЕДАКТИРОВАТЬ] Если fillRect () не был абстрактным, вы можете установить точку останова :) Установите точку останова в paint (), проверьте, кто ее вызывает и был ли очищен фон в это время. Так и должно быть, поскольку рендеринг был бы совершенно неправильным. Затем установите точки останова еще выше в цепочке вызовов.

1 голос
/ 18 мая 2009

Какой метод вы используете, чтобы нарисовать вашу кривую? краска или краскаКомпонент?

1 голос
/ 18 мая 2009

Вы можете перерисовать меньшую часть экрана, используя repaint(Rectangle r)

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

Тогда вы упоминаете мерцание. Поскольку вы используете свинг, который использует двойную буферизацию, ваше мерцание должно исходить от чего-то другого. Вы очищаете экран в paintComponent(...)? То есть позвонить на fillRect(...)? Не делай этого, это не нужно (IIRC).

0 голосов
/ 18 декабря 2012

просто используйте getVisibleRect(); внутри paintComponent(Graphics g), чтобы получить область, которую вам действительно нужно перерисовать

0 голосов
/ 30 июня 2009

Мое решение было частичным редизайн :

Теперь я не представляю каждый элемент «кабеля» компонентом.
Теперь кабели - это просто фиктивные объекты (без участия JComponent).
Перекраска происходит «глобально», на панели содержимого родительского JFrame .

Теперь это эффективно и меньше мерцает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...