You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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();
    }

}

问题原因与解决方法

核心原因

  1. 布局管理器冲突:主面板使用FlowLayout,当调用revalidate()(scores_panel的更新方法中调用了此方法)时,布局管理器会重新计算所有组件的位置,覆盖你通过setLocation()手动设置的坐标,导致组件回到初始布局位置。
  2. 不必要的重绘触发:scores_panel中更新文本后调用revalidate()完全多余,仅更新JLabel文本不会改变面板布局结构,反而会触发整个父容器的布局重排。
  3. 按键逻辑重复触发:左箭头按键的判定条件没有限制重复触发,当箭头在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 04:49:50