java - 使用 Java Swing 高效重画线条

标签 java swing jpanel

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

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

Initial Screen

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

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

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

Screen after

在附上我的代码之前,让我先说明一下它使用的是 null 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();
    }
}

}

最佳答案

所以,一连串错误:

自定义绘画使用不正确...

这个...

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 中的共享上下文,它在任何给定绘制 channel 中绘制的所有组件之间共享。这意味着,除非您先准备好上下文,否则它仍将包含最后一个组件绘制的内容。

同样,不建议覆盖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)或通过构建线路信息本身并将结果传递给组件来间接。

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

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

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

另一种方法是让Thread充当“生产者”,生成行的List。然后,单个组件将充当“消费者”,当新的行列表准备就绪时,它将重新绘制自身。

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

关于java - 使用 Java Swing 高效重画线条,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49352884/

相关文章:

java - 例如,Java 中的最佳实践是创建我自己的界面,而不是改造中现有的界面

java - 为多个 JPanel 注册 1 个 mouseDragged 事件

java - Ubuntu 上的最大 TCP 连接数

Java 7 的 nio.file 包在创建新文件时非常慢

java - 从具有未知行和列的表中获取表模型

java - 如何从 JPanel swing 更改 JFrame 标签

java - 将图标添加到 jTable 的不同行中

java - 我如何通知用户他输入的值已被限制为 JSpinner 下限?

java - JLabel 宽度独立于文本长度

java - JFrame : cannot display two panels in jframe