【问题标题】:Redrawing lines with Java Swing efficiently使用 Java Swing 高效地重绘线条
【发布时间】:2018-08-27 10:01:56
【问题描述】:

我正在尝试寻找最有效的方法来创建动态 java 图形应用程序。我想构建一个带有许多不同部分的大屏幕,所有这些都使用不同的线程重新绘制或更新,以使屏幕看起来“活”。 然而,我最初的尝试是可怕的,屏幕变得非常慢,错误等等 - 所以我想我需要创建不同的模块(JPanels),每个模块都包含其他图形部分(线条、圆圈等),并且每个单独重绘不同的 JPanel(需要时),而不是整个主面板(或框架)。

所以我写了一个小演示程序——我的程序包含一个窗口,有多个面板,包裹在我的名为“MyPanel”的对象中——每个这样的 MyPanel 都包含几条画线(我有一个 Line 对象),所有线都以从左上角开始,有不同的长度和角度)。每个不同的 MyPanel 都有不同的线条颜色(参见此图)。

我实例化了几个工作线程,每个线程指定一个 MyPanel - 工作人员等待 5 秒,然后尝试以下列方式重新绘制所有线条:

  • 从 JPanel (MyPanel) 中删除所有现有行。
  • 创建具有不同角度和长度的新线条。
  • 通过调用 super.repaint() 重绘 JPanel (MyPanel) 这是整个目的,只更新这个面板,让它重绘它的所有子部分,而不是整个程序强>

然而,发生了一些奇怪的事情:当重新绘制面板时,每个面板都以一种可能包含所有其他 MyPanel 的方式重新绘制,或者以某种方式镜像主屏幕 - 非常不清楚这里到底发生了什么。此外,面板的所有“背景不透明度”都消失了(参见这张图片)。

在我附加我的代码之前,让我说它使用了一个空的 LayoutManager。我知道就效率、模块化等方面而言,这是一个很大的“不可以”。但是我别无选择,因为我需要快速创建一个非常复杂且精确的演示,这仅作为概念验证,所以现在,所有这些缺陷都可以忽略不计。我知道这在设计方面很糟糕,也让我很痛苦,但这是我能按时完成的唯一方法。

这是代码 - 会发生什么? 如果不使用这种方式,如何有效地重新绘制程序的不同部分?请注意,我不能“用背景颜色重新绘制现有线条”,因为我的主程序中有一个背景图像.

任何帮助将不胜感激!

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 * Displays the main windows (this is the "JFrame" object).
 */
public class GUI extends JFrame
{
/**
 * A customized panel which contains several lines with different coordinates, all starting from
 * the top left corner of the panel with coordinates (1,1). The object contains a method which
 * removes all drawn lines from the panel, then redraws lines with different vectors.
 */
public static class MyPanel extends JPanel
{
    private List<Line> _lines;

    private Color _color;

    private int _facet;

    private int _numLines;

    public MyPanel(int facet, int numLines, Color color)
    {
        _facet = facet;
        _color = color;
        _numLines = numLines;
        _lines = new ArrayList<>();

        super.setLayout(null);
        createLines();
    }

    public void createLines()
    {
        for(Line line : _lines)
        {
            remove(line);
        }

        _lines.clear();

        Random r = new Random();

        for(int i = 0; i < _numLines; i++)
        {
            int lengthX = r.nextInt(_facet) + 1;
            int lengthY = r.nextInt(_facet) + 1;

            Line line = new Line(1, 1, 1 + lengthX, 1 + lengthY, 1, _color);

            line.setBounds(1, 1, 1 + lengthX, 1 + lengthY);
            super.add(line);

            _lines.add(line);
        }

        super.repaint();
    }
}

/**
 * Represents a line, drawn with antialiasing at a given start and end coordinates
 * and a given thickness.
 */
public static class Line extends JPanel
{
    private int _startX;
    private int _startY;
    private int _endX;
    private int _endY;

    private float _thickness;
    private Color _color;

    public Line(int startX, int startY, int endX, int endY, float thickness, Color color)
    {
        _startX = startX;
        _startY = startY;
        _endX = endX;
        _endY = endY;

        _thickness = thickness;
        _color = color;
    }

