java - 使用计时器和paintComponent 进行渲染会导致没有渲染发生
问题描述
我一直在尝试开发一个游戏,我正处于让所有单独的组件工作的初步阶段(因此我的代码很糟糕),在尝试渲染一个移动的正方形时,我遇到了一个问题。paintComponent 方法将绘制正方形,但是当我使用计时器时,我无法让它工作。
我尝试过移动计时器方法,在不同点使用重绘,并在某一点报废所有内容并从头开始重做。
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.time.Clock;
import java.time.Duration;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JFrame;
public class InputTest extends MyPanel implements KeyListener {
private static int x;
private static int y;
public static void main(String[] args) {
JFrame f = new JFrame();
InputTest p = new InputTest();
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setSize(500, 500);
p.setSize(500, 500);
f.add(p);
f.addKeyListener(new InputTest());
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
x++;
y++;
if (x > 500) {
x = 0;
}
if (y > 500) {
y = 0;
}
System.out.println("Tick"+x+","+y);
}
}, 0, 500);
p.setVisible(true);
f.setVisible(true);
}
@Override
public void keyPressed(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
public void update() {
}
@Override
protected void paintComponent(Graphics g) {
g.setColor(Color.BLACK);
while(x!=501&&y!=501) {
g.fillRect(x, y, 30, 30);
repaint();
}
}
}
正方形应该从左上角缓慢移动到右下角。相反,我没有得到任何图像。
解决方案
Swing 是单线程的
当 Swing 触发绘制周期时,直到层次结构中的所有组件都完成操作后,结果才会呈现到屏幕上。
这意味着当您执行类似...
@Override
protected void paintComponent(Graphics g) {
g.setColor(Color.BLACK);
while (x != 501 && y != 501) {
g.fillRect(x, y, 30, 30);
repaint();
}
}
在您退出该方法之前,什么都不会发生paintComponent
,这意味着,在这种情况下,您绘制的最后一件事就是将要渲染的内容(或者在这种情况下是一个不错的连续性)。
作为旁注,您永远不应该repaint
直接或间接地从绘图方法中调用,这是消耗所有 CPU 周期并使您的系统崩溃的真正好方法。
相反,绘画应该简单地描绘当前/期望的状态。此外,除非您真的知道自己在做什么,否则您应该始终super.paintComponent
先打电话,例如...
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
g.fillRect(x, y, 30, 30);
}
有关更多详细信息,请参阅Swing 中的并发
Swing 不是线程安全的
您应该始终避免从事件调度线程之外更新 UI。这还包括更新 UI 所依赖的状态。这样做会导致各种难以诊断的故障。
由于java.util.Timer
使用它自己的Thread
来执行它的调度,所以它不适合在 Swing 中使用。相反,您应该使用javax.swing.Timer
,它将在 EDT 外等待,但触发它分配ActionListener
了 EDT 的上下文
例如...
Timer timer = new Timer(500, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
x++;
y++;
if (x > 500) {
x = 0;
}
if (y > 500) {
y = 0;
}
// Trigger a new paint cycle...
repaint();
}
});
timer.start();
有关更多详细信息,请参阅如何使用摆动计时器
所有“其他”的东西
KeyListener
最好避免,原因有很多。使用Key Bindings API,它将为您省去很多麻烦。
组件的可用大小总是窗口的大小减去它的边框装饰。这意味着 usingf.setSize(500, 500)
将使可用的上下文区域更小(取决于您运行代码的平台)。
最好覆盖组件的getPreferredSize
方法并返回大小提示。然后,您可以使用JFrame#pack
“打包”内容周围的窗口,使窗口大于内容大小,但您不会浪费时间试图弄清楚为什么事情不会在您期望的地方停止。
可运行示例
我还修改了代码以消除对 的依赖static
,这从来都不是一个好主意
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class InputTest extends JPanel {
private int x;
private int y;
public static void main(String[] args) {
JFrame f = new JFrame();
InputTest p = new InputTest();
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.add(p);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public InputTest() {
Timer timer = new Timer(500, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
x++;
y++;
if (x > 500) {
x = 0;
}
if (y > 500) {
y = 0;
}
// Trigger a new paint cycle...
repaint();
}
});
timer.start();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
@Override
protected void paintComponent(Graphics g) {
g.setColor(Color.BLACK);
g.fillRect(x, y, 30, 30);
}
}
推荐阅读
- python - Python mrjob - 找到 10 个最长的单词,但 mrjob 返回重复的单词
- java - 客户化程度报告
- flutter - 为什么尽管观察者的状态发生变化,但应用程序重启后 Flutter BloC UI 没有更新?
- python - 如何在散点图中以不同颜色绘制顶部 %1?
- jquery - jquery:发现来自ajax请求的html响应不起作用
- html - -webkit-flex-direction 使用 React 时未在浏览器上显示
- python - 尝试将数据帧列表连接到单个数据帧时,“重新索引仅对具有唯一值的索引对象有效”
- javascript - 在 Chrome 浏览器中使用 API 时出错
- postgresql - 如果 NEW 等于 OLD 而没有触发器,则 Postgres 跳过更新
- typescript - TypeScript 检查字符串匹配类型