我正在编写一个必须每隔一段时间重新绘制的挥杆程序。出于某种原因,即使我指定较低的延迟,javax.swing.Timer
似乎也只是每800-900ms重新绘制一次。 (例如100ms)我认为延迟可能是由于repaint()
方法需要800ms才能运行,但我计时它只需要0.3ms。我尝试使用Thread.sleep()
并按所需间隔重新绘制。任何人都可以帮忙解释为什么会这样吗?
我正在尝试做的事情似乎是javax.swing.Timer
的预期目的,所以我很困惑为什么它会如此减慢代码的速度。
主要驾驶员类:
import java.awt.Dimension;
import javax.swing.JFrame;
public class GUIDriver{
public AnimationPanel animationPanel;
public static void main(String[] args){
GUIDriver gui = new GUIDriver();
gui.createAndShowGUI();
}
public void createAndShowGUI(){
animationPanel = AnimationPanel(50);
JFrame frame = new JFrame("Animations");
frame.setContentPane(animationPanel);
frame.setPreferredSize(new Dimension(1015, 840));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.validate();
frame.doLayout();
frame.setVisible(true);
animationPanel.draw(); //only here when using Thread.sleep() method
}
}
自定义JPanel
扩展程序:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Point2D;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Scanner;
import java.text.SimpleDateFormat;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
import javax.swing.Timer;
/**
* This extension of the JPanel overrides the paintComponent method to provide a framework for
* creating 2-D animations.
*/
@SuppressWarnings("serial")
public class AnimationPanel extends JPanel implements ActionListener{
public ArrayList<LeakInstance> data;
public Timer timer;
public SimpleDateFormat dateOutput = new SimpleDateFormat("MM/dd/yyyy");
BufferedImage background;
public Color lineColor = Color.black;
public Color under = Color.blue;
public Color over = Color.red;
public Font defaultFont = new Font("default", Font.BOLD, 14);
public float cutoff = 50;
public int timeStep = 0;
public long startTime = 0;
public Graphics2D screen;
public AnimationPanel(float cutoff) {
super();
setLayout(null);
read("Data.txt");
this.cutoff = cutoff;
timer = new Timer(100, this); //commented out when using Thread.sleep()
try {
background = ImageIO.read(new File("img.png"));
} catch (IOException e) {
e.printStackTrace();
}
repaint();
timer.start(); //commented out when using Thread.sleep()
}
/**
* This method overrides JPanel's paintComponent method in order to customize the rendering of 2-D graphics
* and thus make animation possible. It is not called directly; it is called by repaint() which fully refreshes
* the screen.
*/
@Override
public void paintComponent(Graphics g) {
ArrayList<String> drawn = new ArrayList<String>();
screen = (Graphics2D)g;
RenderingHints renderHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
renderHints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
screen.setRenderingHints(renderHints);
screen.drawImage(background.getScaledInstance(1000, 800, Image.SCALE_SMOOTH), 0, 0, this);
g.setFont(defaultFont);
screen.setColor(Color.orange);
screen.fillRect(485, 735, 100, 20); //cover old date
screen.setColor(lineColor);
screen.drawString(dateOutput.format(data.get(timeStep).getDate()), 500, 750);
screen.drawString(Integer.toString(timeStep), 300, 750);
System.out.println((System.nanoTime() - startTime)/1e9f);
startTime = System.nanoTime();
float x, y;
float z;
int xoffset, yoffset;
for(int i = 0; drawn.size() < 24 && (timeStep-i) > -1; i++){
if(!drawn.contains(data.get(timeStep-i).getName())){
xoffset = 0;
yoffset = 15;
String name = data.get(timeStep-i).getName();
drawn.add(name);
x = data.get(timeStep-i).getLocation().x;
y = data.get(timeStep-i).getLocation().y;
z = data.get(timeStep-i).getZ();
if(z > cutoff)
screen.setColor(over);
else
screen.setColor(under);
switch(name){
//various cases to change x or y offset
}
screen.drawString(Float.toString(z), x+xoffset, y+yoffset);
screen.setColor(lineColor);
screen.drawLine((int)x-2, (int)y, (int)x+2,(int) y);
screen.drawLine((int)x, (int)y-2, (int)x, (int)y+2);
}
}
}
public void draw(){
try{
for(; timeStep < data.size()-1; timeStep++){
Thread.sleep(100);
repaint();
}
}catch(Exception e){
e.printStackTrace();
}
}
public void read(String filename){
File file = new File(filename);
data = new ArrayList<MyType>(100);
try(Scanner scan = new Scanner(file)){
while(scan.hasNextLine())
data.add(new MyType(scan.next(), scan.next(), scan.nextFloat(),
new Point2D.Float(scan.nextFloat(), scan.nextFloat())));
}catch (Exception e){
e.printStackTrace();
}
Collections.sort(data);
}
@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource().equals(timer)){
if(timeStep < data.size()-1){
timeStep++;
repaint();
}
else
timer.stop();
}
}
}
答案 0 :(得分:3)
我遇到的主要问题是
screen.drawImage(background.getScaledInstance(1000, 800, Image.SCALE_SMOOTH), 0, 0, this);
这会导致repaint
事件不断发生,同时将更新重绘时间缩短为0.25毫秒
当我预先缩放图像时(我必须将其更改为Image
),例如......
try {
background = ImageIO.read(new File("C:\\Users\\Shane Whitehead\\Dropbox\\Wallpapers\\5781217115_4182ee16da_o.jpg"));
background = background.getScaledInstance(1000, 800, Image.SCALE_SMOOTH);
} catch (IOException e) {
e.printStackTrace();
}
我能够获得0.1ms的重绘时间。
我尝试在data
列表中使用100,1000和10,000个元素而没有太多问题(只是为了好玩,我尝试了100,000,仍然重新绘制了0.1ms)
您需要特别注意确保油漆工艺得到很好的优化
根据评论更新
然而,它仍然没有解释为什么用油漆方法 〜。3ms导致定时器耗时~800ms。或者为什么使用 javax.swing.Timer似乎比使用慢一个数量级 Thread.sleep()方法
实际上确实如此,但您需要了解事件调度线程,事件队列和RepaintManager
的工作原理。
基本上...
paintComponent
被呼叫,您拨打getScaledInstance
,通过repaint
触发ImageObserver
请求。 getScaledInstance
速度慢,100毫秒慢。 RepaintManager
优化重绘请求并尝试减少在EDT上发布的绘制事件的数量,这有时是好的,有时是坏的。这些绘制事件放在事件队列javax.swing.Timer
触发事件,此事件被放置到事件队列中,由EDT处理。现在,这就是它变得很奇怪的地方#34; {em>&#34;如果coalesce为true(默认值),则javax.swing.Timer
进行优化,只允许一个Runnable在EventQueue上排队并等待&#34; - 所以如果有预先存在的&#34;计时器&#34;事件已经在事件队列中,没有新事件发布。actionPerformed
方法,也是在EDT的上下文中,从处理其他事件开始(甚至很短的时间)......这一切都加起来...... < / LI>
paintComponent
,重复...... 所以,可能发生的事情,由getScaledInstance
引起的重复更新相距甚远,以防止RepaintManager
优化这些调用,这会给EDT带来压力,{{{1} 1}}正在逐渐消失,但是因为事件可能没有得到足够快的处理,所以有些东西会被丢弃,从长远来看,这会导致&#34; paint&#34;进一步分开。
Timer
方法不会遭遇这些问题的原因是因为它可以通过新的绘制请求向EDT发送垃圾邮件,而不考虑事件队列的状态......
此外,我可能会破坏您Thread
更新,这意味着在处理Thread
列表中的所有项目之前,不会绘制任何内容,请查看Initial Threads以获取更多详细信息< / p>
有关绘画如何在Swing中工作的更多详细信息,请查看Painting in AWT and Swing
您可能还想查看The Perils of Image.getScaledInstance()和Java: maintaining aspect ratio of JPanel background image,了解有关缩放的更多详情......
更新了一些其他测试
所以我补充说......
data
要测试缩放过程,通常需要大约200-240 ms。剩下的油漆工艺只增加了大约10毫秒。
我在使用long scaleStart = System.currentTimeMillis();
screen.drawImage(background.getScaledInstance(getWidth(), getHeight(), Image.SCALE_SMOOTH), 0, 0, this);
System.out.println("Scaled in: " + ((System.currentTimeMillis()- scaleStart) / 1000f));
时这样做了,所以我没有从转换合并中获得额外的好处。
通过预先缩放图像,我获得了0.1ms的恒定更新(有和没有timer.setCoalesce(false);
)