    public void paint(Graphics g)
    {
        Graphics2D g2d = (Graphics2D)g;

        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        g2d.setColor(_color);
        g2d.setStroke(new BasicStroke(_thickness));
        g2d.drawLine(_startX, _startY, _endX, _endY);
    }
}

/**
 * Stores all "MyPanel" panels of the GUI.
 * The "MyPanels" are rectangular panels containing lines of the same color
 * (different color across different panels).
 */
public List<MyPanel> panels;

public GUI()
{
    setSize(800, 800);
    setLayout(null);
    setTitle("Y U no work??");

    panels = new ArrayList<>();

    // The starting positions (x,y) of the "MyPanel"s. All panels are squares of
    // height = 300 and width = 300.
    int[][] coords = {{1, 1}, {100, 100}, {200, 100}, {50, 300}, {300, 300}, 
                      {0, 400}, {300, 400}, {350, 250}, {370, 390}};

    // The colors of the lines, drawn in the panels.
    Color[] colors = {Color.RED, Color.GREEN, Color.BLUE, Color.ORANGE, Color.CYAN,
                      Color.MAGENTA, Color.YELLOW, Color.PINK, Color.darkGray};


    for(int i = 0; i < colors.length; i++)
    {
        MyPanel panel = new MyPanel(300, 50, colors[i]);
        panel.setBackground(new Color(0, 0, 0, 0));
        // Set the *exact* start coordinates and width/height (null layout manager).
        panel.setBounds(coords[i][0], coords[i][1], 300, 300);
        add(panel);
        panels.add(panel);
    }
}

/**
 * A runnable used to instantiate a thread which waits for 5 seconds then redraws
 * the lines of a given "MyPanel".
 */
public static class Actioner implements Runnable
{
    private MyPanel _panel;

    public Actioner(MyPanel panel)
    {
        _panel = panel;
    }

    public void run()
    {
        while(true)
        {
            try
            {
                Thread.sleep(5000);
            }
            catch(Exception e) {}

            _panel.createLines();
        }
    }
}

public static void main(String[] args)
{
    GUI GUI = new GUI();

    EventQueue.invokeLater(() ->
    {
        GUI.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        GUI.setVisible(true);
    });

    // Create all operating threads (one per "MyPanel").
    for(MyPanel panel : GUI.panels)
    {
        new Thread(new Actioner(panel)).start();
    }
}

}

【问题讨论】:

  • 1) 自定义绘画是通过覆盖paintComponent(...) 而不是paint() 来完成的。 2) 第一条语句应该是super.paintComponent(g) 以清除背景。 3) 行不应扩展 JPanel。它应该只是一个包含有关要绘制的线的信息的类。然后,您保留您绘制的 Line 对象的 ArrayList。有关此方法的示例,请参阅 Custom Painting Approaches。它展示了如何从 ArrayList 绘制 Colored Rectangles
  • 使用上述方法也可以解决空布局问题。
  • 感谢您的评论!我会调查你写的。我相信它会有所帮助:)
  • @camickr 再次感谢您的回复,它帮助我编写了更好的 JPanel 组件,将几张图纸编译成一个 paintComponent 方法真的很有帮助。

标签: java swing jpanel


【解决方案1】:

所以,一连串的错误:

自定义绘画使用不当...

这...

public void paint(Graphics g)
{
    Graphics2D g2d = (Graphics2D)g;

    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

    g2d.setColor(_color);
    g2d.setStroke(new BasicStroke(_thickness));
    g2d.drawLine(_startX, _startY, _endX, _endY);
}

不应该如何进行自定义绘制。 Graphics 是 Swing 中的共享上下文,它在任何给定绘制通道中绘制的所有组件之间共享。这意味着,除非您首先准备上下文,否则它仍将包含从最后一个组件绘制到它的内容。

同样,不建议覆盖paint,它在油漆链中的位置太高了,不正确的使用会导致无穷无尽的问题。

相反,您应该从paintComponent 开始,并确保调用它的super 方法,以维护绘制链操作...

protected void paintComponent(Graphics g)
{
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D)g.create();

    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

    g2d.setColor(_color);
    g2d.setStroke(new BasicStroke(_thickness));
    g2d.drawLine(_startX, _startY, _endX, _endY);

    g2d.dispose();
}

