为什么这个 JLabel 有 2 个位置?
Why does this JLabel have 2 positions?
我正在制作这个程序,其中由用户控制的 JLabel 将在 500x500 帧上移动,但由于某种原因它似乎有两个位置,当它移动时它只是在它们之间闪烁。这是什么原因造成的?
This is how it currently looks like
这是主要内容 window:
package guipkg1;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.*;
import guipkg1.PlayerMove;
public class KeyListenerPage extends JFrame implements KeyListener {
public static JLabel player = new JLabel();
public static boolean isGoingLeft = false;
public static boolean isGoingUp = false;
public static boolean isGoingRight = false;
public static boolean isGoingDown = false;
PlayerMove pmove = new PlayerMove();
KeyListenerPage(){
this.setDefaultCloseOperation(HIDE_ON_CLOSE);
this.setSize(500,500);
this.setLayout(null);
this.setVisible(false);
this.addKeyListener(this);
player.setBounds(0,0,50,50);
player.setText(":)");
this.add(player);
pmove.start();
}
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
if(e.getKeyCode()==37 || e.getKeyCode()==65) {
isGoingLeft = true;
System.out.println("[KeyListenerPage] going left");
} else if(e.getKeyCode()==38 || e.getKeyCode()==87) {
isGoingUp = true;
System.out.println("[KeyListenerPage] going up");
} else if(e.getKeyCode()==39 || e.getKeyCode()==68) {
isGoingRight = true;
System.out.println("[KeyListenerPage] going right");
} else if(e.getKeyCode()==40 || e.getKeyCode()==83) {
isGoingDown = true;
System.out.println("[KeyListenerPage] going down");
}
}
@Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
if(e.getKeyCode()==37 || e.getKeyCode()==65) {
isGoingLeft = false;
System.out.println("[KeyListenerPage] stopped going left");
} else if(e.getKeyCode()==38 || e.getKeyCode()==87) {
isGoingUp = false;
System.out.println("[KeyListenerPage] stopped going up");
} else if(e.getKeyCode()==39 || e.getKeyCode()==68) {
isGoingRight = false;
System.out.println("[KeyListenerPage] stopped going right");
} else if(e.getKeyCode()==40 || e.getKeyCode()==83) {
isGoingDown = false;
System.out.println("[KeyListenerPage] stopped going down");
}
}
}
和移动播放器的线程:
package guipkg1;
import javax.swing.JLabel;
public class PlayerMove extends Thread implements Runnable {
boolean movingLeft = KeyListenerPage.isGoingLeft;
boolean movingUp = KeyListenerPage.isGoingUp;
boolean movingRight = KeyListenerPage.isGoingRight;
boolean movingDown = KeyListenerPage.isGoingDown;
JLabel player = KeyListenerPage.player;
boolean canMoveLeft = false;
boolean canMoveRight = true;
boolean canMoveUp = false;
boolean canMoveDown = true;
public void run() {
System.out.println("running");
while(true) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
movingLeft = KeyListenerPage.isGoingLeft;
movingUp = KeyListenerPage.isGoingUp;
movingRight = KeyListenerPage.isGoingRight;
movingDown = KeyListenerPage.isGoingDown;
if(movingLeft && canMoveLeft) {
player.setBounds(player.getWidth(), player.getHeight(), player.getX()-10, player.getY());
player.setVisible(true);
if(player.getX()<=0) {
canMoveLeft = false;
}
if(player.getX()<490) {
canMoveRight = true;
}
}
if(movingRight && canMoveRight) {
player.setBounds(player.getWidth(), player.getHeight(), player.getX()+10, player.getY());
player.setVisible(true);
if(player.getX()>0) {
canMoveLeft = true;
}
if(player.getX()>=490) {
canMoveRight = false;
}
}
if(movingUp && canMoveUp) {
player.setBounds(player.getWidth(), player.getHeight(), player.getX(), player.getY()-10);
player.setVisible(true);
if(player.getY()<=0) {
canMoveUp = false;
}
if(player.getY()<490) {
canMoveDown = true;
}
}
if(movingDown && canMoveDown) {
player.setBounds(player.getWidth(), player.getHeight(), player.getX(), player.getY()+10);
player.setVisible(true);
if(player.getY()>0) {
canMoveUp = true;
}
if(player.getY()>=490) {
canMoveDown = false;
}
}
}
}
}
为什么会闪烁?
因为您对 player.setBounds()
的参数顺序错误。
根据 JLabel#setBounds() JavaDoc 参数顺序为 x
、y
、width
、height
.
但是您的代码调用带有参数的方法
player.setBounds(player.getWidth(), player.getHeight(), player.getX(), player.getY()+10);
它应该在哪里
player.setBounds(player.getX(), player.getY()+10, player.getWidth(), player.getHeight());
另请注意 Swing 不是 thread-safe。如果您想更改显示的 Swing 组件上的任何内容,您必须在 Swing EDT 上进行,例如将 PlayerMove.run()
重写为
public void run() {
System.out.println("running");
while(true) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
SwingUtilities.invokeLater(this::move);
}
}
并将剩余代码从 PlayerMove.run()
移动到新的 move()
方法中:
private void move() {
movingLeft = KeyListenerPage.isGoingLeft;
movingUp = KeyListenerPage.isGoingUp;
movingRight = KeyListenerPage.isGoingRight;
movingDown = KeyListenerPage.isGoingDown;
if(movingLeft && canMoveLeft) {
player.setBounds(player.getX()-10, player.getY(), player.getWidth(), player.getHeight());
player.setVisible(true);
if(player.getX()<=0) {
canMoveLeft = false;
}
if(player.getX()<490) {
canMoveRight = true;
}
}
if(movingRight && canMoveRight) {
player.setBounds(player.getX()+10, player.getY(), player.getWidth(), player.getHeight());
player.setVisible(true);
if(player.getX()>0) {
canMoveLeft = true;
}
if(player.getX()>=490) {
canMoveRight = false;
}
}
if(movingUp && canMoveUp) {
player.setBounds(player.getX(), player.getY()-10, player.getWidth(), player.getHeight());
player.setVisible(true);
if(player.getY()<=0) {
canMoveUp = false;
}
if(player.getY()<490) {
canMoveDown = true;
}
}
if(movingDown && canMoveDown) {
player.setBounds(player.getX(), player.getY()+10, player.getWidth(), player.getHeight());
player.setVisible(true);
if(player.getY()>0) {
canMoveUp = true;
}
if(player.getY()>=490) {
canMoveDown = false;
}
}
}
请注意,这个解决方案仍然适用于单独的线程(即 PlayerMove
仍然扩展 Thread
并且您仍然必须在 KeyListenerPage
中调用 pmove.start();
)
正如 MadProgrammer 已经评论过的,更好的方法是 use a Swing Timer
这样的解决方案会稍微改变您的代码。
PlayerMove
class会变成
public class PlayerMove implements ActionListener {
boolean movingLeft = false;
boolean movingUp = false;
boolean movingRight = false;
boolean movingDown = false;
JLabel player = KeyListenerPage.player;
boolean canMoveLeft = false;
boolean canMoveRight = true;
boolean canMoveUp = false;
boolean canMoveDown = true;
@Override
public void actionPerformed(ActionEvent e) {
// here is all the moving code,
// i.e. what in your solution was in the run() method
// but without the looping
// The Timer will call this every 20ms for you
}
}
并且在 KeyListenerPage()
构造函数中,您将替换
pmove.start();
和
Timer timer = new Timer(20, pmove);
timer.start();
您的代码中不再需要“永无止境的循环” - Timer
现在负责每 20 毫秒调用一次 pmove.actionPerformed()
。
这段代码:
public void run() {
System.out.println("running");
while(true) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
SwingUtilities.invokeLater(this::move);
}
}
可以通过将 move()
方法替换为 actionPerformed()
方法(方法中的代码不需要更改!)并将 pmove.start();
替换为两个来完全删除线条 - 恕我直言,明显改进。
我正在制作这个程序,其中由用户控制的 JLabel 将在 500x500 帧上移动,但由于某种原因它似乎有两个位置,当它移动时它只是在它们之间闪烁。这是什么原因造成的?
This is how it currently looks like
这是主要内容 window:
package guipkg1;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.*;
import guipkg1.PlayerMove;
public class KeyListenerPage extends JFrame implements KeyListener {
public static JLabel player = new JLabel();
public static boolean isGoingLeft = false;
public static boolean isGoingUp = false;
public static boolean isGoingRight = false;
public static boolean isGoingDown = false;
PlayerMove pmove = new PlayerMove();
KeyListenerPage(){
this.setDefaultCloseOperation(HIDE_ON_CLOSE);
this.setSize(500,500);
this.setLayout(null);
this.setVisible(false);
this.addKeyListener(this);
player.setBounds(0,0,50,50);
player.setText(":)");
this.add(player);
pmove.start();
}
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
if(e.getKeyCode()==37 || e.getKeyCode()==65) {
isGoingLeft = true;
System.out.println("[KeyListenerPage] going left");
} else if(e.getKeyCode()==38 || e.getKeyCode()==87) {
isGoingUp = true;
System.out.println("[KeyListenerPage] going up");
} else if(e.getKeyCode()==39 || e.getKeyCode()==68) {
isGoingRight = true;
System.out.println("[KeyListenerPage] going right");
} else if(e.getKeyCode()==40 || e.getKeyCode()==83) {
isGoingDown = true;
System.out.println("[KeyListenerPage] going down");
}
}
@Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
if(e.getKeyCode()==37 || e.getKeyCode()==65) {
isGoingLeft = false;
System.out.println("[KeyListenerPage] stopped going left");
} else if(e.getKeyCode()==38 || e.getKeyCode()==87) {
isGoingUp = false;
System.out.println("[KeyListenerPage] stopped going up");
} else if(e.getKeyCode()==39 || e.getKeyCode()==68) {
isGoingRight = false;
System.out.println("[KeyListenerPage] stopped going right");
} else if(e.getKeyCode()==40 || e.getKeyCode()==83) {
isGoingDown = false;
System.out.println("[KeyListenerPage] stopped going down");
}
}
}
和移动播放器的线程:
package guipkg1;
import javax.swing.JLabel;
public class PlayerMove extends Thread implements Runnable {
boolean movingLeft = KeyListenerPage.isGoingLeft;
boolean movingUp = KeyListenerPage.isGoingUp;
boolean movingRight = KeyListenerPage.isGoingRight;
boolean movingDown = KeyListenerPage.isGoingDown;
JLabel player = KeyListenerPage.player;
boolean canMoveLeft = false;
boolean canMoveRight = true;
boolean canMoveUp = false;
boolean canMoveDown = true;
public void run() {
System.out.println("running");
while(true) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
movingLeft = KeyListenerPage.isGoingLeft;
movingUp = KeyListenerPage.isGoingUp;
movingRight = KeyListenerPage.isGoingRight;
movingDown = KeyListenerPage.isGoingDown;
if(movingLeft && canMoveLeft) {
player.setBounds(player.getWidth(), player.getHeight(), player.getX()-10, player.getY());
player.setVisible(true);
if(player.getX()<=0) {
canMoveLeft = false;
}
if(player.getX()<490) {
canMoveRight = true;
}
}
if(movingRight && canMoveRight) {
player.setBounds(player.getWidth(), player.getHeight(), player.getX()+10, player.getY());
player.setVisible(true);
if(player.getX()>0) {
canMoveLeft = true;
}
if(player.getX()>=490) {
canMoveRight = false;
}
}
if(movingUp && canMoveUp) {
player.setBounds(player.getWidth(), player.getHeight(), player.getX(), player.getY()-10);
player.setVisible(true);
if(player.getY()<=0) {
canMoveUp = false;
}
if(player.getY()<490) {
canMoveDown = true;
}
}
if(movingDown && canMoveDown) {
player.setBounds(player.getWidth(), player.getHeight(), player.getX(), player.getY()+10);
player.setVisible(true);
if(player.getY()>0) {
canMoveUp = true;
}
if(player.getY()>=490) {
canMoveDown = false;
}
}
}
}
}
为什么会闪烁?
因为您对 player.setBounds()
的参数顺序错误。
根据 JLabel#setBounds() JavaDoc 参数顺序为 x
、y
、width
、height
.
但是您的代码调用带有参数的方法
player.setBounds(player.getWidth(), player.getHeight(), player.getX(), player.getY()+10);
它应该在哪里
player.setBounds(player.getX(), player.getY()+10, player.getWidth(), player.getHeight());
另请注意 Swing 不是 thread-safe。如果您想更改显示的 Swing 组件上的任何内容,您必须在 Swing EDT 上进行,例如将 PlayerMove.run()
重写为
public void run() {
System.out.println("running");
while(true) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
SwingUtilities.invokeLater(this::move);
}
}
并将剩余代码从 PlayerMove.run()
移动到新的 move()
方法中:
private void move() {
movingLeft = KeyListenerPage.isGoingLeft;
movingUp = KeyListenerPage.isGoingUp;
movingRight = KeyListenerPage.isGoingRight;
movingDown = KeyListenerPage.isGoingDown;
if(movingLeft && canMoveLeft) {
player.setBounds(player.getX()-10, player.getY(), player.getWidth(), player.getHeight());
player.setVisible(true);
if(player.getX()<=0) {
canMoveLeft = false;
}
if(player.getX()<490) {
canMoveRight = true;
}
}
if(movingRight && canMoveRight) {
player.setBounds(player.getX()+10, player.getY(), player.getWidth(), player.getHeight());
player.setVisible(true);
if(player.getX()>0) {
canMoveLeft = true;
}
if(player.getX()>=490) {
canMoveRight = false;
}
}
if(movingUp && canMoveUp) {
player.setBounds(player.getX(), player.getY()-10, player.getWidth(), player.getHeight());
player.setVisible(true);
if(player.getY()<=0) {
canMoveUp = false;
}
if(player.getY()<490) {
canMoveDown = true;
}
}
if(movingDown && canMoveDown) {
player.setBounds(player.getX(), player.getY()+10, player.getWidth(), player.getHeight());
player.setVisible(true);
if(player.getY()>0) {
canMoveUp = true;
}
if(player.getY()>=490) {
canMoveDown = false;
}
}
}
请注意,这个解决方案仍然适用于单独的线程(即 PlayerMove
仍然扩展 Thread
并且您仍然必须在 KeyListenerPage
中调用 pmove.start();
)
正如 MadProgrammer 已经评论过的,更好的方法是 use a Swing Timer
这样的解决方案会稍微改变您的代码。
PlayerMove
class会变成
public class PlayerMove implements ActionListener {
boolean movingLeft = false;
boolean movingUp = false;
boolean movingRight = false;
boolean movingDown = false;
JLabel player = KeyListenerPage.player;
boolean canMoveLeft = false;
boolean canMoveRight = true;
boolean canMoveUp = false;
boolean canMoveDown = true;
@Override
public void actionPerformed(ActionEvent e) {
// here is all the moving code,
// i.e. what in your solution was in the run() method
// but without the looping
// The Timer will call this every 20ms for you
}
}
并且在 KeyListenerPage()
构造函数中,您将替换
pmove.start();
和
Timer timer = new Timer(20, pmove);
timer.start();
您的代码中不再需要“永无止境的循环” - Timer
现在负责每 20 毫秒调用一次 pmove.actionPerformed()
。
这段代码:
public void run() {
System.out.println("running");
while(true) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
SwingUtilities.invokeLater(this::move);
}
}
可以通过将 move()
方法替换为 actionPerformed()
方法(方法中的代码不需要更改!)并将 pmove.start();
替换为两个来完全删除线条 - 恕我直言,明显改进。