我正在尝试找到创建动态 Java 图形应用程序的最有效方法。我想构建一个大屏幕,有许多不同的部分,所有这些部分都使用不同的线程重新绘制或更新,以使屏幕看起来“活跃”。 然而,我最初的尝试是可怕的,屏幕变得非常慢,有错误等 - 所以我想我需要创建不同的模块(JPanels),每个模块都包含其他图形部分(线,圆等),并且每个单独重绘不同的 JPanel(需要时),而不是整个主面板(或框架)。
所以我编写了一个小演示程序 - 我的程序包含一个窗口,带有多个面板,包裹在我的名为“MyPanel”的对象中 - 每个这样的 MyPanel 包含几条绘制的线(我有一个 Line 对象),所有线都从从左上角开始,具有不同的长度和角度)。每个不同的 MyPanel 都有不同的线条颜色(参见此图片)。
我实例化了多个工作线程,每个线程指定一个 MyPanel - 工作线程等待 5 秒,然后尝试按以下方式重新绘制所有线条:
- 从 JPanel (MyPanel) 中删除所有现有行。
- 创建具有不同角度和长度的新线条。
- 通过调用 super.repaint() 重绘 JPanel (MyPanel) 这就是整个目的,仅更新此面板,让它重绘自身及其所有子部分,而不是整个程序强>
但是,发生了一些奇怪的事情:当重新绘制面板时,每个面板都以可能也包含所有其他 MyPanel 的方式重新绘制,或者以某种方式镜像主屏幕 - 非常不清楚这里到底发生了什么。此外,面板的所有“背景不透明度”都消失了(参见此图片)。
在附上我的代码之前,让我先说明一下它使用的是 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 Painting和 Painting 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.invokeLater
或 EventQueue.invokeAndWait
参见Concurrency in Swing了解更多详情。
更多线程!=完成更多工作
拥有更多线程并不总是意味着您可以完成更多工作,这是一种平衡行为。
就我个人而言,我会从单个线程开始,负责安排对每个面板的更新,直接(通过 createLines)或通过构建线路信息本身并将结果传递给组件来间接。
请记住,当您安排绘制 channel 时,Swing 将尝试通过减少绘制事件的数量来优化绘制,并简单地绘制更大的区域(根据需要)。此外,在使用非不透明组件时,绘制任何一个组件可能需要绘制其他重叠的组件。
当您扩展线程数量时,请考虑线程是否应该自行创建行,这意味着您不是在 EDT 中浪费时间,而是在单独的线程中执行操作,然后将结果简单地应用到组件。
同样,更多的组件可能会增加需要完成的工作量。
另一种方法是让Thread
充当“生产者”,生成行的List
。然后,单个组件将充当“消费者”,当新的行列表准备就绪时,它将重新绘制自身。
这可能需要您在生产者和消费者之间生成映射,以便您知道哪个 List
行已更新,但这超出了问题的范围
关于java - 使用 Java Swing 高效重画线条,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49352884/