如果您正在修改上下文的状态(尤其是转换,但渲染提示很重要),您应该首先创建一个状态副本,并在完成后将其处理掉。这可以防止对状态的更改传递给其他组件,这可能会导致一些奇怪的渲染问题

查看Performing Custom PaintingPainting in AWT and Swing 了解更多详情

不透明度使用不当

这...

panel.setBackground(new Color(0, 0, 0, 0));

不是您创建透明组件的方式。 Swing 不知道如何处理透明(基于 alpha)的颜色。它只处理不透明和不透明的组件。这是通过使用opaque 属性来实现的。

panel.setOpaque(false);

违反 Swing 线程规则...

Swing 是单线程的,不是线程安全的。

public void run()
{
    while(true)
    {
        try
        {
            Thread.sleep(5000);
        }
        catch(Exception e) {}

        _panel.createLines();
    }
}

在这种情况下调用 createLines 存在线程问题的风险,因为 Swing 会在属性更新时尝试绘制属性,这可能会导致奇怪的绘制伪像。

请记住,油漆传递可能随时发生,大部分时间在您没有互动或不知情的情况下。

相反,我建议可能使用SwingWorker(但它有其局限性)或确保对createLines 的调用是在事件调度线程的上下文中完成的,通过使用EventQueue.invokeLaterEventQueue.invokeAndWait 取决于您的需要

更多详情请见Concurrency in Swing

更多线程!= 完成更多工作

拥有更多线程并不总是意味着您可以完成更多工作,这是一种平衡行为。

我个人会从一个线程开始,负责调度每个面板的更新,直接(通过createLines)或间接通过构建行信息本身并将结果传递给组件。

请记住,当您安排绘制通道时,Swing 将尝试通过减少绘制事件的数量来优化绘制,并简单地绘制更大的区域(根据需要)。此外,在使用非透明组件时,绘制任何一个组件可能需要其他重叠的组件也需要绘制。

当您扩大线程数量时,请考虑线程是否应该自己创建行,这意味着您不是在 EDT 中浪费时间,而是在单独的线程中执行操作,然后简单地将结果应用到组件。

同样,更多的组件可能会增加需要完成的工作量。

另一种方法是让Threads 充当“生产者”,生成List 行。然后单个组件将充当“消费者”,当新的List 行准备好时,它会重新绘制自己。

这可能需要您生成生产者和消费者之间的映射,以便您知道更新了哪一行 List,但这超出了问题的范围

【讨论】:

  • 嘿!非常感谢您的详细回复。我仍在阅读和处理您所写的内容。一连串的错误是意料之中的,我需要在短时间内编写一个大规模的图形演示,对 swing/awt 一无所知。所以你指给我的方向真的很有帮助,我会阅读材料,看看它是否解决了我的问题。主要问题是如何每秒多次更新多条直线 q 矩形 q 圆和其他图形对象,并且不会出现“闪烁”和延迟。再次感谢!全部实现后我会回信。
  • 也许SwingWorker 可能更有用,因为它可用于在后台生成新信息,publish 在前台以块的形式出现processed - 这可以释放每个从同步的复杂性来看线程栅栏的一侧
  • 如果您遇到“滞后”问题,则说明系统的某些部分遇到了问题。这可能是 GC 开销,this demonstrates 一种想法的演变,它使程序能够通过尝试减少 GC 开销来将大约 500 个对象渲染到 5,000 个以上。
  • 另一个想法可能是查看BufferStrategy and BufferCapabilities,它允许您完全控制(AKA 主动渲染)绘制过程
  • 虽然更复杂,但另一个想法是考虑“基于时间”的动画,其中某些事件发生在指定的时间段内,而不是定期发生,这允许动画改变它的速度(或它需要生成的更新数量)基于外部因素 - 我经常使用这个概念作为偏好,它可以通过使用“时间线”概念产生非常复杂的效果 - 这提供了灵活性,因为你可以改变“持续时间”,动画系统会围绕它自动调整
猜你喜欢
  • 1970-01-01
  • 2011-05-22
  • 1970-01-01
  • 1970-01-01
  • 2015-07-08
  • 1970-01-01
  • 2019-07-26
  • 1970-01-01
  • 2015-06-17
相关资源
最近更新 更多