使用setText更新JLabel时JFrame布局重置问题求助
DDR游戏UI布局重置问题
我正在为课程制作一款DDR类游戏,制作了scores_panel面板用于显示分数与连击数。但调用该面板的更新方法(内部通过JLabel的setText更新内容)时,JFrame会重置到初始布局,所有组件回到默认位置。即使直接在主面板添加JLabel并通过按钮输入更新,也会出现此问题。另外按下正确按键时似乎陷入循环,我想了解该问题的原因。我尝试过用按钮测试,发现并非仅JLabel的问题,还尝试将分数面板放入新面板,问题依旧。
主代码
package ddr.view; import java.awt.*; import java.awt.event.*; import javax.swing.*; import ddr.ScreenObserver; import ddr.gameObserver; import ddr.model.Game; public class GameplayGUI implements KeyListener, gameObserver { //UI during gameplay JFrame frame2; ImagePanel mainPanel; JPanel arrowsPanel; JPanel space1; JPanel space2; JPanel space3; JPanel space4; JLabel right; ImageIcon rights; JLabel left; ImageIcon lefts; JLabel up; ImageIcon ups; JLabel down; ImageIcon downs; JLabel back; ImageIcon backs; JPanel gridArrows; JPanel spawnpoint; ImageIcon leftIcon; ImageIcon downIcon; ImageIcon upIcon; ImageIcon rightIcon; JLabel leftMove; JLabel downMove; JLabel upMove; JLabel rightMove; int bottom_row = 450; scores_panel score; Game gamerGame; ScreenObserver controller; int scorex; int scorey; private Timer leftTimer, downTimer, upTimer, rightTimer; public GameplayGUI(ScreenObserver screen, Game game) { //javaswing constructor frame2 = new JFrame("Gameplay"); frame2.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); backs = new ImageIcon(getClass().getClassLoader().getResource("game_background.png")); mainPanel = new ImagePanel(backs); mainPanel.setLayout(new FlowLayout(FlowLayout.CENTER,30,0)); mainPanel.setPreferredSize(new Dimension(700,500)); frame2.addKeyListener(this); spawnpoint = new JPanel(new FlowLayout(FlowLayout.CENTER,30,0)); spawnpoint.setOpaque(false); int stepSize =2; //creating moving arrows and adding them to main panel leftIcon = new ImageIcon(getClass().getClassLoader().getResource("left.gif")); leftMove = new JLabel(leftIcon); downIcon = new ImageIcon(getClass().getClassLoader().getResource("down.gif")); downMove = new JLabel(downIcon); upIcon = new ImageIcon(getClass().getClassLoader().getResource("up.gif")); upMove = new JLabel(upIcon); rightIcon = new ImageIcon(getClass().getClassLoader().getResource("right.gif")); rightMove = new JLabel(rightIcon); mainPanel.add(leftMove); mainPanel.add(downMove); mainPanel.add(upMove); mainPanel.add(rightMove); score = new scores_panel(); mainPanel.add(score); //making the arrow receptors panel and adding it to a grid arrowsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER,30,0)); arrowsPanel.setPreferredSize(new Dimension(700,100)); arrowsPanel.setBackground(Color.gray); arrowsPanel.setOpaque(false); space1 = new JPanel(); space2 = new JPanel(); space3 = new JPanel(); space4 = new JPanel(); space1.setPreferredSize(new Dimension(233,135)); space2.setPreferredSize(new Dimension(233,135)); space1.setOpaque(false); space2.setOpaque(false); gridArrows = new JPanel(new GridLayout(5, 1)); gridArrows.setOpaque(false); gridArrows.add(space1); gridArrows.add(space2); gridArrows.add(arrowsPanel); mainPanel.add(gridArrows); frame2.add(mainPanel); lefts = new ImageIcon(getClass().getClassLoader().getResource("leftarrow.png")); left = new JLabel(lefts); arrowsPanel.add(left); downs = new ImageIcon(getClass().getClassLoader().getResource("downarrow.png")); down = new JLabel(downs); arrowsPanel.add(down); ups = new ImageIcon(getClass().getClassLoader().getResource("uparrow.png")); up = new JLabel(ups); arrowsPanel.add(up); rights = new ImageIcon(getClass().getClassLoader().getResource("rightarrow.png")); right = new JLabel(rights); arrowsPanel.add(right); frame2.pack(); frame2.setVisible(true); frame2.setResizable(false); leftTimer = new Timer(20, new ActionListener() { public void actionPerformed(ActionEvent e) { leftMove.setLocation(leftMove.getX(), leftMove.getY() + stepSize); if (leftMove.getY() > 460){ System.out.println("booo"); } } }); downTimer = new Timer(40, new ActionListener() { public void actionPerformed(ActionEvent e) { downMove.setLocation(downMove.getX(), downMove.getY() + stepSize); } }); upTimer = new Timer(60, new ActionListener() { public void actionPerformed(ActionEvent e) { upMove.setLocation(upMove.getX(), upMove.getY() + stepSize); } }); rightTimer = new Timer(80, new ActionListener() { public void actionPerformed(ActionEvent e) { rightMove.setLocation(rightMove.getX(), rightMove.getY() + stepSize); }}); rightTimer.start(); leftTimer.start(); upTimer.start(); downTimer.start(); gamerGame = game; controller = screen; } public void disable() { frame2.setVisible(false); frame2.setEnabled(false); leftTimer.stop(); downTimer.stop(); upTimer.stop(); rightTimer.stop(); } public void enable() { frame2.setVisible(true); frame2.setEnabled(true); rightTimer.start(); leftTimer.start(); upTimer.start(); downTimer.start(); } public void keyPressed(KeyEvent e) { // +420 immediatly goes to bottom arrows int stepSize = 10; //System.out.println("here"); if(e.getKeyCode()== KeyEvent.VK_RIGHT) { //System.out.println("right"); rightMove.setLocation(rightMove.getX(), rightMove.getY() + stepSize); } else if(e.getKeyCode()== KeyEvent.VK_LEFT) { //System.out.println("left"); leftMove.setLocation(leftMove.getX(), leftMove.getY() + stepSize); if (leftMove.getY() > 400 && leftMove.getY() < 450) { System.out.println("aahhhhh"); controller.press(1); } } else if(e.getKeyCode()== KeyEvent.VK_UP) { //System.out.println("up"); upMove.setLocation(upMove.getX(), upMove.getY() + stepSize); } else if(e.getKeyCode()== KeyEvent.VK_DOWN) { //System.out.println("down"); downMove.setLocation(downMove.getX(), downMove.getY() + stepSize); } } public void keyReleased(KeyEvent e) { } public void keyTyped(KeyEvent e) { } public void set_start(){ leftMove.setLocation(left.getX(),-50); rightMove.setLocation(right.getX(),-50); upMove.setLocation(up.getX(),-50); downMove.setLocation(down.getX(),-50); score.setLocation(downMove.getX()+50,200); scorex = score.getX(); scorey = score.getY(); } @Override public void update(){ score.score_update(gamerGame.getScore()); score.combo_update(gamerGame.getCurrentCombo()); score.setLocation(scorex, scorey); score.setLocation(scorex, scorey); } }
scores_panel代码
package ddr.view; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.GridLayout; import javax.swing.JLabel; import javax.swing.JPanel; public class scores_panel extends JPanel { JLabel comboJLabel; JLabel scoreLabel; JLabel missJLabel; int score; int combo; public scores_panel() { setLayout(new GridLayout(2, 1,0,5)); setPreferredSize(new Dimension(200, 150)); score = 0; combo = 0; scoreLabel = new JLabel(score +" score"); comboJLabel = new JLabel(combo + " combo"); Font scoreFont = new Font("Arial", Font.PLAIN, 20); scoreLabel.setFont(scoreFont); Font comboFont = new Font("Arial", Font.PLAIN, 50); comboJLabel.setFont(comboFont); add(scoreLabel); add(comboJLabel); setOpaque(false); scoreLabel.setForeground(Color.WHITE); scoreLabel.setOpaque(false); comboJLabel.setForeground(Color.WHITE); comboJLabel.setOpaque(false); } public void score_update(int num){ score = num; scoreLabel.setText(score + " score"); this.revalidate(); this.repaint(); } public void combo_update(int num){ combo = num; comboJLabel.setText(combo + " combo"); this.revalidate(); this.repaint(); } }
问题原因与解决方法
核心原因
- 布局管理器冲突:主面板使用
FlowLayout,当调用revalidate()(scores_panel的更新方法中调用了此方法)时,布局管理器会重新计算所有组件的位置,覆盖你通过setLocation()手动设置的坐标,导致组件回到初始布局位置。 - 不必要的重绘触发:
scores_panel中更新文本后调用revalidate()完全多余,仅更新JLabel文本不会改变面板布局结构,反而会触发整个父容器的布局重排。 - 按键逻辑重复触发:左箭头按键的判定条件没有限制重复触发,当箭头在400-450区间内时,每次按键事件都会调用
controller.press(1),如果该方法又触发update,就可能形成循环。
解决步骤
1. 改用绝对布局避免自动重排
将主面板的布局管理器设置为null,这样布局管理器不会干预组件位置,所有坐标由你手动控制:
// 在GameplayGUI构造函数中替换原布局代码 mainPanel.setLayout(null);
同时给所有组件设置明确的bounds(替代setPreferredSize),示例:
// 给移动箭头设置bounds leftMove.setBounds(left.getX(), -50, leftIcon.getIconWidth(), leftIcon.getIconHeight()); downMove.setBounds(down.getX(), -50, downIcon.getIconWidth(), downIcon.getIconHeight()); upMove.setBounds(up.getX(), -50, upIcon.getIconWidth(), upIcon.getIconHeight()); rightMove.setBounds(right.getX(), -50, rightIcon.getIconWidth(), rightIcon.getIconHeight()); // 给scores_panel设置bounds score.setBounds(downMove.getX()+50,200,200,150); // 给gridArrows设置bounds gridArrows.setBounds(0, 350, 700, 150);
其他组件如arrowsPanel、space1等也需要设置对应的bounds,确保位置和大小固定。
2. 移除不必要的revalidate()调用
修改scores_panel的更新方法,删除revalidate(),仅保留文本更新即可(setText会自动触发组件重绘):
public void score_update(int num){ score = num; scoreLabel.setText(score + " score"); } public void combo_update(int num){ combo = num; comboJLabel.setText(combo + " combo"); }
3. 修复按键重复触发问题
添加标记变量,确保一次按键只触发一次判定:
// 在GameplayGUI中添加标记变量 private boolean leftHandled = false; // 修改keyPressed方法中的左箭头逻辑 else if(e.getKeyCode()== KeyEvent.VK_LEFT) { leftMove.setLocation(leftMove.getX(), leftMove.getY() + stepSize); if (leftMove.getY() > 400 && leftMove.getY() < 450 && !leftHandled) { System.out.println("aahhhhh"); controller.press(1); leftHandled = true; } } // 在keyReleased中重置标记 public void keyReleased(KeyEvent e) { if(e.getKeyCode() == KeyEvent.VK_LEFT){ leftHandled = false; } }
内容的提问来源于stack exchange,提问作者Keegan Pham
相关产品推荐
相关产品推荐

