[英]Redrawing lines with Java Swing efficiently
我试图找到创建动态Java图形应用程序的最有效方法。 我想构建一个大屏幕,其中包含许多不同的部分,所有这些部分都使用不同的线程重新绘制或更新,以使屏幕看起来“处于活动状态”。 但是,我这样做的最初尝试太可怕了,屏幕变得非常缓慢,有错误等-所以我认为我需要创建不同的模块(JPanels),每个模块都包含其他图形部分(线,圆等),并且每个(需要时)分别重绘不同的JPanel,而不是整个主面板(或框架)。
因此,我编写了一个小型演示程序-我的程序包含一个窗口,带有多个面板,包装在称为“ MyPanel”的对象中-每个这样的MyPanel都包含多条绘制的线(我有一个Line对象),所有线均从顶部开始-左角,并具有不同的长度和角度)。 每个不同的MyPanel都有不同的线条颜色(请参见下图)。
我实例化了几个工作线程,每个工作线程都指定一个MyPanel-工作线程等待5秒钟,然后尝试按以下方式重新绘制所有行:
但是,发生了一些奇怪的事情:重新绘制面板时,每个面板都以可能也包含所有其他MyPanels的方式重新绘制,或者以某种方式镜像了主屏幕-尚不清楚此处到底发生了什么。 而且,面板的所有“背景不透明度”都消失了(请参见此图像)。
在附加代码之前,我要说它使用一个空的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中的共享上下文,它在任何给定的绘制通道中绘制的所有组件之间都是共享的。 这意味着,除非您首先准备了上下文,否则它将仍然包含最后一个组件所绘制的内容。
同样,不建议覆盖paint
, 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();
}
如果要修改上下文的状态(特别是转换,但是渲染提示很重要),则应首先创建状态的副本并在完成后将其处理。 这样可以防止将状态更改传递给其他组件,这可能导致一些奇怪的渲染问题
有关更多详细信息,请参见在AWT和Swing中执行自定义绘画和绘画。
这个...
panel.setBackground(new Color(0, 0, 0, 0));
不是创建透明组件的方式。 Swing不知道如何处理透明(基于Alpha)的颜色。 它仅处理不透明和不透明组件。 这是通过使用opaque
属性来实现的。
panel.setOpaque(false);
秋千是单线程的,不是线程安全的。
public void run()
{
while(true)
{
try
{
Thread.sleep(5000);
}
catch(Exception e) {}
_panel.createLines();
}
}
在这种情况下调用createLines
带来线程问题的风险,因为Swing会在更新属性时尝试绘制属性,这会导致奇怪的绘制伪像。
请记住,大多数情况下,涂装合格可能随时发生,而无需您的干预或了解。
相反,我建议您也许使用SwingWorker
(但是它有其局限性),或者确保根据事件的需要,通过使用EventQueue.invokeLater
或EventQueue.invokeAndWait
来使用事件调度线程的上下文来完成对createLines
的调用
有关更多详细信息,请参见Swing中的并发 。
拥有更多线程并不总是意味着您可以完成更多工作,这是平衡的举动。
我个人将从一个线程开始,负责直接(通过createLines
)或通过构建行信息本身并将结果传递给组件来间接调度每个面板的更新。
请记住,当您安排绘制过程时,Swing会尝试通过减少绘制事件的次数并简单地绘制较大的区域(根据需要)来优化绘制。 同样,在使用不透明的组件时,绘制任何一个组件可能都需要绘制其他重叠的组件。
随着线程数量的增加,请考虑线程是否应该自己创建线,这意味着,不是在EDT中浪费时间,而是在单独的线程中执行操作,然后将结果简单地应用于组件。
同样,更多的组件可能会增加需要完成的工作量。
另一种方法是让Thread
充当生成行List
的“生产者”。 然后,单个组件将充当“消费者”,并且当准备好新的行List
时,它将重新绘制自身。
这可能需要您在生产者和消费者之间产生一个映射,因此您知道哪个行List
已更新,但这超出了问题的范围。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.